实战:内存分配与回收策略

 java技术体系的自动内存管理,最根本的目标是自动化地解决两个问题:自动给对象分配内存以及自动回收分配给对象的内存。

1 前置知识

1.1 Minor GC 与Full GC

Minor GC: 新生代GC,是指发生新生代的垃圾收集动作。Minor GC非常频繁,回收速度一般也比较快。

Full GC: 老年代GC,也称Major GC,是指老年代的GC,出现了Major GC经常会伴随至少一次的Minor GC(并发绝对),Major GC的速度一般会比Minor GC的慢10倍以上。

Minor GC

当年轻代空间不足时(这里指当时是Eden区而非Survivor区)

Full GC

  1. 调用System.gc(),系统建议执行Full GC,但非绝对;
  2. 老年代空间不足;
  3. 方法区空间不足;
  4. 通过Minor GC后进入老年代的平均大小大于老年代的可用空间;
  5. 由Eden区,survivor from 向survivor to区复制时,对象大于to可用内存,此时会把该对象转存到老年代,但是老年代可用空间小于该对象大小。

表 Minor GC与Full GC触发条件

图 正常情况下Minnor GC运行过程

上图所述的情况是对象没进入老年代的场景。存在以下情况,对象会进入老年代。

1)大对象直接进入老年代。超过-XX:PretenureSizeThreshold(仅对部份回收器有效,对于Parallel回收器,该参数不生效)设定的大小后会直接进入老年代。

2)长期存活的对象将进入老年代。对象在Survivor区中没熬过一次Minor GC,年龄就增加1岁。当增加到一定程度(默认为15),就会被晋升到老年代中。年龄阈值由-XX:MaxTenuringThreshold设置。

3)动态对象年龄判断。如果在Survivor空间中相同年龄所有对象的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可直接进入老年代。

4)空间分配担保进入。Minor GC后,如果Survivor无法容纳存活下来的对象,那么这些对象会被直接送入老年代(前提是老年代本身还有容纳这些对象的剩余空间)。

1.2 虚拟机及垃圾收集器的日志

在JDK 9以前,HotSpot并没有提供统一的日志处理框架,虚拟机各个功能模块的日志开关分布在不同的参数上。直到JDK 9,HotSpot所有功能的日志都收归到了“-Xlog”参数上。

本文的JDK版本的JDK 8。

-Xms

程序启动时占用内存大小。一般来说,设置值大点,程序会启动得快一点。

-Xmx

程序运行期间最大可占用的内存大小。

-Xss

每个线程的堆栈大小。

-Xmn

新生代大小。

-XX:+PrintGCDetails

打印gc详细信息。

-XX:SurvivorRatio=8

新生代中Eden与两个Survivor的比值。

-XX:MaxTenuringThreshold=15

新生代进入老年代的年龄阈值。

jdk 9之前的常用日志参数

1.2.1 gc详细日志字段说明

图 gc日志截图

图 GC日志参数字段

图 GC日志-Eden空间字段

2 实战:新生代到老年代

使用命令java -XX:+PrintCommandLineFlags -version 查看回收器等信息:

图 当前系统使用的回收器相关信息

如图所示,当前系统使用的回收器是ParallelGC。本节验证的是Serial加Serial Old收集器组合下的内存分配和回收策略。vm配置如下:

-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails  -XX:SurvivorRatio=8

2.1 大对象直接进入老年代

XX:PretenureSizeThreshold参数指定大于等于该设置值的对象直接在老年代分配。在vm配置中加上-XX:PretenureSizeThreshold=3M。

public class BigObject {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] all1,all2,all3,all4,all5,all6,all7;
        all2 = new byte[2 * _1MB];
    }
}

图 BigObject运行结果

如上图所示,对象还未进入老年代。先将all2 = new byte[2*_1MB] 改成 all2 = new byte[3*_1MB]。

图 all2更改大小后的运行结果

大对象已直接进入老年代。

2.2 长期存活的对象将进入老年代

当对象年龄增加到一定程度(-XX:MaxTenuringThreshold参数控制,默认为15),在下次GC时,就会晋升到老年代。在vm配置中加上:

-XX:MaxTenuringThreshold=1 -XX:PretenureSizeThreshold=7M

public class BigObject {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] all1,all2,all3,all4,all5,all6,all7;
        all1 = new byte[_1MB / 4];
        all2 = new byte[_1MB * 6];
        all2 = null;
        all2 = new byte[_1MB * 6];
        all2 = null;
        all2 = new byte[_1MB * 6];
    }
}

图 MaxTenuringThreshold=1两次GC后运行结果

将配置修改为-XX:MaxTenuringThreshold=15

图 MaxTenuringThreshold=15两次GC后运行结果

如图所示,两次GC后,对象依旧进入老年代。这是因为HotSpot虚拟机并不永远要求对象年龄必须达到MaxTenuringThreshold设定的值。如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。

要实现PretenureSizeThreshold的效果,同时不受Survivor的影响,则将代码修改如下:将all1 = new byte[_1MB/4 ] 更改成all1 = new byte[_1MB/10]。(即保证survivor中的对象大小不超过survivor的一半。)

图 MaxTenuringThreshold=15两次GC后运行结果2

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值