深入理解Java虚拟机(五)

垃圾收集器与内存分配策略(四)

内存分配和回收策略(一)

        Java中的自动内存管理,分别是给对象分配内存以及回收分配给对象的内存。回收内存,之前已经说了虚拟机的一些垃圾收集器以及运作原理,接下来就是讨论如何给对象分配内存。

        对象的内存分配,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能会直接分配在老年代中,分配规则不是百分百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,以及虚拟机与内存的相关参数配置。(垃圾收集器+内存相关参数配置

1、对象优先在Eden分配

        大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。

/**
 * -verbose:gc 输出虚拟机中GC的详细情况
 * -Xms20M     Java堆最大20M
 * -Xmx20M     Java堆最小20M(说明不可扩展)
 * -Xmn10M     10M分给新生代
 * -XX:+PrintGCDetails
 * -XX:SurvivorRatio=8 新生代Eden区与一个Survivor区的空间比例是8:1
 *
 * @author wcj
 * @date 2019/9/23 14:50
 */
public class MinorGC {
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) {
        byte[] b1, b2, b3, b4;
        b1 = new byte[2 * _1MB];
        b2 = new byte[2 * _1MB];
        b3 = new byte[2 * _1MB];
        //第四次分配时,容量不够了,会触发一个Minor GC
        b4 = new byte[4 * _1MB];
    }
}
输出结果如下:
Heap
 PSYoungGen      total 9216K, used 8154K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 99% used [0x00000007bf600000,0x00000007bfdf6be8,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  to   space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
 ParOldGen       total 10240K, used 4096K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 40% used [0x00000007bec00000,0x00000007bf000010,0x00000007bf600000)
 Metaspace       used 3273K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 360K, capacity 388K, committed 512K, reserved 1048576K

        在上述的代码中,会尝试分配3个2MB大小和一个4MB大小的对象,从输出结果中可以看出“eden space 8192K、from space 1024K、to space 1024K”的信息,新生代总可用空间为9216KB(Eden区+1个Survivor区的总容量)。

        当执行到b4的时候会发生一次Minor GC,因为给b4分配内存时,发现Eden已经被占用了6MB,剩余的空间不足以分配b4的内存,因此发生了Minor GC。GC期间发生已有的b1-b3无法放入Survivor空间(因为剩余的Survivor空间只有1MB大小),所以通过分配担保转移到了老年代。

        在GC结束后,b4顺利的分配到了Eden中,因此只需结果是Eden被b4占用4MB,Survivor空闲,老年代被b1-b3占用6MB。

Minor GC和Full GC的区别        

  • 新生代GC(Minor GC):是指发生在新生代的垃圾收集动作,因为Java对象大多数都是“朝生夕死”,所以Minor非常频繁,一般回收速度也很快
  • 老年代GC(Major GC/Full GC):是指发生在老年代的GC。Major GC的速度一般会比Minor GC慢10倍以上

2、大对象直接进入老年代

        大对象一般是指需要大量连续内存存储的对象,比如很长的字符串或数组。-XX:PretenureSizeThreshold参数,当大于这个设置值的对象直接会在老年代进行分配。其目的是避免Eden区及两个Survivor区之间发生大量的内存复制(新生代采用复制算法收集内存)。

        注意:此参数只适用于Serial和ParNew收集器有效,Parallel Scavenge收集器无法识别此参数,JDK1.8默认使用的是Parallel Scavenge收集器

/**
 * -XX:+UseSerialGC 使用SerialGC垃圾收集器
 * -verbose:gc 输出虚拟机中GC的详细情况
 * -Xms20M     Java堆最大20M
 * -Xmx20M     Java堆最小20M(说明不可扩展)
 * -Xmn10M     10M分给新生代
 * -XX:+PrintGCDetails
 * -XX:SurvivorRatio=8 新生代Eden区与一个Survivor区的空间比例是8:1
 * -XX:PretenureSizeThreshold=3145728 只适用于SerialGC和ParNew收集器
 *
 * @author wcj
 * @date 2019/9/23 16:56
 */
public class BigObjectGC {
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) {
        byte[] b;
        b = new byte[4 * _1MB];
    }
}
输出结果如下:
Heap
 def new generation   total 9216K, used 1845K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  22% used [0x00000007bec00000, 0x00000007bedcd450, 0x00000007bf400000)
  from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
 tenured generation   total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)
 Metaspace       used 3167K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 347K, capacity 388K, committed 512K, reserved 1048576K

        可以看到,“the space 10240K,  40% used”,老年代中的10MB空间被使用了40%,也就是b4被直接分配到老年代中,可知,在SerialGC收集器下,超过3145728KB的对象都会直接在老年代中。

3、长期存活的对象直接进入老年代

        虚拟机采用分代收集思想来管理内存,虚拟机是通过对象年龄(Age)计数器来区分哪些放在新生代,哪些属于老年代。如果对象在Eden创建并经过第一次Minor GC后仍然存活,并且被Survivor容纳,那么就移动到Survivor空间,并且Age=1。该对象每在Survivor中熬过一次Minor GC,age就+1,当age=15时(默认),就会放到老年代中。这个晋升的阈值,可以通过参数-XX:MaxTenuringThreshold设置。

/**
 * -XX:+UseSerialGC 使用SerialGC垃圾收集器
 * -verbose:gc 输出虚拟机中GC的详细情况
 * -Xms20M     Java堆最大20M
 * -Xmx20M     Java堆最小20M(说明不可扩展)
 * -Xmn10M     10M分给新生代
 * -XX:+PrintGCDetails
 * -XX:SurvivorRatio=8 新生代Eden区与一个Survivor区的空间比例是8:1
 * -XX:MaxTenuringThreshold=15 当age=15时,放入老年代
 * -XX:+PrintTenuringDistribution 打印对象年龄的日志
 *
 * @author wcj
 * @date 2019/9/23 16:56
 */
public class LongSurvivalObjectGC {
    //1024字节*1024字节=1MB
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) {
        byte[] b1, b2, b3;
        b1 = new byte[_1MB / 4];
        b2 = new byte[4 * _1MB];
        b3 = new byte[4 * _1MB];
        b3 = null;
        b3 = new byte[4 * _1MB];
    }
}
当XX:MaxTenuringThreshold=1时运行结果如下:
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:     682400 bytes,     682400 total
: 6032K->666K(9216K), 0.0031150 secs] 6032K->4762K(19456K), 0.0031372 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
: 4762K->0K(9216K), 0.0008103 secs] 8858K->4745K(19456K), 0.0008244 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4260K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  52% used [0x00000007bec00000, 0x00000007bf0290f0, 0x00000007bf400000)
  from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
 tenured generation   total 10240K, used 4745K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  46% used [0x00000007bf600000, 0x00000007bfaa2620, 0x00000007bfaa2800, 0x00000007c0000000)
 Metaspace       used 3192K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 354K, capacity 388K, committed 512K, reserved 1048576K

        当方法执行时,b1需要256KB内存,Survivor空间可以容纳。当MaxTenuringThreshold=1时,b1会在第二次GC时进入老年代,新生代变为了0KB。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芦蒿炒香干

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值