【七】内存分配策略

 

JVM参数设置、分析

内存分配策略

  • 优先分配到Eden区
  • 大对象直接分配到老年代
    •   -XX:PretenureSizeThreshold  设定大对象内存大小阈值
    • 一般认为大字符串,数组,为大对象
    • 因为新生代频繁发生垃圾回收,且采用复制算法,若是 频繁复制大对象,影响效率。
  • 长期存活的对象分配到老年代
  • 空间分配担保
  • 动态对象年龄判断

 

 

 

 

-verbose:gc -XX:+PrintGCDetails

 

由如下信息可判断,垃圾收集器新生代肯定使用的是Parellel,老年代可能是GMS

[GC (System.gc()) [PSYoungGen: 693K->464K(6144K)] 693K->472K(19968K), 0.0035897 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 464K->0K(6144K)] [ParOldGen: 8K->329K(13824K)] 472K->329K(19968K), [Metaspace: 2776K->2776K(1056768K)], 0.0042550 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 6144K, used 56K [0x00000007bf980000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 5632K, 1% used [0x00000007bf980000,0x00000007bf98e2b8,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
  to   space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
 ParOldGen       total 13824K, used 329K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000)
  object space 13824K, 2% used [0x00000007bec00000,0x00000007bec52408,0x00000007bf980000)
 Metaspace       used 2782K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 293K, capacity 386K, committed 512K, reserved 1048576K

 

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC

0
[Full GC (System.gc()) [Tenured: 0K->329K(13696K), 0.0027315 secs] 787K->329K(19840K), [Metaspace: 2777K->2777K(1056768K)], 0.0027869 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 6144K, used 55K [0x00000007bec00000, 0x00000007bf2a0000, 0x00000007bf2a0000)  (serial 垃圾收集器的新生代的名称)
  eden space 5504K,   1% used [0x00000007bec00000, 0x00000007bec0dda0, 0x00000007bf160000)
  from space 640K,   0% used [0x00000007bf160000, 0x00000007bf160000, 0x00000007bf200000)
  to   space 640K,   0% used [0x00000007bf200000, 0x00000007bf200000, 0x00000007bf2a0000)
 tenured generation   total 13696K, used 329K [0x00000007bf2a0000, 0x00000007c0000000, 0x00000007c0000000)
   the space 13696K,   2% used [0x00000007bf2a0000, 0x00000007bf2f2408, 0x00000007bf2f2600, 0x00000007c0000000)
 Metaspace       used 2783K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 293K, capacity 386K, committed 512K, reserved 1048576K

 

由此可见jdk1.8默认使用的是Parellel,但是并不是任何环境下都要使用Parellel。应该根据JDK所处的一个环境指定。如果环境是一个服务的server,那么默认指定为Parellel,如果是客户端,收集的内存比较小,停顿时间可观,性能高的情况下,我们一般情况下会使用Serial收集器。

guchunchaodeMacBook-Air:workspaces guchunchao$ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

 

检测内存大于2个G,而且是多核环境,那么默认就认为是Server端。

 

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

 

-Xms  初始堆大小

-Xmx  最大堆大小

-Xmn  年轻代大小

 -XX:SurvivorRatioEden / Survivor 区的大小比值

 

 

 

public class TestHeap {
    public static final int M = 1024 * 1024;
    public static void main(String[] args) {
        for(int i = 0; i < 7; i++) {
            byte[] b = new byte[10 * M];
        }
    }
}

结果:

[GC (Allocation Failure) [DefNew: 859K->308K(9216K), 0.0012388 secs][Tenured: 0K->307K(10240K), 0.0021830 secs] 859K->307K(19456K), [Metaspace: 2647K->2647K(1056768K)], 0.0034872 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [Tenured(老年代): 307K->295K(10240K)(10M), 0.0016707 secs] 307K->295K(19456K), [Metaspace: 2647K->2647K(1056768K)], 0.0017284 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.chaoyijuechen.easypoi.TestHeap.main(TestHeap.java:9)
Heap
 def new generation   total 9216K, used 246K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,   3% used [0x00000007bec00000, 0x00000007bec3d890, 0x00000007bf400000)
  from space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
  to   space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
 tenured generation   total 10240K, used 295K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,   2% used [0x00000007bf600000, 0x00000007bf649eb8, 0x00000007bf64a000, 0x00000007c0000000)
 Metaspace       used 2679K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 289K, capacity 386K, committed 512K, reserved 1048576K

 

最大堆内存大小才20M,新生代10M,老年代10M,而类中运行for体内的的对象为10M被视为大对象(已经等于新生代的大小了,不能可丁可卯)所以扔到老年代而老年代10M也放不下,因而发生了OutOfMemory.若是Eden区内存设置为:13M时,这是新生代就能放下了,每放一个发生一次垃圾回收,要不然没有剩余空间放。

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn13M

[GC (Allocation Failure) [DefNew: 884K->308K(12032K), 0.0028284 secs] 884K->308K(19200K), 0.0031123 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 10548K->306K(12032K), 0.0088254 secs] 10548K->306K(19200K), 0.0088818 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 10762K->306K(12032K), 0.0006648 secs] 10762K->306K(19200K), 0.0007101 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 10546K->306K(12032K), 0.0006589 secs] 10546K->306K(19200K), 0.0006899 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 10546K->306K(12032K), 0.0010204 secs] 10546K->306K(19200K), 0.0010774 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 10546K->306K(12032K), 0.0005397 secs] 10546K->306K(19200K), 0.0005737 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 10546K->306K(12032K), 0.0006790 secs] 10546K->306K(19200K), 0.0007142 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 12032K, used 10762K [0x00000007bec00000, 0x00000007bf900000, 0x00000007bf900000)
  eden space 10752K,  97% used [0x00000007bec00000, 0x00000007bf635db0, 0x00000007bf680000)
  from space 1280K,  23% used [0x00000007bf7c0000, 0x00000007bf80cb30, 0x00000007bf900000)
  to   space 1280K,   0% used [0x00000007bf680000, 0x00000007bf680000, 0x00000007bf7c0000)
 tenured generation   total 7168K, used 0K [0x00000007bf900000, 0x00000007c0000000, 0x00000007c0000000)
   the space 7168K,   0% used [0x00000007bf900000, 0x00000007bf900000, 0x00000007bf900200, 0x00000007c0000000)
 Metaspace       used 2653K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K

