JVM 内存分配和回收策略

2 篇文章 0 订阅

JVM 内存分配和回收策略

1. 前言

对象的内存分配往大方向讲,就是在堆上分配内存。对象主要分配在新生代Eden上,如果启动了本地线程分配缓冲,将按线程优先分配在TLAB上。少数情况也可能直接分配在老年代上,分配规则不是百分百的,主要看使用哪种垃圾收集器,以及虚拟机中内存相关参数的设置。

对于JVM内存区域不了解的同学可以看这里Java内存区域分配和GC

之后我们通过代码验证下这些规则,我安装的是 Server 模式虚拟机,没有指定收集器组合,默认是在PS(Parallel Scavenge + Serial Old)收集器下的内存分配和回收策略。java -version可以看到虚拟机的模式 64-Bit Server

java -version
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)

对于查看GC日志不了解的同学可以看这里看懂GC日志

2. 内存优先在Eden分配

大多数情况下,对象在新生代的Eden区中进行分配。当Eden区没有足够的空间进行分配时,虚拟机将发生一次Minor GC,虚拟机提供了-XX:+printGCDetails这个收集器日志参数,可以在垃圾收集时打印内存回收日志,并且在进程退出时输出当前的内存各区域的分配情况。在实际应用中,内存回收日志一般是打印到文件后通过日志工具进行分析。

新生代GC(Minor GC)指发生在新生代的垃圾收集工作,因为Java对象大多具备朝生夕灭特性,所以Minor GC 非常频繁,一般回收速度也较快。

老年代GC (Major GC/Full GC)指发生在老年代的GC,出现了Major GC,至少会伴随至少一次的 Minor GC(但非绝对的,在PS收集器的收集策略里,就有直接进行 Major GC策略的选择过程),Major GC 的速度一般比 Minor GC 慢10倍以上。

设置JVM参数 -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

//-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
//-Xms20M -Xmx20M Java 堆 20M不可扩展
//-Xmn10M 10M给新生代,剩下10M给老年代。
// -XX:SurvivorRatio=8 表示新生代中 Eden 区和一个Survivor 区的空间比例是 8:1,新生代只使用Eden
// 和一块Suvivor From 区域
public class GCTest {
  
    public static void main(String[] args) throws InterruptedException {
        System.out.println("hello world");
    }
  
}

//输出 PSYoungGen 年轻代,总大小9216(eden+from),使用了3248主要是Eden,老年代总大小10240 使用0
// Metaspace 元数据替换以前的永久代
hello world
Heap
 PSYoungGen      total 9216K, used 3248K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 39% used [0x00000007bf600000,0x00000007bf92c2e8,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  to   space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
 ParOldGen       total 10240K, used 0K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 0% used [0x00000007bec00000,0x00000007bec00000,0x00000007bf600000)
 Metaspace       used 2966K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 312K, capacity 392K, committed 512K, reserved 1048576K
//-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
public class GCTest {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[3 * _1MB];
        allocation2 = new byte[3 * _1MB];
        allocation3 = new byte[3 * _1MB];  //Minor GC 年轻代GC
        allocation4 = new byte[4 * _1MB];  //大对象直接分配到老年代了不GC
    }
}
//输出 代码 allocation3 = new byte[3 * _1MB]; 发生GC,6156K,无法再分配3MB的内存空间,而From 区
//没有3MB的空间,只好通过分配担保机制提前分配到老年代。而allocation4 = new byte[4 * _1MB];创建大对象,则直接放入老年代存储(这个不是绝对的,还是看收集器收集规则,可以多试试)。所以最后老年代是3MB+4MB
[GC (Allocation Failure) [PSYoungGen: 6156K->807K(9216K)] 6156K->3887K(19456K), 0.0055147 secs] [Times: user=0.02 sys=0.01, real=0.01 secs] 
Heap
 PSYoungGen      total 9216K, used 7354K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 79% used [0x00000007bf600000,0x00000007bfc64d48,0x00000007bfe00000)
  from space 1024K, 78% used [0x00000007bfe00000,0x00000007bfec9ca0,0x00000007bff00000)
  to   space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
 ParOldGen       total 10240K, used 7176K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 70% used [0x00000007bec00000,0x00000007bf302020,0x00000007bf600000)
 Metaspace       used 2965K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 312K, capacity 392K, committed 512K, reserved 1048576K

