JVM知识图谱
2022-06-21 14:04:06 2 举报
AI智能生成
为你推荐
查看更多
Jvm线路相关知识图谱
作者其他创作
大纲/内容
内存泄露:是指有的内存地址太过碎片化而无法被利用
内存溢出:是指内存已经装满了,无法再装下更多的对象了
产生的问题
分区:eden区 s0、s1
JVM每次只会使用Eden区和某一个Survivor区
标记复制:eden+s0->存活对象复制到S1
YoungGC:Eden区满了,会首先触发YGC
晋升:存活的年龄超过晋升阈值
年轻代
FGC(Full Garbage Collection):等老年代的内存满了会触发FCG
标记清除整理:碎片整理
老年代
jdk8之后:Metaspace使用的是本地内存,默认情况下只有本地内存有关
元数据区(jdk1.7叫做持久代)
内存分代模型
栈上空间足够,对象比较小、对象比较简单
不需要用到此对象了,将对象出栈就可以了
JVM会首先尝试往栈上分配
对象是不是够大,如果足够大就直接放在老年代区
大对象直接进入老年代,使用-XX:PretenureSizeThreshold参数控制
分配的对象大小大于eden space
eden space剩余空间不足分配,且需要分配对象内存大小不小于eden space总空间的一半
在老年代区的对象经过一次全量垃圾回收FGC后,才有可能被回收掉
尝试分配到老年代区
eden区的:TLAB是线程私有的,每个线程都有自己的TLAB
eden的对象在经过一次GC后,如果被回收掉了,那就结束了生命周期
对象能否被存在eden区线程本地分配缓冲区-TLAB
对象分配过程
线程共享的内存区域:主要用于存放对象实例
堆
线程私有的数据区域:线程启动创建一个栈桢来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息
栈
存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
方法区
线程私有的数据区域,主要与虚拟机用到的 C所编写的 Native 方法相关
本地方法栈
属于线程私有的数据区域,是一小块内存空间,主要代表当前线程所执行的字节码行号指示器
程序计数器
内存模型JMM
不可中断的,不可分割的
JVM自身提供的对基本数据类型读写操作的原子性
通过synchronized和Lock实现原子性
lock(锁定): 作用于主内存的变量,把一个变量标记为一条线程独占状态
read(读取): 作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入): 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
write(写入): 作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中
JMM数据原子操作
原子性
当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值
volatile关键字可以保证可见性
synchronized和Lock也可以保证可见性
可见性
程序执行的顺序按照代码的先后顺序执行
可以通过synchronized和Lock来保证有序性
在本线程内观察,所有的操作都是有序的;而在一个线程内观察另一个线程,所有操作都是无序的
前半句指 as-if-serial 语义:线程内似表现为串行,后半句是指:“指令重排序现象”和“工作内存与主内存同步延迟现象”。
有序性
JMM的三大特性
堆区是线程共享区域
并发环境下从堆区中划分内存空间是线程不安全的
避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度
为什么有TLAB
Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域
多线程同时分配内存时,使用TLAB可以避免一系列的线程安全问题,提升内存分配的吞吐量,是快速分配策略
什么是TLAB
JVM确实是将TLAB作为内存分配的首选
“-XX:UseTLAB”设置是否开启TLAB空间
“-XX:TLABwasteTargetPercent”设置TLAB空间所占用Eden空间的百分比大小
TLAB的再说明
TLAB
避免FullGC过于频繁
【老年代最大连续空闲空间】大于【历次晋升到老年代的对象的平均大小】,产生一次YongGC
-XX:HandlePromotionFailure设置值是否允许担保失败
在JDK7之后,HandlePromotionFailure参数不会再影响到虚拟机的空间分配担保策略,规则变为只有老年代的连续空间大于新生代对象总大小或者大于历次晋升的平均大小就会进行Young GC,否则进行Full GC。
空间分配担保机制
相关概念
标记字(1个机器字)
class指针(1个机器字)
数组长度
对象头
实例数据(字段)
对象体
外部对齐
可以使用Instrument.getObjectSize()方法来估算一个对象占用的内存空间
对象存储
VM线程:单例VMThread对象,负责执行VM操作
定时任务线程:单例WathcerThread对象,模拟VM执行定时操作的计时器中断
GC线程:垃圾收集器中,用于支持并行和并发垃圾回收的线程
编译器线程:将字节码转成机器代码
信号分发线程:等待进程指示信号
分类
方法代码中被植入安全点检测入口
线程处于安全点状态,线程暂停执行
JVM的安全点状态:所有的线程都处于安全点状态
安全点
JVM线程堆栈
JVM内存模型
-service
以-开头的系统参数
环境变量属性
-D 系统属性
java -X 查看支持的非标准参数
-Xmx:最大堆内存
-Xms:内存空间初始大小
-Xmn:年轻代的大小
-Xss:线程栈的大小
-X 非标准参数
-XX:HeapDumpPath:内存快照文件地址
-XX:MaxTenuringThreshold:对象晋升老年代的阈值,默认15
-XX 非稳定参数
-XX:+-HeapDumpOnOutOfMemoryError:开启内存快照
- XX:+UseG1GC:使用G1GC算法
-XX:+PrintCommandLineFlags:查看默认使用的GC算法
-XX:+-flags布尔值开关
-XX:key=value 指定某个选项值
JVM启动参数
jps -mlv
jinfo pid
jps/jinfo:查看java进程
S0C、S1C、S0U、S1U:Survivor 0/1区容量(Capacity)和使用量(Used)
EC、EU:Eden区容量和使用量
OC、OU:年老代容量和使用量
PC、PU:永久代容量和使用量
YGC、YGT:年轻代GC次数和GC耗时
FGC、FGCT:Full GC次数和Full GC耗时
GCT:GC总耗时
jstat -gc -t pid 1000 100
jstat -gcutil pid 1000 100
jstat -gccause pid
jstat:查询jvm内部gc情况
jmap -heap pid:打印堆内存的使用情况
jmap -histo pid 对象占用空间直方图
jmap:查看heap或者类占用空间统计
jstack -l pid:查看当前进程下所有线程的情况
jstack:查看线程信息
jcmd pid VM.version:查看指定线程的vm的版本
jcmd pid GC.heap_info :查看指定进程的堆栈情况
jcmd:执行jvm相关整合命令
JVM命令行工具
jconsole:jdk自带
jvisualVM
VisualGc:idea的插件
jmc
arthas诊断分析工具
图形化工具
JVM调优工具
-Xms -Xmx限定其最小、最大值并设置为相同,减少内存分配的额外时间
年轻代和年老代将根据默认的比例(1:2):NewRadio来调整两者之间的大小
本着Full GC尽可能少的原则,让年老代尽可能缓存经常使用对象
在配置较好的机器上,能够为年老代选择并行收集算法: -XX:+UseParallelOldGC 。
蓄水池作用:适当增加eden区的大小
调优建议
-XX:+PrintGCDetails 打应垃圾收集的情况
-XX:PrintHeapAtGC:打印GC前后的详细堆栈信息
调优过程的参数
正常系统:分配率低
内存泄漏:分配速率持续大于回收速率
性能劣化:分配速率很高,回收速率也很高
分配速率:单位时间内分配的内存量(根据gc日志分析)
提升速率影响FGC的效率
短时间内频繁执行FGC
每次FGC后老年代使用率都很低
提升速率接近分配速率
过早提升
提升速率:用于衡量单位时间内年轻代提升到老年代的数据量
关注点
查询业务日志:请求压力、峰值、熔断等
查看系统资源和监控信息:CPU负载、内存、磁盘使用率、网络流量、网络抖动
查看性能指标:数据库、并发、磁盘使用、内存、网络等
排查系统日志:重启、崩溃、kill等
APM:链路问题
排查应用系统:启动参数、spring配置、jvm配置、数据库参数、GC问题、线程状态、代码漏洞、死锁等
排查资源竞争、坏邻居效应
抽样分析、调整代码、异步化、削峰填谷
疑难问题排查分析手段
排查问题思路
JVM调优
加载:找class文件
验证:验证格式和依赖
准备:静态字段和方法表
解析:符号解析为实际的引用
初始化:构造器、静态变量赋值和静态方法块
使用
卸载
类的生命周期
启动类:main方法的类
new一个类的时候
调用静态方法:加载包含静态方法的类
调用静态字段:加载包含静态字段的类
子类初始化触发父类的初始化
接口实现类被初始化后对应接口被初始化
初次调用methodhander实例,初始化方法指定的类
子主题
类加载的时机
子类引用父类静态变量的时候,不初始化子类
定义对象数组的时候
通过类名获取class对象的时候
class.forname执行初始化为false的时候
classloader加载loadclass方法不会初始化
不会初始化场景
加载jre/lib/rt.jar
启动类加载器
加载jre/lib/ext/*.jar
拓展加载器
加载classpath上指定的类库
应用加载器
加载器
加载器首先会把请求委派给父类加载器
双亲委派
如果加载器在加载某个类的时候,发现这个类依赖于另外几个类或接口,也会去尝试加载这些依赖项
负责依赖
一旦某个类被一个类加载器加载,那么它会缓存这个加载结果,不会重复加载。
缓存加载
加载器特点
JVM类加载器
当前真正执行的方法的局部变量和输入参数
活动线程
所有类的静态字段
JNI引用
可达对象/根对象GC Roots
黑色:根对象,或者该对象与它的子对象都被扫描过(对象被标记了,且它的所有field也被标记完了)。
灰色:对象本身被扫描,但还没扫描完该对象中的子对象(它的field还没有被标记或标记完)。
白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,既垃圾对象(对象没有被标记到)。
有至少一个黑色对象在自己被标记之后指向了这个白色对象
所有的灰色对象在自己引用扫描完成之前删除了对白色对象的引用
漏标条件
remark(重新标记)阶段需要全量重新深度扫描,STW时间长
CMS采用的是 增量更新
SATB 与RSet 配合,Remark阶段无需全量深度扫描,GC效率高
G1采用的是 原始快照
漏标问题
三色标记
所有的应用线程全部暂停
STW
大部分新生对象很快无用,存活较长时间的对象,可能存活更长的时间
分代假设
Rset
有一个引用指向一个对象,计数就加1 ,直到这个数为0,就会被当作垃圾
引用计数
优势:可以处理循环依赖,只扫描部分对象
从根上对象开始搜索
根可达算法
如何找到垃圾
GC概念
标记:遍历所有的可达对象,并在本地内存native中分门别类记录
清除:清除不可达对象,释放内存
并行GC和CMSGC的原理
标记清除
清除算法
从eden区+s0区复制存活对象到S1,清除eden区和s0区
标记复制:Young区的算法
复制算法
标记清除整理:清理后对不连续的内存空间进行压缩碎片整理
整理算法
GC算法
每次垃圾回收只有少量的对象存活,直接从from区+eden区复制到to区后,清除from区和eden区
老年代默认都是存活的对象,采用移动的方式,标记可达对象,删除不可达对象。整理老年代空间的内存是将所有存活区的对象复制到老年代空间进行依次存放。(对象晋升的概念)。
GC原理
-XX:+UseSerialGC配置使用的。
对年轻代使用标记复制算法,对老年代使用标记清除整理算法
-XX:+UseParNewGC:可以对年轻代进行并行处理
单线程执行,应用需要暂停
串行GC
-XX:UseParallelGC:年轻代和老年代的垃圾回收都会触发STW事件
年轻代使用标记复制算法,老年代使用标记清除整理算法
-XX:ParallelGCThreads=N指定并行处理GC的线程数量,默认CPU核心线程数。
多线程并行执行垃圾回收,关注高吞吐。业务线程暂停
jdk 6\\7\\8默认GC算法
新生代并行回收器,内存分布使用的复制算法
是支持GC自适应的调节策略,使用-XX:UseAdaptiveSizePolicy参数开启
Young GC / Parallel Scavenge
使用的基于“标记-整理”算法优化的“Mark–Summary-Compaction”算法
Mark:采用串行标记所有从GC Roots可直达的对象,然后并行标记所有存活的对象。
Summary:Region的密度 = 存活对象的内存大小 / Region内存大小 ,summary阶段查到密度需要整理的point
Compaction:需要整理的部分(summary阶段统计),采用“整理”算法进行并行操作
过程
Full GC / PSCompact(ParallelOld GC)
GC模式
并行GC
-XX:+UseConMarkSweepGC
年轻代采用并行的STW方式的标记复制算法,老年代使用并发标记清除算法
CMS使用的并发线程等于核心线程的1/4.
不进行内存压缩碎片处理,使用空闲的free-lists管理内存空间的回收
初始化标记
并发标记
并发预处理
最终标记 STW
并发清除
并发重置
CMSGC执行的6个阶段
只能做老年代回收器
在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代
point-out(外部指针)
老年代中有一块区域用来记录指向新生代的引用
JVM内部Rset(Remember Set) 用来记录跨代对象引用关系
多线程并发标记和清除,关注于降低延迟
判断当前 Old 区使用率是否大于阈值,则触发 CMS GC
-XX:CMSInitiatingOccupancyFraction 进行设置回收阈值,如果没有设置,默认为 92%
剩余8%,用于并发清除和并发重置过程中,产生的新对象存储
CMS GC
并发的 CMS GC 算法,退化为 Foreground 单线程串行 GC 模式
内存碎片导致的 Promotion Failed
增量收集担保失败
晋升失败(Promotion Failed)导致退化
CMS 的并发清理阶段,Mutator 还在运行,因此不断有新的垃圾产生
Old 区回收的阈值不能太高,否则预留的内存空间很可能不够,从而导致 Concurrent Mode Failure 发生
并发模式失败(Concurrent Mode Failure)导致的退化
GC退化
进行标记时,除了从GC ROOTS开始遍历,还会从RSet遍历
三色标记+Incremental Update算法
CMSGC
Young GC 阶段
使用point-in(内部指针)来解决
标记哪些新生代引用老年代
每个Region中都有一个RSet,记录其他Region到本Region的引用信息
RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index
Remembered Set:跟踪指向某个heap区内的对象引用
底层数据结构以 Bit Map实现。
如果一个Card中的对象有引用指向Young区,则将其标记为Dirty Card,下次需要进行YoungGC时,只需要去扫描Dirty Card即可。
引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另外一个概念,卡表(Card Table)
一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间
Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址
卡表(Card Table)
通过划分多个内存区域增量整理和回收,进一步降低延迟
三色标记+snapshot at the begining (SATB)算法
专门存放巨型对象
一个对象占用的空间超过了分区容量50%以上
Humongous区
Eden空间耗尽时会被触发
根扫描:静态和本地对象被扫描
更新RS:处理dirty card队列更新RS
处理RS:检测从年轻代指向年老代的对象
对象拷贝:拷贝存活的对象到survivor/old区域
处理引用队列:软引用,弱引用,虚引用处理
GC过程
YoungGC
Mix GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区
初始标记(STW):G1 GC 对根进行标记
根区域扫描(非STW):G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象
并发标记(非STW):G1 GC 在整个堆中查找可访问的(存活的)对象,可以被STW的年轻代回收打断
最终标记(Remark,STW)该阶段是 STW 回收,帮助完成标记周期。G1 GC 清空 SATB 缓冲区,跟踪未被访问的存活对象,并执行引用处理。
清除垃圾(Cleanup,STW)G1 GC 执行统计和 RSet 净化的 STW 操作
全局并发标记
拷贝存活对象
MixedGC
G1GC的分区是逻辑分区,并不是物理分区
分区
G1GC垃圾优先
Serial+SerialOld实现单线程的低延迟垃圾回收机制
ParNew+CMS实现多线程低延迟垃圾回收机制
ParallelScavenge和ParallelScavengeOld实现多线程高吞吐量垃圾回收机制
常用组合
GC策略
JVM知识图谱
0 条评论
回复 删除
下一页