GC:PerNew与CMS的工作原理

101 阅读4分钟

image.png

我们都知道,在jvm堆内存中可以存在新生代和老年代,当创建一个对象并且被引用的时候,这个对象一般会存储在新生代中(通过参数配置可以将对象直接存入老年代)。当新生代中的内存被装满时会触发Young GC,当老年代中的内存被占满时会触发Old GC

由于jvm保姆级的帮我们处理了不被引用的对象回收,所以开发人员基本可以不用在意对回收的处理,只要写好自己的代码就可以了!

不会真的有人不注意GC回收吧! 不会真的有人不注意GC回收吧! 不会真的有人不注意GC回收吧!

image.png

话不多说直接伪代码!


public class Main{
    public static void main(String[] args) {
        One one = new One();
        createItem();
}

    private Item createItem(){
        Item item = new Item();
        item.setName("今天不加班");
        return item;
    }
}

image.png

启动main方法,会经历以下流程:

  • main方法入栈,在堆内存中创建一个One对象并存入新生代中,局部变量one引用堆内存中的One对象。
  • 执行createItem()方法,createItem入栈,创建并赋值Item对象,局部变量item引用Item对象。

当main方法结束,createItem方法会先出栈,伴随着createItem方法的出栈,里面的item局部变量也会被清除并且中断与Item对象的引用,然后main方法出栈,one局部变量也停止对One对象的引用。

那么这个时候堆内存中的Item和One其实已经没有变量对他进行引用了,这两个对象已经是属于垃圾对象需要被gc清除,但gc并不会立马对这两个垃圾对象进行清除,而是等到新生代中的内存满了快装不下时才会触发gc。假设过了5分钟,我们的服务创建的对象已经把新生代分配到的内存占用完了,此时触发一次Minor Gc清理垃圾对象。

清理前:

image.png

清理后:

image.png

清理结束后会将垃圾对象从内存中剔除,从图中可以看到留下来很多的内存碎片

当系统需要创建一个内存地址连续的对象时,因为存在很多的内存碎片,导致无法给出一个完整的能够装下新对象的空间,那么对内存空间造成了很多的浪费,如果新生代内存中存在过多的内存碎片会导致无法创建任何的新对象,最终导致不断的minor gc。

复制算法

如果只在一个内存空间中对垃圾对象进行清理会造成很多的内存碎片,最终影响系统的稳定性。 那么我们将新生代内存空间分成2块,1块用于存储所有的进入新生代的对象的内存空间,另一块用于存储minor gc过后存活的内存对象。

image.png

此时A区存放所有未清理之前的对象,这些对象包含没被引用的垃圾对象也存在仍被引用的存活对象。

image.png

image.png

将A区中的存活对象复制到B区中,那么A区中仅存的对象都是垃圾对象,所以只要对A区中的所有垃圾对象进行统一的清理即可,而minor gc过后系统仍将所有创建的对象存入A区,这样就避免了内存碎片占用资源的问题了。

但这样还是会存在一个问题:B区中的存活对象并不是永久都是被引用的。比如:一个局部变量被加载进了A区,刚好在这个方法没有出栈之前进行了一个minor gc,由于这个对象是被引用的所以把他复制到了B区,过了一段时间之后方法出栈了这个对象也就不被引用了,此时他已经是一个垃圾对象了,那么当下一次minor gc时应该如何进行垃圾回收呢?

image.png

再次触发minor gc,A区中的存活对象和B1区的存活对象都会被复制到B2区中,然后回收掉A区和B1区的垃圾对象。这种复制方式在后续中会循环往复。

在JVM中A区被称为Eden,B1B2称为S0和S1。

标记-清除算法

我们现在知道在新生代中是使用的复制算法解决的gc清理问题,那么老年代是怎么清理的呢(这里使用CMS作为案例)

Stop the World!

PerNew原理

CMS原理

CMS存在缺陷

G1介绍