JVM内存分配与回收策略

Java技术体系提倡的自动内存管理最终可以归纳为自动化的解决了两个问题,给对象分配内存和回收分配给对象的内存。

对象的内存分配,往大方向讲,就是在堆上分配,但有可能经过JIT编译后被拆散为标量类型并间接的在栈上分配。对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配,少些也会直接分配在老年代上。

对象优先分配在Eden上

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

Minor GC、Major GC和Full GC之间的区别参考连接
GC分为不同的GC类型,分别对应不同的堆区域
Minor GC、Major GC和Full GC之间的区别
Minor GC和Full GC区别

看看下面代码的内存分配机制

package com.madman.base.jvm;

import org.junit.Test;

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

    /**
     * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     */
    @Test
    public void testAllocation2() {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB];  // 出现一次Minor GC
    }

}

结果,这是在jdk1.7下面的运行效果,要看懂GC日志,需要先去了解怎么看GC日志,参考这个链接。
GC日志分析

[GC [PSYoungGen: 8113K->1016K(9216K)] 8113K->3349K(19456K), 0.0074923 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 9216K, used 5414K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 53% used [0x00000000ff600000,0x00000000ffa4b880,0x00000000ffe00000)
  from space 1024K, 99% used [0x00000000ffe00000,0x00000000ffefe030,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 6429K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 62% used [0x00000000fec00000,0x00000000ff2477b8,0x00000000ff600000)
 PSPermGen       total 21504K, used 4938K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
  object space 21504K, 22% used [0x00000000f9a00000,0x00000000f9ed2890,0x00000000faf00000)

Process finished with exit code 0
大对象直接进入老年代

所谓的大对象就是需要大量连续内存空间的java对象,在开发的时候尽量少的new大对象,因为大部分的大对象都是朝生夕死,经常出现大对象容易出发GC,毕竟内存的容量大小是固定的,可通过设置对象的大小让大对象直接进入老年代,JVM参数-XX:PretenureSizeThreshold,这个参数只对Serial和ParNew这两个收集器生效。

测试代码,看看new的对象分配在哪里了

private static final int _1MB = 1024 * 1024;


    /**
     * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     */
    @Test
    public void testAllocation2() {
        byte[] allocation4;
        allocation4 = new byte[8 * _1MB];  // 出现一次Minor GC
    }

结果

Heap
 PSYoungGen      total 9216K, used 6181K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 75% used [0x00000000ff600000,0x00000000ffc09410,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400010,0x00000000ff600000)
 PSPermGen       total 21504K, used 4938K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
  object space 21504K, 22% used [0x00000000f9a00000,0x00000000f9ed2838,0x00000000faf00000)

Process finished with exit code 0
长期存活的对象将进入老年代

虚拟机采用分代收集思想管理内存,那怎么判断哪些对象放到哪些对应的区域呢,为了做到这一点,虚拟机给每个对象定义一个对象年龄计数器,当对象在Eden区域出生并经过一次Minor GC之后,就将它移动到Survivor区域去,它的年龄也就添加一岁,当它在Survivor中经过15(默认)次之后,就晋升到老年代中去,对象晋升到老年代的年龄阈值可通过参数-XX:MaxTenuringThreshold=15设置。

动态对象年龄判断

为了适应不同程序的内存状况,虚拟机并不是总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
可以尝试运行下面代码进行测试,但是我发现我测试的和<<深入理解java虚拟机>>里面测试的结果不一样,估计跟JDK的版本有些关系,我用的是jdk1.7,最明显的就是收集器的名字都不一样~~~

    private static final int _1MB = 1024 * 1024;

 @Test
    public void testTenuringThreshold2() {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[_1MB / 4];   // allocation1+allocation2大于survivo空间一半
        allocation2 = new byte[_1MB / 4];
        allocation3 = new byte[4 * _1MB];
        allocation4 = new byte[4 * _1MB];
        allocation4 = null;
        allocation4 = new byte[4 * _1MB];
    }

结果

[GC [PSYoungGen: 6808K->1008K(9216K)] 15000K->9982K(19456K), 0.0033865 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 1008K->0K(9216K)] [ParOldGen: 8974K->5829K(10240K)] 9982K->5829K(19456K) [PSPermGen: 4940K->4938K(21504K)], 0.0315234 secs] [Times: user=0.06 sys=0.00, real=0.03 secs] 
Heap
 PSYoungGen      total 9216K, used 4328K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 52% used [0x00000000ff600000,0x00000000ffa3a3d8,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 5829K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 56% used [0x00000000fec00000,0x00000000ff1b14c0,0x00000000ff600000)
 PSPermGen       total 21504K, used 4949K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
  object space 21504K, 23% used [0x00000000f9a00000,0x00000000f9ed5438,0x00000000faf00000)
空间分配担保

在发生Minor GC的时候,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次GC,如果小于,则查看-XX:+HandlePromotionFailure设置是否允许担保失败,如果允许,那只会进行Minor GC,如果不允许则改为一次Full GC。

新生代采用复制收集算法,一般而言打开-XX:+HandlePromotionFailure=true,避免Full GC过于频繁。
代码

private static final int _1MB = 1024 * 1024;

/**
 * VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure
 */
@SuppressWarnings("unused")
public static void testHandlePromotion() {
    byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;
    allocation1 = new byte[2 * _1MB];
    allocation2 = new byte[2 * _1MB];
    allocation3 = new byte[2 * _1MB];
    allocation1 = null;
    allocation4 = new byte[2 * _1MB];
    allocation5 = new byte[2 * _1MB];
    allocation6 = new byte[2 * _1MB];
    allocation4 = null;
    allocation5 = null;
    allocation6 = null;
    allocation7 = new byte[2 * _1MB];
}

GC结果

Java HotSpot(TM) 64-Bit Server VM warning: ignoring option HandlePromotionFailure; support was removed in 6.0_24[GC [PSYoungGen: 6307K->1000K(9216K)] 6307K->1334K(19456K), 0.0066351 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC [PSYoungGen: 7382K->936K(9216K)] 7717K->5366K(19456K), 0.0055151 secs] [Times: user=0.02 sys=0.02, real=0.01 secs] 
[GC [PSYoungGen: 7181K->936K(9216K)] 11612K->5366K(19456K), 0.0018965 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 3051K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 25% used [0x00000000ff600000,0x00000000ff810c30,0x00000000ffe00000)
  from space 1024K, 91% used [0x00000000ffe00000,0x00000000ffeea020,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 4430K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 43% used [0x00000000fec00000,0x00000000ff053ab0,0x00000000ff600000)
 PSPermGen       total 21504K, used 4950K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
  object space 21504K, 23% used [0x00000000f9a00000,0x00000000f9ed5b58,0x00000000faf00000)

Process finished with exit code 0

猜你喜欢

转载自blog.csdn.net/u010316188/article/details/80207776