02_JVM原理与最佳实战总结导图版
2021-08-26 15:10:04 19 举报
AI智能生成
JVM学习总结
作者其他创作
大纲/内容
JVM参数
JDK1.8基本参数设置
-Xms512M -Xmx512M -Xmn256M -Xss1M -XX:MetaspaceSize=5M -XX:MaxMetaspaceSize=7M
JDK1.7基本参数设置
-Xms512M -Xmx512M -Xmn256M -Xss1M -XX:PermSize=128M -XX:MaxPermSize=128M
核心参数设置
1. -Xms: Java堆内存的大小
2. -Xmx: Java堆内存的最大大小
3. -Xmn: Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大了
4. -XX:PermSize:永久代大小
5. -XX:MaxPermSize:永久代最大大小
6. -Xss:每个线程的栈内存大小
2. -Xmx: Java堆内存的最大大小
3. -Xmn: Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大了
4. -XX:PermSize:永久代大小
5. -XX:MaxPermSize:永久代最大大小
6. -Xss:每个线程的栈内存大小
其它参数设置
-XX:MaxTenuringThreshold
具体是多少岁进入老年代
-XX:PretenureSizeThreshold
对象超过阀值直接进入老年代
-XX:-HandlePromotionFailure
内存提升失败处理
-XX:PretenureSizeThreshold= 1M
多大的对象直接进入老年代
-XX:InitialHeapSize
初始堆大小
-XX:MaxHeapSize
最大堆大小
-XX:NewSize
初始新生代大小
-XX:MaxNewSize
最大新生代大小
-XX:+PrintGCDetils
打印详细的gc日志
-XX:+PrintGCTimeStamps
这个参数可以打印出来每次GC发生的时间
-Xloggc:gc.log
这个参数可以设置将gc日志写入一个磁盘文件
-XX:TraceClassLoading -XX:TraceClassUnloading
追踪类加载和类卸载的情况,他会通过日志打印出来JVM中加载了哪些类,卸载了哪些类
-XX:+DisableExplicitGC
禁止显式执行GC,不允许你来通过代码触发GC
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/usr/local/app/oom
-XX:HeapDumpPath=/usr/local/app/oom
第一个参数意思是在OOM的时候自动dump内存快照出来,第二个参数是说把内存快照放到哪儿去
垃圾回收器参数
ParNew收集器
-XX:+UseParNewGC 使用ParNew垃圾回收器
默认给自己设置的垃圾回收线程的数量就是跟CPU的核数是一样的
Parallel Scavenge收集器
-XX:ParallelGCThreads 设置线程的数量
CMS(Concurrent Mark Sweep)收集器
-XX:HandlePromotionFailure
"-XX:HandlePromotionFailure" 参数在JDK 1.6以后就被废弃了,所以现在一般都不会在生产环境里设置这个参数了。在JDK1.6以后,只要判断”老年代可用空间”>“新生代对象总和” ,或者”老年代可用空间”> "历次Minor GC升入老年代对象的平均大小”,两个条件满足一个,就可以直接进行Minor GC,不需要提前触发Full GC了。
-XX:CMSFullGCsBeforeCompaction 执行多少次Full GC之后再执行一次内存碎片 整理的工作,默认是0,意思就是每次Full GC之后都会进行一次内存整理。
-XX:+ UseCMSCompactAtFullCollection 在Full GC之后要再次进行"Stop the World",停止工作线程,然后进行碎片整理,就是把存活对象挪到一起,空出来大片连续内存空间,避免内存碎片。
-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5 5次Full GC之后会触发一次Compaction操作, 也就是压缩操作
-XX:CMSInitiatingOccupancyFaction 设置老年代占用多少比例的时候触发CMS垃圾回收
-XX:+CMSParallelInitialMarkEnabled 在CMS垃圾回收器的"初始标记”阶段开启多线程并发执行
-XX:+CMSScavengeBeforeRemark 在CMS的重新标记阶段之前,先尽量执行一次Young GC
疑问:这个参数是否真有用
G1收集器
-XX:+UseG1GC 指定使用G1垃圾回收器
默认是2048个region,每个region大小是堆大小/2048,JVM最多可以有2048个Region,然后Region的大小必须是2的倍数
-XX:G1HeapRegionSize 指定region的大小
-XX:G1NewSizePercent 设置新生代初始占比的,其实维持这个默认值即可
刚开始的时候,默认新生代对堆内存的占比是5%
-XX:G1MaxNewSizePercent 设置新生代最大占比
在系统运行中,JVM其实会不停的给新生代增加更多的Region,但是最多新生代的占比不会超过60%
一旦Region进行了垃圾回收,此时新生代的Region数量还会减少, 这些其实都是动态的
-XX:SurvivorRatio=8 设置新生代的Eden和Survivor占比
-XX:MaxGCPauseMills G1执行GC的时候最多可以让系统停顿多长时间
默认是200ms
- XX:MaxTenuringThreshold 对象在新生代躲过了很多次的垃圾回收,达到了一定的年龄了
-XX:InitiatingHeapOccupancyPercent 触发新生代+老年代的混合垃圾回收大小
默认值是45%
老年代占据了堆内存的45%的Region的时候,此时就会尝试触发一个新生代+老年代一起回收的混合回收阶段
-XX:G1MixedGCCountTarget 混合回收的总次数
一次混合回收的过程中, 最后一个阶段执行几次混合回收,默认值是8次
-XX:G1HeapWastePercent
在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清理掉。
这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立即停止混合回收,意味着本次混合回收就结束了。
-XX:G1MixedGCLiveThresholdPercent region中存活对象低于多少才进行混合回收
默认值是85%,意思就是确定要回收的Region的时候,必须是存活对象低于85%的Region才可以进行回收
参数模板
-Xms4096M -Xmx4096M -Xmn3072M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+DisableExplicitGC -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oom
java对象内存计算
Java变量所占内存
Java基本(primitive)数据类型所占内存
引用(reference)类型在32位系统上每个占用4bytes(一个字长), 在64位系统上每个占用8bytes(一个字长)。
注:包装类型属于引用类型
注:包装类型属于引用类型
对象头
对象的内存布局
从上面的这张图里面可以看出,对象在内存中的结构主要包含以下几个部分:
Mark Word(标记字段):对象的Mark Word部分占4个字节,其内容是一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。
Klass Pointer(Class对象指针):Class对象指针的大小也是4个字节,其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址
对象实际数据:这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节。
对齐:最后一部分是对齐填充的字节,按8个字节填充。
Mark Word(标记字段):对象的Mark Word部分占4个字节,其内容是一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。
Klass Pointer(Class对象指针):Class对象指针的大小也是4个字节,其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址
对象实际数据:这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节。
对齐:最后一部分是对齐填充的字节,按8个字节填充。
指针压缩
(对象头 + 实例数据 + padding) % 8等于0,且0 <= padding < 8
说明:压缩指针是对先每个变量进行压缩,所有变量的内存总和最终要与是8的倍数。
工具
jstat
jstat -gc PID 间隔时间
jstat -gc 8199 1000
字段解释
1. SOC :这是From Survivor区的大小(所有单位都为KB)
2. S1C :这是To Survivor区的大小
3. SOU :这是From Survivor区当前使用的内存大小
4. S1U :这是To Survivor区当前使用的内存大小
5. EC :这是Eden区的大小
6. EU :这是Eden区当前使用的内存大小
7. OC :这是老年代的大小
8. OU :这是老年代当前使用的内存大小
9.MC:这是方法区(永久代元数据区)的大小
10. MU :这是方法区(永久代元数据区)的当前使用的内存大小
11. YGC :这是系统运行迄今为止的Young GC次数
12. YGCT :这是Young GC的耗时
13. FGC :这是系统运行迄今为止的Full GC次数
14. FGCT :这是Full GC的耗时
15. GCT :这是所有GC的总耗时
2. S1C :这是To Survivor区的大小
3. SOU :这是From Survivor区当前使用的内存大小
4. S1U :这是To Survivor区当前使用的内存大小
5. EC :这是Eden区的大小
6. EU :这是Eden区当前使用的内存大小
7. OC :这是老年代的大小
8. OU :这是老年代当前使用的内存大小
9.MC:这是方法区(永久代元数据区)的大小
10. MU :这是方法区(永久代元数据区)的当前使用的内存大小
11. YGC :这是系统运行迄今为止的Young GC次数
12. YGCT :这是Young GC的耗时
13. FGC :这是系统运行迄今为止的Full GC次数
14. FGCT :这是Full GC的耗时
15. GCT :这是所有GC的总耗时
jmap
jmap -heap PID
打印出来堆内存相关的一些参数设置,然后就是当前堆内存里的一些基本各个区域的情况
jmap -histo PID
按照各种对象占用内存空间的大小降序排列,把占用内存最多的对象放在最上面打印出来
jmap -dump:live,format=b,file=dump.hprof PID
生成堆内存转储快照
jhat dump.hprof -port 7000
通过图形化的方式去分析堆内存里的对象分布情况
MAT
分析内存快照工具
垃圾回收
垃圾回收机制
垃圾收集触发时机
年轻代
新生代的Eden区都快满了,此时就会触发Minor GC,把存活对象转移到Survivor2区去。
Full GC时会触发Minor GC
老年代
判断"-XX:-HandlePromotionFailure”的参数是否设置是否有设置。没有直接触发一次"Full GC"
老年代的内存大小,小于之前每一次Minor GC后进入老年代的对象的平均大小。
在Minor GC之前,一通检查发现很可能Minor GC之后要进入老年代的对象太多了,老年代放不下,此时需要提前触发Full GC然后再带着进行Minor GC;
在Minor GC之后,发现剩余对象太多放入老年代都放不下了。
-XX:CMSInitiatingOccupancyFaction 参数:如果老年代可用内存大于历次新生代GC后进入老年代的对象平均大小,但是老年代已经使用的内存空间超过了这个参数指定的比例,也会自动触发Full GC。
永久代里的类太多,触发了Full GC
手动执行System.gc()
G1收集器
Mixed GC
老年代在堆内存里占比超过45%就会触发
进行Minor GC之后的几种情况
第一种可能,Minor GC过后,剩余的存活对象的大小,是小于Survivor区的大小的, 那么此时存活对象进入Survivor区域即可。
第二种可能,Minor GC过后,剩余的存活对象的大小,是大于Survivor区域的大小,但是是小于老年代可用内存大小的,此时就直接进入老年代即可。
第三种可能,很不幸,Minor GC过后,剩余的存活对象的大小,大于了Survivor区域的大小,也大于了老年代可用内存的大小。此时老年代都放不下这些存活对象了,就会发生"Handle Promotion Failure"的情况,这个时候就会触发一次“FullGC"。
Full GC就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。疑问:老年代的先还是年代的先?先进行老年代GC,因为是年代空间放不下了才进行的Full GC。
因为这个时候必须得把老年代里的没人引用的对象给回收掉,然后才可能让Minor GC过后剩余的存活对象进入老年代里面。
如果要是Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致所谓的"OOM”内存溢出。
第二种可能,Minor GC过后,剩余的存活对象的大小,是大于Survivor区域的大小,但是是小于老年代可用内存大小的,此时就直接进入老年代即可。
第三种可能,很不幸,Minor GC过后,剩余的存活对象的大小,大于了Survivor区域的大小,也大于了老年代可用内存的大小。此时老年代都放不下这些存活对象了,就会发生"Handle Promotion Failure"的情况,这个时候就会触发一次“FullGC"。
Full GC就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。疑问:老年代的先还是年代的先?先进行老年代GC,因为是年代空间放不下了才进行的Full GC。
因为这个时候必须得把老年代里的没人引用的对象给回收掉,然后才可能让Minor GC过后剩余的存活对象进入老年代里面。
如果要是Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致所谓的"OOM”内存溢出。
元数据区
如果你在代码里大量用了类似上面的反射的东西,那么JVM就是会动态的去生成一些类放入Metaspace区域里
JVM自己创建的奇怪的类,他们的Class对象都是SoftReference, 也就是软引用的。
JVM在反射过程中动态生成的类的Class对象,他们都是SoftReference软引用的。
什么情况下Minor GC后的对象会进入老年代?
1、默认躲过15次GC之后进入老年代。
2、当前放对象的Survivor区域里,一批对象的总大小大于这块Survivor区域的内存大小的50%,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代。
3、大对象直接进入老年代。
2、当前放对象的Survivor区域里,一批对象的总大小大于这块Survivor区域的内存大小的50%,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代。
3、大对象直接进入老年代。
垃圾回收器
垃圾回收器分类
年轻代
Serial(单词意思:连续的)收集器:一个单线程收集器,在进行回收的时候,必须暂停其他所有的工作线程,直到收集结束。缺点:因为要完全暂停线程,所以用户体验不佳。但是由于新生代回收得较快,所以停顿的时间非常少,而且没有线程切换的开销,因此也简单高效。通过 -XX:+UseSerialGC参数启用。
ParNew(单词意思:标准的)收集器:这个是Serial收集器的多线程版本,适用于多核CPU的设备。但对于单核的设备来说,需要进行线程之间的切换,效率反而没有单线程的高。通过-XX:ParallelGCThreads参数限制收集的线程数,-XX:+UseParNewGC参数启用。
Parallel Scavenge(单词意思:平行清除)收集器:该收集器是我们文章中的所有例子的默认年轻代收集器。他的关注点和其他的收集器不同,其他的关注点是尽可能的缩短Full GC的时间。而该收集器关注的是一个可控的吞吐量。吞吐量=运行代码的时间/(运行代码的时间+GC的时间),通过参数-XX:MaxGCPauseMillis设置最大GC的停顿时间和-XX:GCTimeRatio 设置吞吐量的大小。-XX:+UseParallelGC参数启用。主要适合在后台运算而不需要太多交互的任务。
老年代
Serila Old收集器:该收集器是Serial收集器的老年代版,同样是一个单线程的收集器,优劣势和Serial收集器一样,这里就不多说了。
Parallel Old收集器:在我们之前文章的代码例子中默认的年老代收集器,也是Parallel Scavenge收集器的老年代版本。关注点也和Parallel Scavenge收集器一样,注重系统的吞吐量,适合于CPU资源敏感的场合。
CMS(Concurrent Mark Sweep)收集器:是一种以最短停顿时间为目标的收集器。当应用尤其重视服务的响应速度,希望系统能有最短的停顿时间,该收集器非常适合。
CMS默认启动的垃圾回收线程的数量是(CPU核数+ 3) /4
Concurrent Mode Failure问题
如果CMS垃圾回收期间,系统程序要放入老年代的对象大于了可用内存空间,此时会如何?
这个时候,会发生Concurrent Mode Failure,就是说并发垃圾回收失败了,我一边回收, 你一边把对象放入老年代,内存都不够了。
此时就会自动用"Serial Old"垃圾回收器替代CMS,就是直接强行把系统程序"Stop the World",重新进行长时间的GC Roots追踪,标记出来全部垃圾对象,不允许新的对象产生
然后一次性把垃圾对象都回收掉,完事儿了再恢复系统线程。
所以在生产实践中,这个自动触发CMS垃圾回收的比例需要合理优化一下,避免"Concurrent Mode Failure" 问题
这个时候,会发生Concurrent Mode Failure,就是说并发垃圾回收失败了,我一边回收, 你一边把对象放入老年代,内存都不够了。
此时就会自动用"Serial Old"垃圾回收器替代CMS,就是直接强行把系统程序"Stop the World",重新进行长时间的GC Roots追踪,标记出来全部垃圾对象,不允许新的对象产生
然后一次性把垃圾对象都回收掉,完事儿了再恢复系统线程。
所以在生产实践中,这个自动触发CMS垃圾回收的比例需要合理优化一下,避免"Concurrent Mode Failure" 问题
相关参数
-XX:CMSInitiatingOccupancyFaction 设置老年代占用多少比例的时候触发CMS垃圾回收, JDK 1.6里面默认的值是92%
-XX:+UseCMSCompactAtFullCollection
在Full GC之后要再次进行"Stop the World",停止工作线程,然后进行碎片整理,就是把存活对象挪到一起,空出来大片连续内存空间,避免内存碎片。
-XX:CMSFullGCsBeforeCompaction
意思是执行多少次Full GC之后再执行一次内存碎片 整理的工作,默认是0,意思就是每次Full GC之后都会进行一次内存整理。
G1收集器:根据你预设的gc停顿时间,给新生代分配一些Region,然后到一定程度就触发gc, 并且把gc时间控制在预设范围内,尽量避免一次性回收过多的Region导致gc停顿时间超出预期
大对象的分配和回收的策略
G1提供了专门的Region来存放大对象,而不是让大对象进入老年代的Region中
大对象的判定规则就是一个大对象超过了一个Region大小的50%
新生代、老年代在回收的时候,会顺带带着大对象Region一起回收
垃圾回收器搭配
垃圾回收器搭配图
JVM优化
JVM优化核心思想
所谓JVM优化,就是尽可能让对象都在新生代里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。
Full GC优化的前提是Minor GC的优化,Minor GC的优化的前提是合理分配内存空间,合理分配内存空间的前提是对系统运行期间的内存使用模型进行预估。所以只要对系统运行期间的内存使用模型做好预估,然后分配好合理的内存空间,尽量让Minor GC之后的存活对象留在Survivor里不要去老年代,然后其余的GC参数不做太多优化,系统性能基本上就不会太差。
新生代垃圾回收优化之一: Survivor空间够不够
尽量让每次Minor GC后的对象都留在Survivor里,不要进入老年代,这是你首先要进行优化的一个地方。
不需要针对小概率事件特意优化参数
如:因为必须是CMS触发Full GC的时候,系统运行期间还让200MB对象进入老年代,这个概率其实本身就很小,但是理论上是有可能的。
分析JVM问题基本流程
如何分析设置JVM堆内存大小流程
1、分析系统的背景?
2、分析核心业务流程是什么?
3、找系统的压力集中在哪里?
4、预测每秒种需要处理多少核心业务的请求?
5、统计处理每个核心业务请求的耗时是多少?
6、预估每个核心业务请求所占用的内存空间?
7、计算每秒处理核心业务请求所占用的内存空间?
8、预估完整的系统内存占用?
9、设置系统的JVM堆内存参数
2、分析核心业务流程是什么?
3、找系统的压力集中在哪里?
4、预测每秒种需要处理多少核心业务的请求?
5、统计处理每个核心业务请求的耗时是多少?
6、预估每个核心业务请求所占用的内存空间?
7、计算每秒处理核心业务请求所占用的内存空间?
8、预估完整的系统内存占用?
9、设置系统的JVM堆内存参数
JVM栈内存与永久代大小又该如何设置
一般永久代刚开始上线一个系统, 没太多可以参考的规范,但是一般你设置个几百MB, 大体上都是够用的
栈内存大小设置,一般也不会特别的去预估和设置的, 一般默认就是比如512KB到1MB,就差不多够了。
JVM优化流程
QPS预估
估算一下自 己负责的系统每个核心接口每秒多少次请求,每次请求会创建多少个对象,每个对象大概多大,每秒钟会使用多少内存空间?
这样接着就可以估算出来Eden区大概多长时间会占满?
然后就可以估算出来多长时间会发生一次Young GC,而且可以估算一下发生Young GC的时候,会有多少对象存活下来,会有多少对象升入老年代里,老年代对象增长的速率大概是多少,多久之后会触发一次Full GC。
通过一连串的估算,就可以合理的分配年轻代和老年代的空间,还有Eden和Survivor的空间
原则就是:尽可能让每次Young GC后存活对象远远小于Survivor区域,避免对象频繁进入老年代触发Full GC。
这样接着就可以估算出来Eden区大概多长时间会占满?
然后就可以估算出来多长时间会发生一次Young GC,而且可以估算一下发生Young GC的时候,会有多少对象存活下来,会有多少对象升入老年代里,老年代对象增长的速率大概是多少,多久之后会触发一次Full GC。
通过一连串的估算,就可以合理的分配年轻代和老年代的空间,还有Eden和Survivor的空间
原则就是:尽可能让每次Young GC后存活对象远远小于Survivor区域,避免对象频繁进入老年代触发Full GC。
压测之后合理调整JVM参数
线上系统的监控和优化
系统统一的JVM参数模板
频繁Full GC常见原因
1.系统承载高并发请求,或者处理数据量过大,导致Young GC很频繁,而且每次Young GC过后存活对象太多,内存分配不合理,Survivor区域过小,导致对象频繁进入老年代,频繁触发Full GC。
2.系统一次性加载过多数据进内存,搞出来很多大对象,导致频繁有大对象进入老年代,必然频繁触发Full GC
3.系统发生了内存泄漏,莫名其妙创建大量的对象,始终无法回收,一直占用在老年代里,必然频繁触发Full GC
4. Metaspace (永久代)因为加载类过多触发Full GC
5.误调用System.gc()触发Full GC
2.系统一次性加载过多数据进内存,搞出来很多大对象,导致频繁有大对象进入老年代,必然频繁触发Full GC
3.系统发生了内存泄漏,莫名其妙创建大量的对象,始终无法回收,一直占用在老年代里,必然频繁触发Full GC
4. Metaspace (永久代)因为加载类过多触发Full GC
5.误调用System.gc()触发Full GC
一些疑问
Parallel Scavenge是怎么控制吞吐量?
0 条评论
下一页