3. 大对象直接分配在老年代

所指的大对象是需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组,byte[]就是典型大对象,大对象对虚拟机的内存分配来说是一个坏消息(程序中尽量避免),经常出现大对象会容易导致内存还有不少空间时就提前出发垃圾收集以获取足够的连续空间来安置他们。虚拟机提供一个 -XX:PretenureSizeThreshold 参数,令大对象直接在老年代分配,这样做的目的是避免在Eden区和两个Suvivor区之间发生大量的内存复制(新生代用复制算法收集内存)。

//-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 
//-XX:PretenureSizeThreshold=5242880  设置5MB以上对象进老年代
public class GCTest {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[4 * _1MB]; //进eden空间
        allocation2 = new byte[5 * _1MB]; //直接进老年代
    }
}
//输出
Heap
 PSYoungGen      total 9216K, used 7347K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 89% used [0x00000007bf600000,0x00000007bfd2cd28,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  to   space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
 ParOldGen       total 10240K, used 5120K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 50% used [0x00000007bec00000,0x00000007bf100010,0x00000007bf600000)
 Metaspace       used 2965K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 312K, capacity 392K, committed 512K, reserved 1048576K

3. 长期存活对象进入老年代

虚拟机使用分代回收思想管理内存,内存回收就必须能识别哪些对象应该放在新生代,哪些放在老年代,为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden产生并且经过一次Minor GC仍然存活,并且能被Survivor 容纳,则被移动到Survivor空间中,对象年龄1。每熬过一次 Minor GC,年龄增加1岁,当他的年龄增加到一定程度(默认15岁),就会晋升到老年代中。年龄阈值可以通过参数-XX:MaxTenuringThreshold设置。

//-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 
//-XX:MaxTenuringThreshold=1 熬过一次回收进入老年代
//-XX:PretenureSizeThreshold=5242880 5MB对象直接进入老年代
//-XX:+UseSerialGC 虚拟机运行在 Client 模式下的默认值,打开此开关后,使用Serial + Serial Old 收集器组合进行内存回收 在此环境测试

public class GCTest {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
        byte[] allocation1, allocation2, allocation3, allocation4,allocation5;
        allocation1 = new byte[_1MB / 4];
        allocation3 = new byte[4 * _1MB];
        allocation3 = null;
        allocation3 = new byte[4 * _1MB];
        allocation3 = null;
        allocation3 = new byte[4 * _1MB];
    }
}

//输出 GC2次后,allocation1 进入老年代
[GC (Allocation Failure) [DefNew: 7439K->929K(9216K), 0.0033421 secs] 7439K->929K(19456K), 0.0034009 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 5189K->0K(9216K), 0.0022454 secs] 5189K->750K(19456K), 0.0022992 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 def new generation   total 9216K, used 4178K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  51% used [0x00000007bec00000, 0x00000007bf014930, 0x00000007bf400000)
  from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
 tenured generation   total 10240K, used 750K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,   7% used [0x00000007bf600000, 0x00000007bf6bb998, 0x00000007bf6bba00, 0x00000007c0000000)
 Metaspace       used 2966K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 312K, capacity 392K, committed 512K, reserved 1048576K

4. 动态年龄对象判定

为了更好适应不同程序的内存情况,虚拟机不是永远要求年龄达到阈值才进入老年代的,如果Survivor区中相同年龄的所有对象大小总和大于Survivor的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到阈值要求的年龄。有兴趣的同学可以自己写代码试试

5. 空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用连续空间是否大于新生代所有对象空间总和,如果条件成立,那么Minor GC是安全的,如果不成立,则虚拟机会查看HandlePromotionFailure是否允许担保失败,如果允许,那么会持续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行Minor GC,尽管这次GC是可能有风险的,如果小于,或者不允许担保失败,那这次改为Full GC。大多数情况下会把HandlePromotionFailure 担保打开。

参考《深入理解Java虚拟机》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值