JVM
2022-02-17 10:29:50 0 举报
AI智能生成
JVM知识汇总
作者其他创作
大纲/内容
实战
Java 堆溢出
虚拟机栈溢出
本地方法栈溢出
方法区溢出
常量池溢出
本机直接内存溢出
垃圾回收
对象回收的判断
1.引用计数算法
给对象添加一个引用计数器,
每一个地方引用它,计数器值就加1,
对象引用失效的时候,计数器就减1.
计数器值为0的时候,此对象为垃圾
每一个地方引用它,计数器值就加1,
对象引用失效的时候,计数器就减1.
计数器值为0的时候,此对象为垃圾
优点:判定效率高
缺点:对象间循环依赖,此算法
无法正确判断是否为垃圾对象
主流虚拟机没有这种算法
2.可达性分析算法
GC Roots"的对象作为起始点
从这些节点开始向下搜索,
搜索所走过的路径称为引用链(Reference Chain)
当一个对象到GC Roots没有任何引用链相连时,
则证明此对象是不可达的
从这些节点开始向下搜索,
搜索所走过的路径称为引用链(Reference Chain)
当一个对象到GC Roots没有任何引用链相连时,
则证明此对象是不可达的
作为GC Roots对象
栈帧中本地变量表中引用的对象
方法区类静态属性引用的对象
方法区中常量引用的对象
方法栈JNI引用的对象
强引用 Object obj = new Object
软引用 有用非必须
弱引用 非必须
虚引用 最弱的引用
枚举根节点
安全点
方法返回前
调用某个方法之后
抛出异常的位置
循环的末尾
安全区域
方法区回收
垃圾回收效率较低
回收内容
废弃常量
无用类
1.该类所有实例都被回收
2.加载该类的ClassLoader被回收
3.该类对应的class对象没有被引用,无法在任何地方反射访问该类方法
垃圾收集算法
分代收集算法
根据不同分代的特点,选择合适的垃圾收集算法
新生代
绝大多数对象朝生夕死,会被回收,所以采用复制算法
老年代
大多数对象不会死去,采用标记-清理或者标记-整理算法
复制算法(适用于年轻代)
1.内存按容量划分为大小相等的两块,其中一块用于存放对象
2.一块内存用完了,标记存活对象,将存活对象复制到另外一块
3.将有垃圾对象的那块清空内存
缺点1:在对象存活率较高时,复制操作次数多,效率降低
缺点2:內存缩小了一半,需要额外空间做分配担保
标记清除算法(适用于老年代)
标记存活对象,统一回收未被标记的对象
缺点:产生大量不连续的内存碎片
标记整理算法(适用于老年代)
1.标记步骤和标记清除算法相同
2.标记完后垃圾对象移动到一端,然后在清理掉
垃圾收集器
Serial收集器(最基本的收集器)
Serial新生代
单线程
垃圾收集的时候暂停所有线程
优点简单高效,缺点停顿时间长
年轻代-复制算法,老年代-标记整理算法
-XX:+UseSerialGC, -XX:+UseSerialOldGC
Serial Old收集器
Serial的老年代版本
Parallel收集器
Parallel Scavenge收集器
多线程,吞吐量高
新生代收集器
年轻代-复制算法,老年代-标记整理算法
JDK8新生代默认的垃圾收集器
Parallel Old收集器
Parallel的老年代版本
JDK8老年代默认的垃圾收集器
ParNew收集器
和Parallel类似,可以和CMS一起使用
CMS收集器
STW时间短,但是整个垃圾收集时间可能变长
使用标记-清除算法,可以通过参数配置,多少次FGC后做碎片整理
1.初始标记 STW
记录GC Root直接引用的对象,不做根节点枚举,耗时忽略不计
2.并发标记(可与用户线程并行)
从GC Root直接引用的对象开始遍历整个对象图
因为用户程序继续执行,可能导致已经标记过的对象状态发生改变,例如:对象复活
3.重新标记 STW
修正并发标记过程中用户程序新产生的浮动垃圾
用到三色标记中的增量更新算法做重新标记
4.并发清除(可与用户线程并行)
开启用户线程,同时GC线程开始对未标记的区域进行清扫
这个阶段如果有新增的对象会被标记为黑色,不会被回收,详见三色标记算法
(用户程序执行过程中新生成的对象没有被标识过,并且不能被回收)。用的是增量更新
(用户程序执行过程中新生成的对象没有被标识过,并且不能被回收)。用的是增量更新
使用标记清除算法
ConCurrent Mode Failure
并发阶段用户线程也会产生垃圾,可能会有大对象放到老年代,而老年代大小又不够
此时会STW,并用Serial Old来做FullGC
此时会STW,并用Serial Old来做FullGC
核心参数
-XX:+UseConcMarkSweepGC:启用cms
-XX:ConcGCThreads:并发的GC线程数
-XX:+UseCMSCompactAtFullCollection:FullGC之后做整理(减少碎片),做整理阶段会STW,因为会改写对象内存地址
-XX:CMSFullGCsBeforeCompaction:多少次FullGC之后做一次内存碎片的压缩整理(默认0)
-XX:CMSInitiatingOccupancyFraction:当老年代使用达到该比例时会触发FullGC(默认是92%),避免ConCurrent Mode Failure
-XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),
如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
-XX:+CMSScavengeBeforeRemark:在FullGC前启动一次minor gc,目的在于减少老年代对年轻代的引
用,降低CMSGC的标记阶段时的开销,一般CMS的GC耗时80%都在标记阶段
用,降低CMSGC的标记阶段时的开销,一般CMS的GC耗时80%都在标记阶段
-XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
-XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW
G1收集器
G1将Java堆划分为多个大小相等的独立区域(Region),JVM最多可以有2048个Region。
物理上不分年轻代和老年代,只是逻辑上还分年轻代和老年代。年轻代和老年代不再是物理隔阂,它们是Region的集合
G1把整个Java堆划分成2046个大小相等的独立region,默认年轻代占整个堆内存的5%
从整体上来看采用了标记-整理算法,从局部上来看采用了复制算法
建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内完成垃圾收集。
Humongous区
对象优先放入Eden区,如果对象大小超过Eden区的50%,则直接放入Humongous区
G1回收步骤
初始标记(STW)
并发标记
最终标记(STW),同CMS的重新标记
筛选回收(STW),计算回收收益比,根据用户所期望的GC停顿时间来制定回收计划,
收集收益比最大的region。筛选回收不一定把所有垃圾全部清理完
收集收益比最大的region。筛选回收不一定把所有垃圾全部清理完
G1垃圾收集分类
YoungGC
YoungGC并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收大概要多久时间,
如果回收时间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,
不会马上做Young GC,直到下一次Eden区放满,G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的值,
那么就会触发Young GC
如果回收时间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,
不会马上做Young GC,直到下一次Eden区放满,G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的值,
那么就会触发Young GC
MixedGC
老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,回收所有的 Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区,正常情况G1的垃圾收集是先做 MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够 的空region能够承载拷贝对象就会触发一次Full GC
FullGC
ZGC
ZGC是基于指针着色的并发标记算法,目的是减少STW
三色标记
回收标记不在记录在对象头上,记录在对象指针上,用对象指针内存位数来标识回收标识
标记-Mark
转移-relocate
重定位-remap
回收步骤
标记阶段
初始标记(STW)
并发标记
再标记(STW)
转移阶段
并发转移准备
初始转移(STW)
并发转移
GC日志
GC日志最前面时间是虚拟机启动以来经过的秒数
【GC 【Full GC 垃圾收集的停顿类型
类加载机制
什么是类的加载机制
类加载的生命周期
加载
1.通过类的全限定名称(包名+类名)获取类的二进制流(没有指明必须从哪里获取)
1. 从压缩包中读取,如jar、war
2. 从网络中获取,如Web Applet
3. 动态生成,如动态代理、CGLIB
4. 由其他文件生成,如JSP
5. 从数据库读取
6. 从加密文件中读取
2.将这个二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构(InstanceKlass)
3.在堆区生成java.lang.Class对象(InstanceMirrorKlass),作为方法区这个类中各种数据的访问入口
类的静态属性也是存放在InstanceMirrorKlass中的
类的静态属性也是存放在InstanceMirrorKlass中的
4.加载和连接是交叉进行的
连接
验证
确保Class文件的字节流中包含的信息符合当前虚拟机要求
文件格式验证
是否以cafebaby开头
版本号是否正确
元数据验证
是否有父类
是否继承了final类
Java语法
字节码验证
校验对虚拟机的安全性危害
符号引用验证
准备
为类变量分配内存并赋初值
通知虚拟机为ConstantValue分配值(final没有赋初值一说,直接赋值)
解析
符号引用转换为直接引用
1.类或接口的解析
2.字段解析
3.类方法解析
4.接口方法解析
初始化
执行构造器 <clinit>
收集类中所有类变量的赋值动作和静态语句块中的合并语句
子类的clinit()执行之前,父类clinit()一定执行完成
生成的clinit代码段顺序和定义的顺序是一致的
<clinit>不是必须的,类或者接口中没有对类变量赋值,编译器就可以不生成这个方法
不会执行父接口的(),除非要使用父接口中定义的变量
多线程环境下可以正常加锁,同步,只有一个会去执行,其余挂起,如果有一个线程耗时,那么造成多个线程阻塞
使用
卸载
类加载器
双亲委派模型
启动类加载器(虚拟机中的一部分)
将虚拟机识别的类库加载到内存
其他的类加载器(独立于虚拟机外部)
扩展类加载器
应用程序类加载器
负责加载ClassPath上指定的类库
自定义类加载器
工作过程
1. 防止重复加载同一个.class, 保证数据安全
2. 保证核心.class不能被篡改, 保证了Class执行安全
打破双亲委派模型
自定义类加载器
SPI机制
虚拟机字节码执行引擎
运行时栈帧结构
局部变量表
操作数栈
动态连接
方法返回地址
分派
静态分派
垃圾收集底层算法实现
三色标记
并发标记过程中,用户程序还在跑,导致对象间的引用会发生变化,会有多标和漏标的情况
三色分类
黑色:表示对象已经被垃圾收集器访问过,且对象关联的所有引用对象都已经被扫描过了
灰色:表示对象已经被垃圾收集器访问过,但至少存在一个关联对象还没被扫描过
白色:表示对象尚未被垃圾收集器访问过
多标-浮动垃圾
在并发标记过程中,如果由于方法运行结束导致部分局部变量(gcroot)被销毁,这个gcroot引用的对象之前又被扫描过(被标记为非垃圾对象),
那么本轮GC不会回收这部分内存。这部分本应该回收但是没有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响垃圾回收的正确性,
只是需要等到下一轮垃圾回收中才被清除。
那么本轮GC不会回收这部分内存。这部分本应该回收但是没有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响垃圾回收的正确性,
只是需要等到下一轮垃圾回收中才被清除。
针对并发标记(还有并发清理)开始后产生的新对象,通常的做法是直接全部当成黑色,
本轮不会进行清除。这部分对象期间可能也会变为垃圾,这也算是浮动垃圾的一部分。
本轮不会进行清除。这部分对象期间可能也会变为垃圾,这也算是浮动垃圾的一部分。
漏标-读写屏障
增量更新
增量更新就是当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用关系记录到集合中,
在重新标记过程中将这些记录过的引用关系中的黑色对象为根,重新扫描一次。CMS用增量更新
在重新标记过程中将这些记录过的引用关系中的黑色对象为根,重新扫描一次。CMS用增量更新
原始快照
当灰色对象要删除指向白色对象的引用关系时,将这个要删除的引用记录下来,在重新扫描中将这些记录过的引用关系中的灰色对象为根,重新扫描一次,这样就能扫描到白色的对象(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)。G1用原始快照
记忆集和卡表
调优思路
1. jinfo -flags pid得到各个区的内存分配情况
2. jstat -gc pid 2000 10000估算年轻代对象增长的速率
3. 根据年轻代对象增长速率我们能够根据eden区的大小推算出Young GC大概多久触发一次
4. 每次Young GC后有多少对象存活和进入老年代:根据Young GC的频率,
观察每次Young GC后大概多少对象进去老年代,从而可以推算出老年代对象增长速率
观察每次Young GC后大概多少对象进去老年代,从而可以推算出老年代对象增长速率
5. 知道了老年代对象的增长速率就可以推算出Full GC的触发频率了
优化思路其实简单来说就是尽量让每次Young GC后的存活对象小于Survivor区域的50%,都留存在年轻代里。
尽量别让对象进入老年 代。尽量减少Full GC的频率,避免频繁Full GC对JVM性能的影响
尽量别让对象进入老年 代。尽量减少Full GC的频率,避免频繁Full GC对JVM性能的影响
jmap -info看下创建对象的情况,有哪些大对象被创建了
运行时数据区域
程序计数器PCR
1.较小的内存
2.当前线程执行的字节码行号指示器
3.字节码指示器:通过改变这个计数器的值来选取下一条需要执行的指令
4.线程私有
5.如果执行的是java方法,则记录的是字节码指令的地址,如果是Native方法,计数器值为空
6.不会内存溢出
java虚拟机栈
1.线程私有
2.生命周期和线程相同
3.每个方法创建栈帧
1.局部变量表
A.存放编译器可知的各种基本数据类型,对象引用
B.64位的long,dubbo占用两个Slot
C.所需的内存在编译期就确定
2.操作数栈(参数传递池)
算术运算的时候是通过操作数栈来进行的
调用其他方法的时候是通过操作数栈来进行参数传递的
3.动态链接(运行中方法的符号引用的实际指向地址)
保存当前运行方法符号引用的实际指向地址, 指向常量池的类方法地址
解析
1. Class文件在编译过程中,一切方法调用在Class文件里面存储的都只是符号引用
2. 在class文件的加载过程中, 会把符号引用转换为直接引用(方法在实际运行时内存布局中的入口地址)
以上为解析调用的过程, 是静态的, 即在类加载时便清楚对象调用方法的实际指向地址
分派
1. 静态分派--场景:重载--编译过程中根据方法的参数类型确定使用哪个方法的符号引用
2. 动态分派--场景:重写--运行过程中根据对象的实际类型选择使用父类/子类中定义的方法的类方法
4.返回地址
5.异常情况
A:线程请求的栈深度大于虚拟机允许的深度
B:栈拓展内存不够用,内存溢出
本地方法栈
1.线程私有
2.执行的是native方法
3.异常情况和虚拟机栈相同
Java 堆
1.jvm中内存最大的一块
2.线程共享
3.存放对象实例
4.垃圾回收的主要区域
5.新生代
1.Eden
2.From Survivor
3.To Survivor
6.老年代
7.多个线程私有的分配缓存区TLAB
8.物理上不连续的内存空间
9.异常:内存溢出
(永久代)方法区
1.线程共享
2.存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码
3.运行时常量池(存储编译器生成的字面量和符号引用)
4.使用直接内存
直接内存
NIO中使用这块内存居多(例如: netty零拷贝)
非堆内存受总内存大小限制
对象的创建
1.new 指令创建对象
A:检查这个指令的参数能否在常量池中找到类的符号引用
B:检查这个符号引用是否已经被加载、解析、初始化过
C:没有的话先进行类加载过程
D:类加载完成后,分配内存
2.分配内存方式
A:指针碰撞
假设内存规整,指针一边是空闲空间一边是被分配空间,通过指针移动表示使用了多少内存
B:空闲列表
C:两种方式取决于垃圾收集器类型,带有压缩整理的垃圾收集器可以使用指针碰撞
D:并发情况下线程不安全
CAS
按照线程划分在不同的空间TLAB
E:内存分配好的话将内存空间初始化为0,然后将hashCode,分代年龄等信息保存在对象头
3.对象的内存布局
A:对象头
第一部分存储hashCode,分代年龄,锁信息等数据(MarkWord)
第二部分类型指针(通过这个指针确定是哪个类的实例)
数组的话还存储数据的长度
B:实例数据
类的字段信息,包括父类的字段信息等
存储顺序受分配策略的影响:相同宽度的字段总是被分配到一起
父类中的字段信息在子类之前
C:对齐填充
虚拟机要求对象大小是8字节的整数倍
如果对象大小不是8字节的整数倍的话,需要通过占位符补全
4.对象的访问定位
句柄访问 详看对象访问句柄图 优点:对象移动reference不用修改,句柄池更新即可
直接指针访问 详细看对象指针图 优点:速度块
假设内粗不规整,占用内存后的对象会记录在一张列表上
5.指针压缩
开启指针压缩:-XX:+UseCompressedOops
减小对象头大小,因为对象头放在堆内存,如果对象头太大,堆上只能放更少的对象,会触发GC
同样大小的堆可以放更多对象,避免触发多次GC
内存分配策略
年轻代
对象主要分配在新生代的Eden区域
启动本地线程分配缓存的话,则优先在TLAB上分配
优先将对象分配在新生代上, Eden区域内存不够时发送YGC
老年代
大对象直接进入老年代(在Serial和ParNew有效)
可以配置参数(PretenureSizeThreshold)指定大于多大对象直接进入老年代,防止年轻代内存复制频繁
长期存活的对象进入老年代
1.虚拟机给对象添加年龄计数器-MaxTenuringThreshold
2.对象动态年龄判断
3.老年代空间分配担保机制(这种情况会导致FGC次数比YGC次数多)
1.Minor GC之前,JVM会判断老年代最大可用的内存是否大于年轻代所有对象内存总和,条件成立那么认为是安全的
2.是否设置了-XX:-HandlePromotionFailure,没有设置的话尝试进行一次Minor GC,
这个是有风险,因为老年代剩余的内存可能不够
这个是有风险,因为老年代剩余的内存可能不够
3.老年代会判断老年代剩余的内存是否小于历史晋升到老年代对象的平均值, 小于的话则需要FULL GC 来腾出更多的内存
栈上分配
有些对象如果作用域只在方法体内,对象可以分配在栈上,而不是堆上,在方法结束后随栈帧内存一起被回收掉
逃逸分析
分析对象的作用域,当一个对象在方法中被定义后,它可能被外部方法所引用
标量替换
通过逃逸分析,确定对象不会被外部访问(通过逃逸分析得出对象不逃逸),并且对象可以被进一步分解,JVM不会创建该对象,而是将对象的成员变量分解为若干个被这个方法使用的成员变量替代,这些替代的成员变量在栈上分配空间,这样就不会因为没有一大片连续的内存空间导致对象内存不够分配
常用的命令
jps 显示指定系统内所有的虚拟机进程
jstat -gc 监视虚拟机各种运行状态的信息
jinfo -flags pid:实时查看,调整虚拟机参数
jmap -histo pid:查看内存信息、实例个数以及占用内存大小
jmap -heap pid:查看JVM堆信息
jmap -dump:live,format=b,file=filename pid
jhat 虚拟机堆转储快照分析工具
jstack 生成虚拟机当前时刻的线程快照
类文件结构
class文件是一组以8字节为基础单位的二进制流
由无符号数和表构成
0 条评论
下一页