深入理解JVM-常用的JVM配置及分析

本章章节目录:

  Trace跟踪参数

  内存设置参数

  永久区分配参数

  栈分配参数

 基本配置:

 JDK7环境下面的配置信息 

blob.png

  这章节JVM内存设置参数分析的实例代码: 

public class JvmTest {
    public static void main(String[] args) {
        byte [] bytes=null;
        for (int i = 0; i <10; i++) {
            bytes=new byte[1*1024*1024];
        }
    }
}

Trace跟踪参数:

  -XX:+PrintGC/ -verbose:gc

   表示输出虚拟机中GC的详细情况.

    -XX:+PrintGC 与 -verbose:gc 是一样的,可以认为-verbose:gc 是 -XX:+PrintGC的别名.

   JVM的设置为:

   -Xmx20m -Xms5m -verbose:gc/-XX:+PrintGC

 GC输出结果:

 [GC 3052K->1648K(7168K), 0.0130765 secs]
 [GC 3797K->2704K(10240K), 0.0021147 secs]
 [GC 7999K->3740K(10240K), 0.0018291 secs]
 [Full GC 3740K->1588K(13312K), 0.0171226 secs]

  结果分析:

    GC: 箭头前后的数据3052K和1648K分别表示垃圾收集GC前后所有存活对象使用的内存容量,当年轻代没有足够的的空间时,虚拟机执行一次Minor GC。

    说明有3052K-1648K=1404K的对象容量被回收,括号内的数据7168K为堆内存的总容量,收集所需要的时间是0.0130765秒(这个时间在每次执行的时候会有所不同)Full GC:触发老年代的GC回收,触发的条件:http://book.51cto.com/art/201011/235592.htm

  -XX:+PrintGCDetails

   –打印GC详细信息

  -XX:+PrintGCTimeStamps

   –打印CG发生的时间戳

   JVM的设置为:

-Xmx20m -Xms5m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

  GC输出结果:

