Java 堆从 GC 的角度还可以细分为: 新生代 (Eden 区 、From Survivor 区 和 To Survivor 区 )和老年代。
1. 新生代
是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。
1.1 Eden 区
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。
1.2 ServivorFrom
上一次 GC 的幸存者,作为这一次 GC 的被扫描者。
1.3 ServivorTo
保留了一次 MinorGC 过程中的幸存者。
1.4 MinorGC 的过程(复制->清空->互换)
MinorGC 采用复制算法。
- eden、servicorFrom复制到 ServicorTo,年龄+1
首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区);
- 清空 eden、servicorFrom
然后,清空 Eden 和 ServicorFrom 中的对象;
- ServicorTo 和 ServicorFrom 互换
最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区。
2. 老年代
主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。
MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。
3. 永久代
指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。
3.1. JAVA8 与元数据
在 Java8 中,永久代已经被移除,被一个称为"元数据区"(元空间)的区域所取代。元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制。
4.Minor GC、Major GC、Full GC是什么?
- Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快。(一般采用复制算法回收垃圾)
- Major GC是老年代GC,指的是发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多。(可采用标记清楚法和标记整理法)
- Full GC是清理整个堆空间,包括年轻代和老年代
5. Minor GC、Major GC、Full GC区别及触发条件?
-
Minor GC 触发条件一般为:
(1)eden区满时,触发MinorGC。即申请一个对象时,发现eden区不够用,则触发一次MinorGC。
(2)新创建的对象大小 > Eden所剩空间时触发Minor GC
-
Major GC和Full GC 触发条件一般为:
Major GC通常是跟full GC是等价的
(1)每次晋升到老年代的对象平均大小>老年代剩余空间
(2)MinorGC后存活的对象超过了老年代剩余空间
(3)永久代空间不足
(4)执行System.gc()
(5)CMS GC异常
(6)堆内存分配很大的对象
6.为什么新生代要分Eden和两个 Survivor 区域?
-
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
-
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历15次Minor GC还能在新生代中存活的对象,才会被送到老年代。
-
设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)
7.Java堆老年代( Old ) 和新生代 ( Young ) 的默认比例?
-
默认的新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 --XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。
-
其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,Edem 和俩个Survivor 区域比例是 = 8 : 1 : 1 ( 可以通过参数 --XX:SurvivorRatio 来设定 ),
-
但是JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
jvm相关知识点持续更新中!喜欢的话请点赞、收藏、关注哦!