1. JVM的分代模型
年轻代和老年代:
年轻代特点是创建之后很快就会被回收。
老年代特点是需要长期存在的。
大部分的正常对象,都是优先在新生代分配内存的。
在分配对象到新生代时,发现内存不够会触发垃圾回收,叫Minor GC(Young GC)
一个实例对象在新生代中,每次回收后还存在,则年龄就会增加1。超过一定年龄,即移到到老年代里面。
2. JVM核心参数
| -Xms | 堆内存大小 |
|---|---|
| -Xmx | 堆内存最大大小 |
| -Xmn | 堆内存中,新生代大小,扣除剩下为老年代大小 |
| -XX:PermSize | 永久代大小 |
| -XX:MetaspaceSize | 1.8后 |
| -XX:MaxPermSize | 永久代最大大小 |
| -XX:MaxMetaspaceSize | 元空间最大大小 |
| -Xss | 每个现场的栈内存大小 |
3. 什么时候会触发垃圾回收
内存快满了,会触发回收。
可达性分析,GC Roots:局部变量、静态变量。
强引用,不会回收。
弱引用,内存不够会回收。
4. 垃圾回收算法
复制算法:应用在新生代,把内存划分为两块区域,使用其中一块,待一块快满的时候,把里面存活对象移到另外一块区域。对内存使用效率低。
复制算法的优化:Eden区+Survivor区
1个Eden区,2个Survivor区
每次将Eden+1个Survivor区存活的对象,移到另外一个Survivor区中。好处是只有10%内存是被闲置的。
5. 何时进入老年代
- 躲过15次GC之后进入老年代,-XX:MaxTenuringThreshold=15来设置
- 动态对象年龄判断:当前放对象的Survivor区里,一批对象的总大小大于了这块Survivor区域的内存大小的50%,那么此时等于这批对象年龄的对象,就可以直接进入老年代。例如:Survivor2有两个存活对象,年龄是2,然后内存加起来超一半,此时这个区域的大于等于2岁的对象,全部进入老年代。实际逻辑如下:1岁+2岁+3岁+N岁对象总和超一半,则把N岁以上的进入老年代。
- 大对象直接进入老年代,-XX:PretenureSizeThreshold=1046576,即大于1M对象直接进老年代。
- Minor GC后的对象太多,超过了Survivor区大小的也直接进老年代
老年代使用的是标记整理算法
6. 垃圾回收器
ParNew、CMS、G1
Serial和Serial old:分别回收新生代和老年代的垃圾对象,单线程.
ParNew和CMS:多线程的,使用-XX:+UseParNewGC参数开启;-XX:ParallelGCThreads设置线程数,一般不修改。
CMS 4个阶段:初始标记(STW,速度很快)、并发标记(最耗时,不影响系统工作)、重新标记(STW,速度很快)、并发清理(耗时,不影响系统工作)。
CMS的垃圾回收线程是比较耗费CPU资源的,默认启动的垃圾回收线程数量是(CPU核+3)/4
Concurrent Mode Failure,CMS垃圾回收的触发机制,其中有一个就是当老年代内存占用达到一定比例,就自动执行GC。-XX:CMSInitiatingOccupancyFaction=92%,因为在回收的时候,还是允许程序放入老年代对象的,当进入的大于剩余的8%内存,就会发送Concurrent Mode Failure,此时Serial Old会替代CMS,就会强行STW,重新进行长时间的GC Roots追踪,标记出全部垃圾对象,不允许新的对象产生,然后一次性回收,恢复正常使用。
内存碎片:-XX:UseCMSCompactAtFullCollection。默认是打开的,Full GC后,会STW,进行碎片整理。 -XX:CMSFullGCsBeforeCompaction,执行多少次后进行碎片整理,默认是0,即每次都会进行碎片整理。 使用CMS垃圾回收器的话,推荐使用的模板为:
-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0
G1:更加优秀的算法和设计机制
Java堆内存拆分为多个大小相等的Region,也会有分代概念,只是逻辑上的概念,设置一个垃圾回收的预期停顿时间,通过追踪每个Region中可以回收对象的大小和预估时间来实现控制回收时间。
参数:
-Xms与-Xmx来设置堆大小
JVM最多有2048个Region,比如堆大小是4G,则除以2048个Region,每个Region就2M,也可以-XX:G1HeapRegionSize 指定数量,刚开始默认新生代对堆内存占比是5%,-XX:G1NewSizePercent,来设置比例,一般不需要调整,新生代默认不超过60%,-XX:G1MaxNewSizePercent控制,-XX:MaxGCPauseMills参数。默认200ms,即停顿200ms进行垃圾回收。
大对象Region,G1专门提供Region放置大对象,只要超过Region的50%就算大对象,如果一个对象太大,会跨Region存储。
-XX:InitiatingHeapOccupancyPercent,默认是45%,即老年代占据堆内存的45%是时候,进行一次Full GC。
回收过程:
初始标记(STW,速度很快)
并发标记(耗时,不影响系统运行)
最终标记(STW)
混合回收,计算老年代Region存活对象数量,占比,执行垃圾回收预期性能和效率,然后STW系统,根据停顿时间,选择相应Region进行回收,-XX:G1MixedGCCountTarget,默认是8,即会混合回收8次。
-XX:G1HeapWastePercent,默认5%,即混合回收的时候基于复制算法,当空出Region超5%即停止混合回收。
-XX:G1MixedGCLiveThresholdPercent,默认值85%,意思是确定要回收Region的时候,必须是存活对象低于85%的Region才进行回收。
进行Mix的回收,是基于复制算法的,如果没有空闲的Region可以承载存活的对象,就会触发失败,一旦失败,立马会STW,采用单线程标记、清理、压缩空闲出一批Region。
G1什么时候进行新生代gc了,这是个动态过程,根据设置的停顿时间,进行动态的回收。
G1的新生代如何优化? 给足够的内存,同时设置合理的-XX:MaxGCPauseMills参数。
G1的优化是控制好回收时间!!!!!
为什么说G1适合大内存应用?
传统的CMS如果分配大内存的话,只有在内存快满的时候才会触发垃圾回收,可以想象下,此时需要进行大段的连续的内存做碎片整理,需要STW的时间肯定会长。而使用G1,则是一个动态的过程,即停顿200ms,回收下,停顿200ms,回收下,将整个需要STW的时间拆细后,让前端用户感知变弱。