针对10M的对象共发生了6次新生代的垃圾回收,最后一次for循环产生的对象留在了Eden区,没有其他对象与其竞争堆空间,所以没有发生第7次GC

 为什么年轻代的值由10 -> 13后就没有发生OutOfMemory呢?因为此时Eden区能放得下了,而10M时新生代放不下,只能当成大对象扔到老年代,老年代也放不下,所以就报了OutOfMemory异常。

 

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

byte[] b = new byte[8 * M];  发生6次GC ,tenured generation(老年代),the space 10240K,  82% used
byte[] b = new byte[9 * M];   发生6次GC ,tenured generation(老年代),the space 10240K,  92% used  (此时一个对象的空间大小以及大于)
byte[] b = new byte[10 * M]; 对象被视为大对象扔到老年代,老年代也放不下,所以报OutOfMemory异常




-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8    Eden / survivor = 8

public class TestHeap {
    public static final int M = 1024 * 1024;
    public static void main(String[] args) {
            byte[] a = new byte[2 * M];
            byte[] b = new byte[2 * M];
            byte[] c = new byte[2 * M];
            byte[] d = new byte[4 * M];
    }
}

结果:

[GC (Allocation Failure) [DefNew: 7003K->308K(9216K), 0.0142125 secs] 7003K->6452K(19456K), 0.0143851 secs] [Times: user=0.01 sys=0.01, real=0.02 secs] 
Heap
 def new generation   total 9216K, used 4487K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  51% used [0x00000007bec00000, 0x00000007bf014930, 0x00000007bf400000)
  from space 1024K,  30% used [0x00000007bf500000, 0x00000007bf54d2d8, 0x00000007bf600000)
  to   space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
 tenured generation   total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000)
 Metaspace       used 2654K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K

 

 

设置Eden区和survivor区的比值,并手动回收老年代:System.gc()

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

public class TestHeap {
    public static final int M = 1024 * 1024;
    public static void main(String[] args) {
            byte[] a = new byte[2 * M];
            byte[] b = new byte[2 * M];
            byte[] c = new byte[2 * M];
            byte[] d = new byte[4 * M];
            System.gc();
    }
}

结果:

[GC (Allocation Failure) [DefNew: 7003K->308K(9216K)新生代内存被回收, 0.0131855 secs] 7003K->6452K(19456K), 0.0133545 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[Full GC (System.gc()) [Tenured: 6144K->6144K(10240K), 0.0021979 secs] 10708K->10546K(19456K), [Metaspace: 2647K->2647K(1056768K)], 0.0022415 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 def new generation   total 9216K, used 4730K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  57% used [0x00000007bec00000, 0x00000007bf09e8e0, 0x00000007bf400000)  大概是4M   ----> 对象d
  from space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)  因为System.gc(),所以这部分被回收掉了,变成了0%
  to   space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
 tenured generation   total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000)  大概是6M  --->  对象a,b,c
 Metaspace       used 2654K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K

 

 

证明:System.gc(); 垃圾回收时,将from space 所指的区域给回收了。

