JVM GC 机制

一、Java堆内存结构
 
JVM将堆分成了二个大区新生代(Young)和老年代(Old),新生代又被进一步划分为Eden(伊甸园空间)和Survivor(幸存者空间)区,而Survivor由Survivor1和Survivor2组成,也有些人喜欢用FromSpace和ToSpace来代替。这里为什么要将Young划分为Eden、Survivor1、Survivor2这三块,给出的解释是  
    “Young中的98%的对象都是死朝生夕死,所以将内存分为一块较大的Eden和两块较小的Survivor1、Survivor2,JVM默认分配是8:1:1,每次调用Eden和其中的Survivor1(FromSpace),当发生回收的时候,将Eden和Survivor1(FromSpace)存活的对象复制到Survivor2(ToSpace),然后直接清理掉Eden和Survivor1的空间。”

1.Young Gen Space 空间三个区的内存比例为 eden : s1 : s2 = 8:1:1
2.其中一个幸存者空间必须保持是空的。如果两个幸存者空间都有数据,或者两个空间都是空的,那一定标志着你的系统出现了某种错误
 
新生代(Young generation): 绝大多数最新被创建的对象会被分配到这里,由于大部分对象在创建后会很快变得不可到达,所以很多对象被创建在新生代,然后消失。对象从这个区域消失的过程我们称之为”minor GC“。
 
老年代(Old generation): 对象没有变得不可达,并且从新生代中存活下来,会被拷贝到这里。其所占用的空间要比新生代多。也正由于其相对较大的空间,发生在老年代上的GC要比新生代少得多。对象从老年代中消失的过程,我们称之为”major GC“(或者”full GC“)
 
永久代(PermanentGeneration),用来保存程序运行时长期存活的对象,如类的元数据。但是Java8中已经用meta space完全替代了永久代。jvm参数-XX:PermSize和-XX:MaxPermSize选项会被忽略。
永久代是在堆内存    之中保存的,但是永久代不会被回收。例如intern()方法,如果永久代中的数据量过大,那么这个程序依然会抛出OutOfMemery异常。
 
(1)对象在内存中移动过程:
1.绝大多数刚刚被创建的对象会存放在伊甸园空间。
2.在伊甸园空间执行了第一次GC之后,存活的对象被移动到其中一个幸存者空间。
3.此后,在伊甸园空间执行GC之后,存活的对象会被堆积在同一个幸存者空间。
4.当一个幸存者空间饱和,还在存活的对象会被移动到另一个幸存者空间。之后会清空已经饱和的那个幸存者空间。
5.在以上的步骤中重复几次依然存活的对象,就会被移动到老年代。
 
(2)对象分配规则:
  • 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
  • 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
  • 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
  • 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
  • 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。 
 
 
二、Java 垃圾回收流程
 
 
GC流程:
  1. 当我们new一个对象的时候,首先会在Eden空间申请一块内存空间;
  2. 如果Eden空间不足,则会执行一次MinorGC;在执行MinorGC的过程中,Eden区中存活的对象和其中一块Survivor存活的对象通过复制算法拷贝到另外一块空白的Survivor区中。并且在对象转移的过程中,某些对象到达一定的年龄(这个年龄是动态判断的,最大为15)就会进入到老年代;
  3. 如果这个时候空间还不充足,则会执行一次MajorGC,回收Old区;在执行MajorGC的过程是比较耗时的,并且会至少触发一次MinorGC(要腾出Eden区空间);
  4. 如果这个时候空间还不充足,则会执行一次FullGC(包含新生代、年老代、永久化的GC);
  5. 如果这个时候空间还不充足,则会抛出OOM异常。
 
GC 类型:
  • Minor GC 针对新生代的 GC,Eden区满了之后会触发,Servivor区满了不会触发。
  • Major GC 针对老年代的 GC
  • Full GC 针对新生代、老年代、永久带的 GC。这个从GC打印日志也可以看出来
 
 
MinorGC
年轻代是所有新对象产生的地方。当年轻代内存空间被用完时,就会触发垃圾回收。这个垃圾回收叫做MinorGC。年轻代被分为3个部分——Enden区和两个Survivor区。
新对象在年轻代产生,当年轻代空间占满后,会触发MinorGC开始垃圾回收,大多数新建对象位于Eden区,Eden区满后触发一次MinorGC,存活下来的对象转到survivor0区,survivor0区满后触发执行MinorGC,survivor0区存活对象全部转入survivor1区,这样保证一段时间内总有一个survivor区为空。经过多次MinorGC仍然存活的对象会转入老年代,这是由设定的年龄阈值来决定的。
 
MajorGC
老年代空间存储长期存活的对象,老年代占满时会触发MajorGC,花费时间较长,由于垃圾回收会导致“stoptheworld”事件,即所有线程都会停下来等待垃圾回收执行完成,所以对响应要求高的应用应尽量减少发生MajorGC,如微博后台程序发生MajorGC就会导致前台页面刷新超时,若是股票等实时交易应用发生MajorGC导致响应超时,可能会给客户带来损失,这都是系统调优要考虑到的问题。
在分代收集算法中,由于年轻代中对象的存活率低,通常使用复制算法进行垃圾回收,老年代一般使用”标记-清理”或”标记-整理”算法回收存活率较高的对象。
 
Minor和Full GC有什么不一样?
  • 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕死的特性,所以Minor GC非常频繁,一般回收速度也比较快。
  • 老年代GC(Major GC/Full GC):指发生在老年代的GC,出现Major GC,经常会伴随至少一次的Minor GC(但并非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
 
 
JDK1.8后与JDK1.8之前
  • Jdk1.6及之前:常量池分配在永久代 。
  • Jdk1.7:有,但已经逐步“去永久代” 。
  • Jdk1.8及之后:无(java.lang.OutOfMemoryError: PermGen space,这种错误将不会出现在JDK1.8中)。元空间使用的是直接内存。jdk1.8取消永久代的目的是为了将hotspot与jrockit两个虚拟机的标准联合为一个。
 

 

posted @ 2020-06-16 11:13  cao_xiaobo  阅读(778)  评论(0编辑  收藏  举报