JVM分代模型--新生代 的垃圾回收

为什么要有JVM分代模型?
JVM中的对象生命周期不同,有的对象长期存在,而有的对象朝生夕死,对象生命周期的不同直接导致了进行垃圾回收时存活对象比例不同,而存活对象比例不同就导致需要使用不同的垃圾回收算法:当对象存活比例高时,应该采用标记-清除、标记-整理算法;当对象存活比例低时,采用复制算法更合理。这也就是为什么JVM要有分代模型的原因。

JVM垃圾回收分代模型中把回收区域分为三块:新生代、老年代、永久代。前面我们说过垃圾回收主要针对的是堆内存和方法区,其实就是把方法区作为了永久代,然后把堆内存划分为了两部分:新生代与老年代。

JVM类加载器会把.class文件加载到方法区(元数据区)中,也就说永久代中,永久代中存储的就是字节码文件生成的类信息、静态变量、常量等;JVM执行引擎在读取字节码跑程序的过程中,会生成一些对象,生成的对象一般都是先放在新生代里去,经过一些机制处理之后依然存活的对象会进入老年代里去。

新生代采用的是什么垃圾回收算法?
一般情况下新创建的对象都是分配在新生代中,但经过前面垃圾产生的分析过程,我们可以知道:所有由线程的栈中的栈帧中的局部变量表中的局部变量引用的对象,在方法执行完之后,引用就会消失,GC Root就遍历不到这样的对象了,所以我们说java中99%的对象都是“朝生夕死”的。

可以说当一个请求打到你的JVM里面,这个请求对应的一个线程会执行很多方法,对应的方法中会创建很多对象,当这些方法执行完之后,创建的这些对象都成为了垃圾,创建的这些对象只是为当前请求服务了一下然后就被抛弃成为垃圾了,而且每分钟会有大量请求打到JVM中,对应的就会产生大量的垃圾,而且随着系统承载的请求量的升高,垃圾产生速度也会升高。

所以我们得出这样的事实:新生代里面的99%的对象存活时间都很短,每次垃圾回收的时候大概只有1%的对象能存活下来。所以新生代采用的垃圾回收算法是:复制算法。正是因为存活的对象比较少,所以需要复制的对象自然就少,所以效率高,而标记-清除、标记-整理都需要对垃圾对象进行清除工作,对于垃圾对象比较多的新生代来说并不适合。

新生代内存是如何划分的?如何提高内存使用率?
但正如我们前面分析的复制算法的工作过程:

 

 

 

复制算法有一个缺点:内存使用率低,因为要把其中一块内存空置起来,作为垃圾回收之后的存活对象复制的目的地,如果不做任何处理,内存使用率就真的只有50%了。

那么如何在使用复制算法的基础之上尽量提高内存使用率呢?

既然只有大概1%的对象会存活,那么可以考虑将两块内存的用途分开,一个专门用于存放创建的新对象,叫做Eden(伊甸园的意思,代表诞生地),一个专门用于存放存活的对象,叫做Survivor(存活地),这样就可以把Eden设置大一点,把Survivor设置小一点:

 

 

 

但还有一个问题,存活后的对象在下次垃圾回收中还需要进行回收,因为有些对象只是存活过了一次垃圾回收,不代表下一次垃圾回收还能存活着,也就说说Survivor也变成了需要回收的地方,把Eden、Survivor都回收处理一遍之后剩下存户的对象放在哪里?

答案就是可以再弄一个Survivor区,这样新生代内就有了三块内存区域:Eden、Survivor1、Survivor2:

 

 

 

然后看一下在采用复制算法的垃圾回收器工作时,这三块内存区域分别起到什么作用:

新创建的对象都分配在Eden区,当Eden区满了之后,垃圾回收器开始工作,把Eden区所有GC Root可到达的对象复制出来,复制到Survivor1区,并清空Eden区:

 

 

 

然后程序继续跑,新创建的对象还是分配在Eden区,当Eden区再次满了之后,垃圾回收器开始工作,回收的目标区域是Eden区和Survivor1区,然后把所有GC Root可到达的对象复制出来,放到Survivor2区中,然后清空Eden区和Survivor1区:

 

 

 

然后程序继续跑,新创建的对象还是分配在Eden区,当Eden区再次满了之后,垃圾回收器开始工作,这次回收的目标区域是Eden区和Survivor2区,同样把所有GC Root可到达的对象复制出来,放到Survivor1区中,然后清空Eden区和Survivor2区。

 

 

 

依次往复……

实际上JVM新生代中的内存划分就是由 Eden区、Survivor1区、Survivor2区组成的,而且通过参数 –XX:SurvivorRatio 可以来设置Eden区与Survivor区的大小比值,默认为8,所以默认情况下新生代内存区域中,Eden区占了80%,两个Survivor区各占了10%。

通过这样的设计,新生代在使用复制算法的垃圾回收器的基础上,将内存使用率由原来的50%提升到了80% !

新生代什么时候会触发垃圾回收?
答案很简单:在新生代内存满了的时候。这时候,要把新创建的对象放入到新生代里去,结果发现内存满了,放不下了,怎么办?新生代进行垃圾回收呗,即Young GC,也可以叫Minor GC。以下我们统一称之为Young GC。

新生代会发生OOM吗?
OOM,Out of Memory,内存溢出。想一想,新生代会发生OOM吗?新生代在满了之后就会触发垃圾回收,而且垃圾回收之后剩余的存活对象一般是放在其中一个Survivor区中,那么如果Survivor区空间不够了的话,新生代会发生OOM吗?答案是:不会的,新生代不会发生OOM。后面我们会看到老年代有一个空间担保机制,当Survivor区空间不够的时候,便可以将这些存活对象放入老年代去,所以新生代把空间不够的问题交给了老年代来处理,OOM也只会发生在老年代。

 

参数设置总结:
-Xmx:决定了堆内存最大大小

-Xms:决定了堆内存初始大小

-Xmn:决定了新生代大小,对应的老年代大小就是:Xmx减去Xmn

–XX:SurvivorRatio:决定了Eden区与Survivor区的比值,默认为8

-Xss:决定了每个线程的栈内存大小

-XX:PermSize: 决定了方法区大小,也就说永久代大小

-XX:MaxPermSize: 决定了方法区最大大小,也就说永久代最大大小

posted @ 2023-02-27 09:50  木木林2022  阅读(98)  评论(0)    收藏  举报