一、判断对象是否可以回收
垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收 的,哪些对象是「存活」的,是不可以被回收的;哪些对象已经「死掉」了,需 要被回收。 一般有两种方法来判断:
引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。 当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
二、堆空间分配年轻代老年代及对应回收算法
堆主要用于存放各种类的实例对象和数组。在java中被分为两个区域:年轻代和老年代。
年轻代和老年代的划分是为了更好的内存分派及回收。提高效率。堆是垃圾回收机制的重点区域。我们知道垃圾回收机制有三种,minor gc,major gc 和full gc。针对于堆的就是前两种。年轻代的叫 minor gc,老年代的叫major gc。
1. 年轻代
年轻代中存在的对象是死亡非常快的,存在朝生夕死的情况。尺寸随堆大小的增加和减少而相应的变化,默认值是保持为堆的1/15。所以为了提高年轻代的垃圾回收效率,又将年轻代划分为三个区域: Eden区、SurvivorFrom区、SurvivorTo区。
eden和survivor默认比例是8:1:1,进行垃圾回收采用的是分代复制算法(优点是避免内存碎片)。新创建的对象都会被分配到Eden区(如果该对象占用内存非常大,则直接分配到老年代区),当Eden区内存不够的时候就会触发MinorGC(Survivor满不会引发MinorGC,而是将对象移动到老年代中), 每次新生代的使用,会是eden区和一块survivor区。当我们进行垃圾回收的时候,清除正在使用的区域,将其中的存货对象,放入到另一个survivor区域,并进行整理,保证空间的连续。如果对象长时间存活,则将对象移动到老年区。“From”区和“To”区互换角色,原Survivor To成为下一次GC时的Survivor From区, 总之,GC后,都会保证Survivor To区是空的。存活下来的对象,他的年龄会增长1。当对象的年龄一次次存活,一次次增长,到达15的时候,这些对象就会移步到老年代。在年轻代执行gc的时候,如果老年代的连续空间小于新生代对象的总大小,就会触发一次full gc。是为了给新生代做担保,保证新生代的老年对象可以顺利的进入到老年代的内存区。
2. 老年代
随着Minor GC的持续进行,老年代中对象(年龄大于15的对象)也会持续增长,导致老年代的空间也会不够用,最终会执行Major GC(或full gc)(MajorGC 的速度比 Minor GC 慢很多很多,据说10倍左右),full gc会包含年轻代的gc。但老年代只要执行gc就一定是full gc。full gc使用的算法是:标记清除(回收)算法或标记压缩算法。
标记无用对象,然后进行清除回收。 标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,当进行标记清除时,会停止整个程序(stop the world),它将垃圾收集分为两个阶段:
标记阶段:从根节点开始遍历,标记所有被引用的对象,一般在对象的header中标记为可达对象。
清除阶段:collector对堆内存从头到尾进行线性遍历,如果发现某个对象的header没有标记为可达对象,则回收 。这里的回收是把对象的地址保存在空闲的地址列表中(内存分配),下次对象需要加载时,判断垃圾的位置空间是否够,如果够就存放覆盖原有的地址。
优点:实现简单,不需要对象进行移动。
缺点:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。
3. 永久代(元空间)
在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间,Metaspace)的区域所取代。
值得注意的是:元空间并不在虚拟机中,而是使用本地内存(之前,永久代是在jvm中)。这样,解决了以前永久代的OOM问题,元数据和class对象存在永久代中,容易出现性能问题和内存溢出,毕竟是和老年代共享堆空间。java8后,永久代升级为元空间独立后,也降低了老年代GC的复杂度。
元空间也是对java虚拟机的方法区的一种实现。元空间与永久代最大的区别在于,元空间不在虚拟机中,使用本地内存。通过配置如下参数可以更改元空间的大小。
-XX:MetaspaceSize:初始空间的大小。达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
永久代的回收会随着full gc进行移动,消耗性能。每种类型的垃圾回收都需要特殊处理元数据。将元数据剥离出来,简化了垃圾收集,提高了效率。
三、其他垃圾回收算法
1. 复制算法(年轻代使用)
为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。
优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。
缺点:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。
2. 标记-整理算法(标记压缩算法)
在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎 片。因此就出现了一种标记-整理算法(Mark-Compact)算法,与标记-清除算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。
优点:解决了标记-清理算法存在的内存碎片问题。
缺点:仍需要进行局部对象移动,一定程度上降低了效率。
参考:
JVM年轻代,老年代,永久代详解 - 经典鸡翅 - 博客园 (cnblogs.com)
jvm之年轻代(新生代)、老年代、永久代以及GC原理详解_老年代空间多大-CSDN博客