【JVM系列】内存分配与回收策略详解


Minor GC 和 Full GC

  • Minor GC:回收新生代,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。

  • Full GC:回收老年代和新生代,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。

内存分配策略

当我们new一个对象实例时,首先是存入堆中新生代中的伊甸园区,如果伊甸园区空间满了,就会进行YGC,本篇文章就讲述一下对象的分配过程是如何的…

一般过程

第一次轻GC

image-20210615215811323.png

  1. new的对象先放着伊甸园区;
  2. 当伊甸园区空间满了时,程序又要创建新的对象时,JVM垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区的不再被其他对象所引用的对象进行销毁,再加载新的对象到伊甸园区;
  3. 然后将伊甸园区剩余的对象移动到幸存者0区,每个对象被分配了一个年龄计数器(age),每进行一次GC,幸存下来的对象age累加,上面的两个幸存对象age被赋值为1;
  4. GC完成后,此时伊甸园区是空的了。

说明:

  1. S0区和S1区,也被叫做From区和To区,判断二者很简单,谁是空的谁是To,则另外一个就是From区;
  2. 每次GC时,幸存的对象则会被放入To区,以上面的为例,第一次GC时,S0是To区,当GC完成后,S1区是空的就变成了To区;

第二次轻GC

image-20210615221650327.png

  1. 当伊甸园区空间又满了时,进行第二次GC,将伊甸园区幸存的对象放入S1区,age此时为1;

  2. 同时对S0区的对象进行判断是否还被使用,如果被使用的话,就将其放入S1区,age累加此时为2;

  3. 第二次GC完成后,S0区为空了,此时S0区为To区,S1区为From区;

第N次GC

image-20210615223737583.png

  1. 第N次GC时,我们发现S1区的有两个对象的age已经是15了,如果此时这两个对象还是被引用的,则将其晋升到Old区;
  2. 其中15为默认阈值,这个阈值是可以自己设置:-XX:MaxTenuringThreshold=<N>

特殊过程

image-20210615232416390.png

  1. 创建一个新的对象时,首先判断Eden区是否放的下,如果放的下,就为其分配内存,放不下的话,就进行YGC;
  2. 然后再判断Eden区是否放的下,如果此时放的下的话,就为其分配内存,如果还是放不下,说明这个对象比伊甸园区的空间还要大,这个对象是个超大对象,此时将其放入Old区,如果Old区也放不下,就进行FGC,然后再判断Old区能不能放下,放的下就存在Old区,如果还是放不下,就会出现OOM异常;
  3. 在进行YGC的时候,幸存的对象会放入幸存区,此时判断是否能放下,如果幸存区放不下,就会直接放入Old区。

小结

  1. 什么时候进行GC呢?

    • 当伊甸园区满的时候才会进行GC,幸存区满的时候不会进行GC,只有当伊甸园区满的时候,幸存区才会被动进行GC。
  2. 关于幸存者S0和S1区:复制之后有交换,谁空谁是To;

  3. 对于垃圾回收:频繁在新生区进行回收,很少在老年区收集,几乎不在永久区/元空间收集。

Full GC 的触发条件

对于 Minor GC,其触发条件非常简单,当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:

1. 调用 System.gc()

只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

2. 老年代空间不足

老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。

为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。

3. 空间分配担保失败

使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。具体内容请参考上面的第 5 小节。

4. JDK 1.7 及以前的永久代空间不足

在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。

当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么虚拟机会抛出 java.lang.OutOfMemoryError。

为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。

5. Concurrent Mode Failure

执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM内存结构: JVM内存分为如下五个部分: 1. 程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。每个线程都有一个程序计数器,是线程私有的,生命周期与线程相同。 2. Java虚拟机栈 Java虚拟机栈也是线程私有的,生命周期与线程相同。每个方法执行的时候,JVM都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法调用结束后,相应的栈帧也会被销毁。 3. 本地方法栈 本地方法栈也是线程私有的,它与Java虚拟机栈的作用非常相似,只不过它是为虚拟机使用到的Native方法服务。 4. JavaJava堆是JVM所管理的内存中最大的一块,也是所有线程共享的。Java堆是用于存储对象实例的内存区域,几乎所有的对象实例都在这里分配内存Java堆是垃圾收集器管理的重点区域,也被称为GC堆。 5. 方法区 方法区也是线程共享的,用于存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在JDK8之前,永久代(PermGen)是方法区的一部分。在JDK8时,永久代被彻底移除,使用了元空间(Metaspace)来代替。 内存分配策略JVM内存分配策略主要有以下几种: 1. 对象优先在Eden区分配 当JVM需要为新的对象分配内存时,会优先在Eden区进行分配。如果Eden区没有足够的空间,JVM会通过Minor GC回收部分内存空间。 2. 大对象直接进入老年代 如果要分配的对象大小超过了Eden区的一半,JVM会直接将该对象分配到老年代。这样做的目的是为了避免在Eden区内产生大量的垃圾对象,从而降低了Minor GC的频率。 3. 长期存活的对象进入老年代 JVM会为每个对象定义一个年龄计数器,当一个对象在Eden区经历了一次Minor GC后仍然存活,会被移动到Survivor区。在Survivor区中,对象会被继续观察,如果其存活时间达到了一定的阈值,就会被晋升到老年代中。这样做的目的是为了保证长期存活的对象能够在老年代中有足够的空间进行分配。 4. 空间分配担保 每次进行Minor GC时,JVM都会检查老年代的可用空间是否足够,如果足够,就可以安全地将所有存活的对象晋升到老年代中。如果不足,JVM会检查这次Minor GC之前的晋升到老年代的对象的平均大小与老年代的剩余空间的比值,如果比值大于某个阈值(通常为50%),那么这次Minor GC就会中止,JVM会进行Full GC来释放一些空间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值