0.141: [GC [PSYoungGen: 3052K->504K(3584K)] 3052K->1640K(7168K), 0.0235905 secs] [Times: user=0.05 sys=0.02, real=0.02 secs] 
0.165: [GC [PSYoungGen: 2653K->504K(6656K)] 3789K->2680K(10240K), 0.0015040 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.167: [GC [PSYoungGen: 5799K->504K(6656K)] 7975K->3712K(10240K), 0.0011836 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.168: [Full GC [PSYoungGen: 504K->0K(6656K)] [ParOldGen: 3208K->1588K(6656K)] 3712K->1588K(13312K) [PSPermGen: 2517K->2516K(21504K)], 0.0119618 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
 Heap
 PSYoungGen      total 6656K, used 1085K [0x00000000ff900000, 0x0000000100000000, 0x0000000100000000)
  eden space 6144K, 17% used [0x00000000ff900000,0x00000000ffa0f638,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  ParOldGen       total 6656K, used 1588K [0x00000000fec00000, 0x00000000ff280000, 0x00000000ff900000)
  object space 6656K, 23% used [0x00000000fec00000,0x00000000fed8d280,0x00000000ff280000)
 PSPermGen       total 21504K, used 2523K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
  object space 21504K, 11% used [0x00000000f9a00000,0x00000000f9c76c10,0x00000000faf00000)

 结果分析:

    0.141:时间戳 第一次发生了GC回收,年轻代中回收了将近3M内存,整体占用内存是7M, 用时0.05秒 

   -Xloggc:log/gc.log

    指定GC log的位置,以文件输出来帮助开发人员分析问题

    JVM参数设置: 

  -Xmx20m -Xms20m -Xmn1m -XX:+PrintGCDetails -Xloggc:gc.log -XX:+PrintGCTimeStamps

   -XX:+PrintHeapAtGC:

   每次一次GC后,都打印堆信息

   JVM参数设置:

 -XX:+PrintGCDetails -XX:+PrintHeapAtGC  -XX:+PrintGCDateStamps  -XX:+PrintTenuringDistribution -verbose:gc -Xloggc:gc.log
{Heap before GC invocations=1 (full 0):
 PSYoungGen      total 229376K, used 196608K [0x00000000f0000000, 0x0000000100000000, 0x0000000100000000)
  eden space 196608K, 100% used [0x00000000f0000000,0x00000000fc000000,0x00000000fc000000)
  from space 32768K, 0% used [0x00000000fe000000,0x00000000fe000000,0x0000000100000000)
  to   space 32768K, 0% used [0x00000000fc000000,0x00000000fc000000,0x00000000fe000000)
 ParOldGen       total 262144K, used 0K [0x00000000e0000000, 0x00000000f0000000, 0x00000000f0000000)
  object space 262144K, 0% used [0x00000000e0000000,0x00000000e0000000,0x00000000f0000000)
 PSPermGen       total 98304K, used 12768K [0x00000000da000000, 0x00000000e0000000, 0x00000000e0000000)
  object space 98304K, 12% used [0x00000000da000000,0x00000000dac783d8,0x00000000e0000000)
2016-09-07T13:12:50.021+0800: 5.659: [GC
Desired survivor size 33554432 bytes, new threshold 7 (max 15)
 [PSYoungGen: 196608K->24141K(229376K)] 196608K->24149K(491520K), 0.0282485 secs] [Times: user=0.05 sys=0.00, real=0.03 secs] 
Heap after GC invocations=1 (full 0):
 PSYoungGen      total 229376K, used 24141K [0x00000000f0000000, 0x0000000100000000, 0x0000000100000000)
  eden space 196608K, 0% used [0x00000000f0000000,0x00000000f0000000,0x00000000fc000000)
  from space 32768K, 73% used [0x00000000fc000000,0x00000000fd793710,0x00000000fe000000)
  to   space 32768K, 0% used [0x00000000fe000000,0x00000000fe000000,0x0000000100000000)
 ParOldGen       total 262144K, used 8K [0x00000000e0000000, 0x00000000f0000000, 0x00000000f0000000)
  object space 262144K, 0% used [0x00000000e0000000,0x00000000e0002000,0x00000000f0000000)
 PSPermGen       total 98304K, used 12768K [0x00000000da000000, 0x00000000e0000000, 0x00000000e0000000)
  object space 98304K, 12% used [0x00000000da000000,0x00000000dac783d8,0x00000000e0000000)
}

JVM常用内存设置参数分析:

  Xmx20m -Xms5m  :

  说明:当下Java应用最大可用内存为20M, 最小内存为5M:

public static void main(String[] args) {
        System.out.print("Xmx=");
        System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024
                + "M");
 
        System.out.print("free mem=");
        System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024
                + "M");
 
        System.out.print("total mem=");
        System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024
                + "M");
    }
Xmx=18.0M
free mem=6.019462585449219M
total mem=7.0M

   注:大家可以发现,这里打印出来的Xmx值和设置的值之间是由差异的,total Memory和最大的内存之间还是存在一定差异的,就是说JVM一般会尽量保持内存在一个尽可能底的层面,而非贪婪做法按照最大的内存来进行分配。

   在测试代码中新增如下语句,申请内存分配:

blob.png

  blob.png

  total memory上升了,同时可用的内存也上升了,所以JVM在分配内存过程中是动态的, 按需来分配的。

  -Xmn 设置新生代大小

  -XX:NewRatio= 参数可以设置Young与Old的大小比例,-server时默认为1:2,如果太小,会使大对象直接分配到old区去,增大major collections的执行的次数,影响性能。

  -XX:SurvivorRatio= 参数可以设置Eden与Survivor的比例,默认为1:8,Survivio大了会浪费,如果小了的话,会使一些大对象在做minor gc时,直接从eden区潜逃到old区,让old区的gc频繁。这个参数保持默认就好了,一般情况下,对性能影响不大。

代码示例1:

 JVM的设置为:

 -Xmx20m -Xms20m -Xmn1m  -XX:+PrintGCDetails

  由于内存申请大小为1M, 故年轻代无法满足需求,所以10m的内存分配都放到了年老代。新生代没有使用。没有触发gc

  GC输出结果:

Heap
 PSYoungGen      total 512K, used 0K [0x00000000fff00000, 0x0000000100000000, 0x0000000100000000)
  eden space 0K, -2147483648% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 19456K, used 11258K [0x00000000fec00000, 0x00000000fff00000, 0x00000000fff00000)
  object space 19456K, 57% used [0x00000000fec00000,0x00000000ff6febb0,0x00000000fff00000)
 PSPermGen       total 21504K, used 2521K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
  object space 21504K, 11% used [0x00000000f9a00000,0x00000000f9c765e8,0x00000000faf00000)

 结果分析:

   由于内存申请大小为1M, 故年轻代无法满足需求,所以10m的内存分配都放到了年老代。这里发生了一次GC,一个内存分配失败,在年轻代中。

代码示例2: 

 JVM参数设置:

 -Xmx20m -Xms20m -Xmn15m  -XX:+PrintGCDetails

 GC输出结果:

Heap
 PSYoungGen      total 13824K, used 11525K [0x00000000ff100000, 0x0000000100000000, 0x0000000100000000)
  eden space 12288K, 93% used [0x00000000ff100000,0x00000000ffc41758,0x00000000ffd00000)
  from space 1536K, 0% used [0x00000000ffe80000,0x00000000ffe80000,0x0000000100000000)
  to   space 1536K, 0% used [0x00000000ffd00000,0x00000000ffd00000,0x00000000ffe80000)
 ParOldGen       total 5120K, used 0K [0x00000000fea00000, 0x00000000fef00000, 0x00000000ff100000)
  object space 5120K, 0% used [0x00000000fea00000,0x00000000fea00000,0x00000000fef00000)
 PSPermGen       total 21504K, used 2521K [0x00000000f9800000, 0x00000000fad00000, 0x00000000fea00000)
  object space 21504K, 11% used [0x00000000f9800000,0x00000000f9a765e8,0x00000000fad00000)

 结果分析:

   按15m的新生代设置,全部分配在eden区,老年代没有使用,没有触发gc。

代码示例3:

  JVM参数设置:

-Xmx20m -Xms20m -Xmn6m -XX:+PrintGCDetails

  GC输出结果:

[GC [PSYoungGen: 4130K->498K(5632K)] 4130K->1714K(19968K), 0.0084347 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
  [GC [PSYoungGen: 4658K->498K(5632K)] 5874K->2778K(19968K), 0.0017023 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 Heap
 PSYoungGen      total 5632K, used 3817K [0x00000000ffa00000, 0x0000000100000000, 0x0000000100000000)
  eden space 5120K, 64% used [0x00000000ffa00000,0x00000000ffd3dc90,0x00000000fff00000)
  from space 512K, 97% used [0x00000000fff80000,0x00000000ffffc968,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 14336K, used 2280K [0x00000000fec00000, 0x00000000ffa00000, 0x00000000ffa00000)
  object space 14336K, 15% used [0x00000000fec00000,0x00000000fee3a030,0x00000000ffa00000)
 PSPermGen       total 21504K, used 2524K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
  object space 21504K, 11% used [0x00000000f9a00000,0x00000000f9c77168,0x00000000faf00000)

  结果分析:

    按6m的新生代设置,触发了2次gc,由于from和to的大小小于1M的对象大小,eden区会直接进入老年代。年轻代默认使用复制算法GC,所以需要暂用一半的内存512K.

代码示例4:

  JVM参数设置:

  -Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails

   SurvivorRatio :年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。表示Eden:Survivor=2:2,一个Survivor区占整个年轻代的1/4 大概分配1.7M空间大小。

 GC输出结果:

[GC [PSYoungGen: 3097K->1522K(5632K)] 3097K->1730K(18944K), 0.0207945 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 
 [GC [PSYoungGen: 4648K->536K(5632K)] 4856K->1768K(18944K), 0.0054836 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
 [GC [PSYoungGen: 3807K->1522K(5632K)] 5039K->2770K(18944K), 0.0040313 secs] [Times: user=0.02 sys=0.01, real=0.00 secs] 
Heap
 PSYoungGen      total 5632K, used 3634K [0x00000000ff900000, 0x0000000100000000, 0x0000000100000000)
  eden space 4096K, 51% used [0x00000000ff900000,0x00000000ffb10208,0x00000000ffd00000)
  from space 1536K, 99% used [0x00000000ffd00000,0x00000000ffe7c978,0x00000000ffe80000)
  to   space 1536K, 0% used [0x00000000ffe80000,0x00000000ffe80000,0x0000000100000000)
 ParOldGen       total 13312K, used 1248K [0x00000000fec00000, 0x00000000ff900000, 0x00000000ff900000)
  object space 13312K, 9% used [0x00000000fec00000,0x00000000fed38010,0x00000000ff900000)
 PSPermGen       total 21504K, used 2524K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
  object space 21504K, 11% used [0x00000000f9a00000,0x00000000f9c77168,0x00000000faf00000)

 结果分析:

   from和to的大小可以为1/xmn,大于触发了3次新生代gc,一共回收了7M左右的空间,最后剩余3M在系统当中 ,使用了老年代1M多。

代码示例5:

  JVM参数设置:

 -Xmx20m -Xms20m -XX:NewRatio=1 -XX:SurvivorRatio=3 -XX:+PrintGCDetails

  GC输出结果:

[GC [PSYoungGen: 5161K->1746K(8192K)] 5161K->1746K(18432K), 0.0144682 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 
 [GC [PSYoungGen: 7185K->1746K(8192K)] 7185K->1746K(18432K), 0.0044661 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 8192K, used 2831K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 6144K, 17% used [0x00000000ff600000,0x00000000ff70f638,0x00000000ffc00000)
  from space 2048K, 85% used [0x00000000ffe00000,0x00000000fffb4978,0x0000000100000000)
  to   space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
 ParOldGen       total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000)
 PSPermGen       total 21504K, used 2524K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
  object space 21504K, 11% used [0x00000000f9a00000,0x00000000f9c77168,0x00000000faf00000)

   新生代占一半大小(10m),幸存区为3:1:1(6m:2m,2m),触发了1次gc,回收了4m左右空间,没有使用老年代。对于这种临时对象,减少老年代的使用是gc优化的关键。

 堆溢出分析:

    -XX:+HeapDumpOnOutOfMemoryError  OOM之时导出堆镜像到文件

    -XX:+HeapDumpPath  导出OOM文件的路径设置

    -XX:OnOutOfMemoryError 在OOM时,执行一个脚本

 

代码示例7:

  JVM设置:

 -Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump

public static void main(String[] args) {  
        List<Object> refs = new ArrayList<Object>();  
        for (int i=0; i<25; i++) {  
            refs.add(new byte[1*1024*1024]);  
        }  
     }

blob.png

  Dump文件的位置在D盘的oom.dump,文件大小为18m,一般这类文件的打开需要使用visualvm之类来进行。

blob.png

  打开JDK的安装路径,在bin目录下即可发现visualvm的可程序程序:

blob.png

  示例如下:-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p   在发生OOM之时,发送邮件或者重启应用等动作。

  示例如下:使用JDK自带jvisualvm监控tomcat

永久区分配参数:

   JVM中的内存区域一般分为3个部分: 年轻代、年老代和永久代;永久代在JDK 7中逐渐变化,到JDK 8之后完全消失,合并到了Native堆中

   永久代:主要是JVM在运行过程中,存放Class的静态信息,Main方法信息,常量信息,静态方法和变量信息,共享变量等信息。一般很少被JVM进行回收。一般的动态替换Class的行为都是在这个区域来进行的。

JDK 7下的永久区参数设置

    参数设置示例:

  -XX:PermSize=5M -XX:MaxPermSize=7M

    说明:  PermSize为永久区大小, MaxPermSize为最大的永久区大小

    代码示例:

      JVM参数:

-XX:PermSize=5M -XX:MaxPermSize=7M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\oom.dump
for(int i=0;i<100000;i++){
        CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap());
  }

blob.png

JDK 8中的永久区设置:

   JDK8中已经完全移除了永久带。这项工作是在这个bug:https://bugs.openjdk.java.net/browse/JDK-6964458推动下完成的。JDK8中,PermSize和MaxPermSize参数也一并移除了。

  在移除了Perm区域之后,JDK 8中使用MetaSpace来替代,这些空间都直接在堆上来进行分配。  在JDK8中,类的元数据存放在native堆中,这个空间被叫做:元数据区。JDK8中给元数据区添加了一些新的参数。

   -XX:MetaspaceSize=<NNN> <NNN>是分配给类元数据区(以字节计)的初始大小(初始高水位),超过会导致垃圾收集器卸载类。这个数量是一个估计值。当第一次到达高水位的时候,下一个高水位是由垃圾收集器来管理的。

   -XX:MaxMetaspaceSize=<NNN> <NNN>是分配给类元数据区的最大值(以字节计)。这个参数可以用来限制分配给类元数据区的大小。这个值也是个估计值。默认无上限。

  -XX:MinMetaspaceFreeRatio=<NNN>,<NNN>是一次GC以后,为了避免增加元数据区(高水位)的大小,空闲的类元数据区的容量的最小比例,不够就会导致垃圾回收。

  -XX:MaxMetaspaceFreeRatio=<NNN>,<NNN>是一次GC以后,为了避免减少元数据区(高水位)的大小,空闲的类元数据区的容量的最大比例,超过就会导致垃圾回收。

     默认情况下,类元数据的分配仅受限于可用的本地内存。我们可以使用新的MaxMetaspaceSize参数限定类元数据可用的本地内存的数量。它类似于MaxPermSize。当类元数据区使用量到达MetaspaceSize(32位机客户端模式12M,32位服务器模式16M,64位机会更大)的时候,会触发垃圾回收,然后回收掉无用的类加载器和class对象。      

     MetaspaceSize的值设置的过大会延长垃圾回收时间。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。

栈大小分配:

    在JVM中除了堆heap之外,还有stack栈;栈速度快,空间小,主要用于存放方法级别的临时数据,本文将分析其使用以及注意事项。

   栈主要用以存储方法调用之时,方法体之内的局部变量、参数 操作数栈、动态链接、方法出入口存放在栈上; 在单个线程调用和执行过程中,系统也会为thread分配独立的栈空间;在程序执行过程中,不同的类之间的方法调用,其调用层次,也会被存放到栈之中,所以栈的大小决定了方法调用的深度;最常见的情况是递归的时候,栈溢出的情况。

栈的设置

  参数设置:

 -Xss128k

 代码示例:

public class TestStackDeep {  
    private static int count = 0;  
  
    public static void recursion(long a, long b, long c) {  
        long e = 1, f = 2, g = 3, h = 4, i = 5, k = 6, q = 7, x = 8, y = 9, z = 10;  
        count++;  
        recursion(a, b, c);  
    }  
  
    public static void main(String args[]) {  
        try {  
            recursion(0L, 0L, 0L);  
        } catch (Throwable e) {  
            System.out.println("deep of calling = " + count);  
            e.printStackTrace();  
        }  
    }  
}
java.lang.StackOverflowError

  Stack本身空间小,所以在设置过程中,受限于其空间,不要设置太大。如果先系统的线程数较多,则Stack就要设置小,但是Stack深度就会变小。

 如果想增加迭代次数,则可以进行如下操作:

  尽可能使用占用空间小的数据类型,比如int替代long, float替代double等

  尽可能减少局部变量的使用

  增大-Xss的大小

  避免使用逃逸分析,-XX:-DoEscapeAnalysis. 详细内容可以参考我的blog关于逃逸分析的内容。

发表评论