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