Minor GC(新生代收集):
触发条件:当Eden区满足时触发,Survivor区满时不会触发。但是当触发时会对Eden和Survivor区进行垃圾回收
每次触发前会进行空间分配担保
空间分配担保:在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次Minor GC可以确保是安全的。如果不成立,则虚拟机会先查看-XX:HandlePromotionFailure参数的设置值是否允许担保失败(Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者-XX:HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次Full GC。
Major GC(老年代收集):
触发条件:当old区满时触发,对老年代(old)进行垃圾回收,每次Major GC前常常伴随着至少一次Minor GC。也就是当老年代空间不足的时候会先触发一次Minor GC,如果之后空间还不足就触发Major GC。目前只有CMS GC会有单独收集老年代的行为
old区满必然是从新生代区复制了数据过来,而新生代晋升老年代有几种情况
1、Eden满了触发Minor GC把满足Age条件的对象晋升。
2、由Endn复制到To区时To区大小不足,把放不下的对象放入老年代
3、大对象直接进入老年代(多大可以通过参数PretenureSizeThreshold设置,默认3M)
4、动态对象年龄判定(From中并非一定要到阈值年龄才晋升,当From中所有相同年龄对象所占大小大于From区大小的一半时,把大于等于这个年龄的对象晋升老年代)
Major GC的速度比Minor GC慢10倍以上,STW更长,应当避免。如果Major GC之后空间还是不足就报OOM
Full GC(整堆收集)
针对区域:新生代、老年代、方法区(永久代/元空间)
触发条件:
1、调用System.gc()
2、老年代空间不足
3、方法区空间不足
4、通过Minor GC进入老年代的平均大小大于老年代剩余可以的空间
5、由Ednd、from向To区复制时(什么时候会复制?Minor GC时),对象大小大于To区的可用空间,则把对象转存到老年代,且对象大小大于老年代剩余可用空间(实际上还是老年代空间不足)
GC日志分析
-Xms60m -Xmx60m -XX:+PrintGCDetails -XX:SurvivorRatio=8
注意:很多时候Major GC和Full GC会混淆使用,注意区分是老年代收集还是整堆收集
垃圾回收会引发STW问题,暂停其他的用户线程,等回收完成恢复用户线程运行
TLAB
出现解决的问题:为了解决对象在堆分配空间存在的线程安全问题,加锁又慢。
-XX:UseTLAB设置是否开启TLAB
怎么解决:为每一个线程分配一个私有的分配空间即TLAB,大小默认1%的Eden大小。该空间只能由该线程去分配内存,但是该空间分配的对象对于线程是共享的
缺点:
1、TLAB空间大小是固定的,且较小,所以存放不下大对象
2、TLAB空间的浪费。TLAB空间还剩一点点没有用到,有点舍不得。
所以JVM设置了最大浪费空间:
当剩余的空间小于最大浪费空间,那该TLAB属于的线程在重新向Eden区申请一个TLAB空间。进行对象创建,还是空间不够,那你这个对象太大了,去Eden区直接创建吧!
当剩余的空间大于最大浪费空间,那这个大对象请你直接去Eden区创建,我TLAB放不下没有使用完的空间
3、Eden空间够的时候,你再次申请TLAB没问题,我不够了,Heap的Eden区要开始GC
4、TLAB允许浪费空间,导致Eden区空间不连续,积少成多。以后还要人帮忙打理。
TLAB的本质其实是三个指针管理的区域:start,top 和 end,每个线程都会从Eden分配一块空间,例如说100KB,作为自己的TLAB,其中 start 和 end 是占位用的,标识出 eden 里被这个 TLAB 所管理的区域,卡住eden里的一块空间不让其它线程来这里分配。