1、[GC (Allocation Failure):新生代
新生代总共有10M,Eden区8M,survivor1:1M,suvivor2:1M. 对象a,b,c(都为2M)被扔到Eden区,8M占了6M,此时Eden还剩2M,当将d(4M)往Eden区扔的时候,发现Eden区不够了,此时VM就自动发生了一次垃圾收集:
[GC (Allocaltion Failure)。这种GC也叫MannerGC ,这是发生在新生代的GC,新生代对象是朝生夕死,存活的不多,是垃圾收集器重点光顾的地区。所以这种GC操作经常发生。特点是相较于Full GC执行时间短。
2、[Full GC (System.gc()) :老年代
  1)、手动发生:System.gc();
  2)、系统自动发生
  3)、调用频率比gc小得多。老年代的内存中的大对象存活几率非常长,所以Full GC 发生的频率不大。
  4)、执行的时间比较长,耗费的性能可能是GC的10倍以上。

   

  

 

 

 

 

 非大对象被扔进Eden区

public class TestHeap {
    
    public static final int M = 1024 * 1024;
    public static void main(String[] args) {
        byte[] b = new byte[10 * M];
    }
}

 

vm参数:

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC   

Heap
 def new generation   total 39296K, used 12337K [0x0000000740000000, 0x0000000742aa0000, 0x000000076aaa0000)
  eden space 34944K,  35% used [0x0000000740000000, 0x0000000740c0c430, 0x0000000742220000)
  from space 4352K,   0% used [0x0000000742220000, 0x0000000742220000, 0x0000000742660000)
  to   space 4352K,   0% used [0x0000000742660000, 0x0000000742660000, 0x0000000742aa0000)
 tenured generation   total 87424K, used 0K [0x000000076aaa0000, 0x0000000770000000, 0x00000007c0000000)
   the space 87424K,   0% used [0x000000076aaa0000, 0x000000076aaa0000, 0x000000076aaa0200, 0x0000000770000000)
 Metaspace       used 2653K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K

10M 大小的对象 并不被视为大对象,只在Eden区,不直接进老年代

 

 设置大对象阈值

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -XX:PretenureSizeThreshold=10M

 

 

Heap
 def new generation   total 39296K, used 2097K [0x0000000740000000, 0x0000000742aa0000, 0x000000076aaa0000)
  eden space 34944K,   6% used [0x0000000740000000, 0x000000074020c420, 0x0000000742220000)
  from space 4352K,   0% used [0x0000000742220000, 0x0000000742220000, 0x0000000742660000)
  to   space 4352K,   0% used [0x0000000742660000, 0x0000000742660000, 0x0000000742aa0000)
 tenured generation   total 87424K, used 10240K [0x000000076aaa0000, 0x0000000770000000, 0x00000007c0000000)
   the space 87424K,  11% used [0x000000076aaa0000, 0x000000076b4a0010, 0x000000076b4a0200, 0x0000000770000000)
 Metaspace       used 2653K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K

 

因为设置了大对象阈值为10M,所以再次跑程序,被扔进了老年代中

 

长期存活的对象分配到老年代

-XX:MaxTenuringThreshold   默认是15

每发生一次GC,对象若是没被清理,对象便从Eden到Suvivor0,再在Survivor0和Survivor1之间来回复制。每发生一次位移,Age就+1。直至Age = 15时依然没有被回收掉,便被扔到老年代。

查看策略:建立一个小对象,手动垃圾回收,看回收多少次后对象被扔到老年代。

(“注意:1.6之前OK,1.7&1.8可能age到 2,3次就被扔到老年代了”)待验证

调用一次System.gc()便被扔到了老年代。 ????? 这个参数有待考究。

 

 

 

 

空间分配担保

-XX:+HandlePromotionFailure

 

逃逸分析与栈上分配

逃逸分析:分析对象的作用阈

如果一个对象被定义在方法体内部后,那么他的受访问权限仅限于方法体内,一旦其引用外部成员后,那么这个对象就发送了逃逸。

如果这个对象仅仅在方法体内部有效,就认为没有逃逸,就可以把这个对象分配到栈上;否则不分配到栈上。

public class TestAllocation {
    
    public TestAllocation obj;
    
    /**方法返回TestAllocation对象,发送逃逸*/
    public TestAllocation getInstance() {
        return obj == null ? new TestAllocation() : obj;
    }

    /**为成员属性赋值,发生逃逸*/
    public void setObj(TestAllocation obj) {
        this.obj = new TestAllocation();
    }
    
    /**对象仅仅在本方法中使用,没有发生逃逸*/
    public void useObject() {
        TestAllocation obj = new TestAllocation();
    }
    
    /**引用成员变量的值,发生逃逸*/
    public void useObject2() {
        TestAllocation obj = getInstance();
    }
    
}

 

posted @ 2019-03-13 14:33  超轶绝尘  阅读(619)  评论(1编辑  收藏  举报