zoukankan      html  css  js  c++  java
  • JVM学习八:常用JVM配置参数

    前面学习的都是和类加载相关的知识,接下来学习的则和GC相关的知识,都是JVM的几个重点块。

    零、在IDE的后台打印GC日志:

    既然学习JVM,阅读GC日志是处理Java虚拟机内存问题的基础技能,它只是一些人为确定的规则,没有太多技术含量。

    既然如此,那么在IDE的控制台打印GC日志是必不可少的了。现在就告诉你怎么打印。

    (1)如果你用的是Eclipse,打印GC日志的操作如下:

    d32742cf-b002-4c55-a185-d4ccdc90a69c

    bc5b8afb-9d1f-438b-9225-ee7fbbbe2454

    在上图的箭头处加上-XX:+PrintGCDetails这句话。于是,运行程序后,GC日志就可以打印出来了:

    25d80649-69f0-47b2-a3bb-418ba4457849

    (2)如果你用的是IntelliJ IDEA,打印GC日志的操作如下:

    94726055-e81f-45b8-8978-d1277c5acb17

    f2c896da-404c-4415-98ef-5b582dec3528

    在上图的箭头处加上-XX:+PrintGCDetails这句话。于是,运行程序后,GC日志就可以打印出来了:

    6b1b4352-7172-4404-ac6c-b94c16036d73

    当然了,光有-XX:+PrintGCDetails这一句参数肯定是不够的,下面我们详细介绍一下更多的参数配置。

    一、Trace跟踪参数:

    1、打印GC的简要信息:

    -verbose:gc
    -XX:+printGC

    解释:可以打印GC的简要信息。比如:

    [GC 4790K->374K(15872K), 0.0001606 secs]

    [GC 4790K->374K(15872K), 0.0001474 secs]

    [GC 4790K->374K(15872K), 0.0001563 secs]

    [GC 4790K->374K(15872K), 0.0001682 secs]

    上方日志的意思是说,GC之前,用了4M左右的内存,GC之后,用了374K内存,一共回收了将近4M。内存大小一共是16M左右。

    2、打印GC的详细信息:

    -XX:+PrintGCDetails

    解释:打印GC详细信息。

    -XX:+PrintGCTimeStamps

    解释:打印CG发生的时间戳。

    理解GC日志的含义:

    例如下面这段日志:

    [GC[DefNew: 4416K->0K(4928K), 0.0001897 secs] 4790K->374K(15872K), 0.0002232 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

    上方日志的意思是说:这是一个新生代的GC。方括号内部的“4416K->0K(4928K)”含义是:“GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)”。而在方括号之外的“4790K->374K(15872K)”表示“GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)”。

    再往后看,“0.0001897 secs”表示该内存区域GC所占用的时间,单位是秒。

    再比如下面这段GC日志:

    1fe41f36-cc6b-4a8b-b48e-8cbe2e3a04af

    上图中,我们先看一下用红框标注的“[0x27e80000, 0x28d80000, 0x28d80000)”的含义,它表示新生代在内存当中的位置:第一个参数是申请到的起始位置,第二个参数是申请到的终点位置,第三个参数表示最多能申请到的位置。上图中的例子表示新生代申请到了15M的控件,而这个15M是等于:(eden space的12288K)+(from space的1536K)+(to space的1536K)

    疑问:分配到的新生代有15M,但是可用的只有13824K,为什么会有这个差异呢?等我们在后面的文章中学习到了GC算法之后就明白了。

    3、指定GC log的位置:

    -Xloggc:log/gc.log

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

    805e8e33-1e3b-46c0-af9d-d68f4d38816f

      

    -XX:+PrintHeapAtGC

    解释:每一次GC前和GC后,都打印堆信息。

    例如:

    1c6f3837-4b31-4ac2-a639-e79c92f80df5

    上图中,红框部分正好是一次GC,红框部分的前面是GC之前的日志,红框部分的后面是GC之后的日志。

    -XX:+TraceClassLoading

    解释:监控类的加载。

    例如:

    [Loaded java.lang.Object from shared objects file]

    [Loaded java.io.Serializable from shared objects file]

    [Loaded java.lang.Comparable from shared objects file]

    [Loaded java.lang.CharSequence from shared objects file]

    [Loaded java.lang.String from shared objects file]

    [Loaded java.lang.reflect.GenericDeclaration from shared objects file]

    [Loaded java.lang.reflect.Type from shared objects file]

    -XX:+PrintClassHistogram

    解释:按下Ctrl+Break后,打印类的信息。

    例如:

    c8050739-0029-47cd-95bd-fbbd6289a5d1

    二、堆的分配参数:

    1、-Xmx –Xms:指定最大堆和最小堆

    举例、当参数设置为如下时:

    -Xmx20m -Xms5m

    然后我们在程序中运行如下代码:

    System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");     //系统的最大空间
    System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
    System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间

     运行效果:

    79c1029d-58fe-47d9-aa2e-1c5ee7e741cd

    保持参数不变,在程序中运行如下代码:(分配1M空间给数组)

    复制代码
    复制代码
    byte[] b = new byte[1 * 1024 * 1024];
    System.out.println("分配了1M空间给数组");
    System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
    System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
    System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
    复制代码
    复制代码

    运行效果:

    14d260c9-28bf-4544-a36f-ee14a1d59623

    注:Java会尽可能将total mem的值维持在最小堆。

    保持参数不变,在程序中运行如下代码:(分配10M空间给数组)

    复制代码
    复制代码
    byte[] b = new byte[10 * 1024 * 1024];
    System.out.println("分配了10M空间给数组");
    System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
    System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
    System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间
    复制代码
    复制代码

    运行效果:

    284e8036-8d70-46bc-aac1-99c9b3deb3ef

    如上图红框所示:此时,total mem 为7M时已经不能满足需求了,于是total mem涨成了16.5M。

    保持参数不变,在程序中运行如下代码:(进行一次GC的回收)

    复制代码
    复制代码
    System.gc();
    System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
    System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
    System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间 
    复制代码
    复制代码

    运行效果:

    e419c020-0da3-4046-9b7f-f542ee14a780

    问题1: -Xmx(最大堆空间)和 –Xms(最小堆空间)应该保持一个什么关系,可以让系统的性能尽可能的好呢?

    问题2:如果你要做一个Java的桌面产品,需要绑定JRE,但是JRE又很大,你如何做一下JRE的瘦身呢?

    2、-Xmn、-XX:NewRatio、-XX:SurvivorRatio:

    • -Xmn

        设置新生代大小

    • -XX:NewRatio

        新生代(eden+2*s)和老年代(不包含永久区)的比值

            例如:4,表示新生代:老年代=1:4,即新生代占整个堆的1/5

    • -XX:SurvivorRatio(幸存代)

        设置两个Survivor区和eden的比值

            例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10

    现在运行如下这段代码:

    复制代码
    复制代码
    public class JavaTest {
        public static void main(String[] args) {
            byte[] b = null;
            for (int i = 0; i < 10; i++)
                b = new byte[1 * 1024 * 1024];
        }
    }
    复制代码
    复制代码

    我们通过设置不同的jvm参数,来看一下GC日志的区别。

     

    (1)当参数设置为如下时:(设置新生代为1M,很小)

    -Xmx20m -Xms20m -Xmn1m -XX:+PrintGCDetails 

    运行效果:

    4f0b24b4-cc74-4fd6-af15-b30a784d351b

    总结:

      没有触发GC

        由于新生代的内存比较小,所以全部分配在老年代。

    (2)当参数设置为如下时:(设置新生代为15M,足够大)

    -Xmx20m -Xms20m -Xmn15m -XX:+PrintGCDetails

    运行效果:

    2cb6145f-8c1b-4269-bcfa-31912d2f0d41

    上图显示:

    没有触发GC

    全部分配在eden(蓝框所示)

    老年代没有使用(红框所示)

    (3)当参数设置为如下时:(设置新生代为7M,不大不小)

    -Xmx20m -Xms20m –Xmn7m -XX:+PrintGCDetails

    运行效果:

    0e0cc65d-e291-477a-ba7f-7d433f1085cc

    总结:

      进行了2次新生代GC

      s0 s1 太小,需要老年代担保

    (4)当参数设置为如下时:(设置新生代为7M,不大不小;同时,增加幸存代大小)

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

    运行效果:

    35eb96d6-9251-45e5-8120-05b82210df06

    总结:

        进行了至少3次新生代GC

        s0 s1 增大

    (5)当参数设置为如下时:

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

    运行效果:

    c85f7057-1842-4d11-bc28-fc766e5681f8

    (6)当参数设置为如下时: 和上面的(5)相比,适当减小幸存代大小,这样的话,能够减少GC的次数

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

    fd3322ec-a853-49aa-86fa-81d8b3a02f8c

    3、-XX:+HeapDumpOnOutOfMemoryError、-XX:+HeapDumpPath

    • -XX:+HeapDumpOnOutOfMemoryError

        OOM时导出堆到文件

          根据这个文件,我们可以看到系统dump时发生了什么。

    • -XX:+HeapDumpPath

        导出OOM的路径

    例如我们设置如下的参数:

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

    上方意思是说,现在给堆内存最多分配20M的空间。如果发生了OOM异常,那就把dump信息导出到d:/a.dump文件中。

    然后,我们执行如下代码:

    Vector v = new Vector();
    for (int i = 0; i < 25; i++)
      v.add(new byte[1 * 1024 * 1024]);

    上方代码中,需要利用25M的空间,很显然会发生OOM异常。现在我们运行程序,控制台打印如下:

    3320aba5-2aa6-42bc-b656-57bbc5d8ec41

    现在我们去D盘看一下dump文件:

    8782a0ae-62fb-43a8-a5a6-1c5691e7fa59

    上图显示,一般来说,这个文件的大小和最大堆的大小保持一致。

    我们可以用VisualVM打开这个dump文件。

    注:关于VisualVM的使用,可以参考下面这篇博客:

    使用 VisualVM 进行性能分析及调优:http://www.ibm.com/developerworks/cn/java/j-lo-visualvm/

    或者使用Java自带的Java VisualVM工具也行:

    f9158d50-95d0-4732-942c-e872181fa530

    f69bd0d2-a355-4a93-81c1-c3e71bce7509

    上图中就是dump出来的文件,文件中可以看到,一共有19个byte已经被分配了。 

    4、-XX:OnOutOfMemoryError:

    • -XX:OnOutOfMemoryError

        在OOM时,执行一个脚本。

          可以在OOM时,发送邮件,甚至是重启程序。

    例如我们设置如下的参数:

    -XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p //p代表的是当前进程的pid 

    上方参数的意思是说,执行printstack.bat脚本,而这个脚本做的事情是:D:/tools/jdk1.7_40/bin/jstack -F %1 > D:/a.txt,即当程序OOM时,在D:/a.txt中将会生成线程的dump。

    5、堆的分配参数总结:

    • 根据实际事情调整新生代和幸存代的大小
    • 官方推荐新生代占堆的3/8
    • 幸存代占新生代的1/10
    • 在OOM时,记得Dump出堆,确保可以排查现场问题

    6、永久区分配参数:

    • -XX:PermSize  -XX:MaxPermSize

        设置永久区的初始空间和最大空间。也就是说,jvm启动时,永久区一开始就占用了PermSize大小的空间,如果空间还不够,可以继续扩展,但是不能超过MaxPermSize,否则会OOM。

        他们表示,一个系统可以容纳多少个类型

    代码举例:

    我们知道,使用CGLIB等库的时候,可能会产生大量的类,这些类,有可能撑爆永久区导致OOM。于是,我们运行下面这段代码:

    for(int i=0;i<100000;i++){
      CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap());
    }

    上面这段代码会在永久区不断地产生新的类。于是,运行效果如下:

    fd7bcefb-d6d5-4fe0-8d77-9cddae2733fc

    总结:

      如果堆空间没有用完也抛出了OOM,有可能是永久区导致的

        堆空间实际占用非常少,但是永久区溢出 一样抛出OOM。

    三、栈的分配参数:

    1、Xss:

    设置栈空间的大小。通常只有几百K

      决定了函数调用的深度

      每个线程都有独立的栈空间

      局部变量、参数 分配在栈上

    注:栈空间是每个线程私有的区域。栈里面的主要内容是栈帧,而栈帧存放的是局部变量表,局部变量表的内容是:局部变量、参数。

    我们来看下面这段代码:(没有出口的递归调用)

    复制代码
    复制代码
    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(); } } }
    复制代码
    复制代码

    上方这段代码是没有出口的递归调用,肯定会出现OOM的。

    如果设置栈大小为128k:

    -Xss128K 

    运行效果如下:(方法被调用了294次)

    5c2b2060-e54a-4e7c-9a30-81567204d55b

    如果设置栈大小为256k:(方法被调用748次)

    7d6be7d6-b646-42bf-9357-1a3bccbb7a49

    意味着函数调用的次数太深,像这种递归调用就是个典型的例子。

    参考资料:

    《深入JVM内核原理诊断与优化》视频学习  

     http://www.cnblogs.com/smyhvae 

  • 相关阅读:
    topcoder srm 445 div1
    topcoder srm 440 div1
    topcoder srm 435 div1
    topcoder srm 430 div1
    topcoder srm 400 div1
    topcoder srm 380 div1
    topcoder srm 370 div1
    topcoder srm 425 div1
    WKWebView强大的新特性
    Runtime那些事
  • 原文地址:https://www.cnblogs.com/williamjie/p/9558214.html
Copyright © 2011-2022 走看看