深入JVM内核(三)——常用JVM配置参数

由于之前看的容易忘记,因此特记录下来,以便学习总结与更好理解,该系列博文也是第一次记录,所有有好多不完善之处请见谅与留言指出,如果有幸大家看到该博文,希望报以参考目的看浏览,如有错误之处,谢谢大家指出与留言。

一、常用JVM配置参数

1、Trace跟踪参数

2、堆的分配参数

3、栈的分配参数

二、Trace跟踪参数(主要是gc的跟踪)

1、-verbose:gc    打开gc的跟踪日志

2、-XX:+printGC   这个也是打开GC的log开关  

3、可以打印GC的简要信息(在系统程序运行时就可以打印gc的日志信息)

     每一次GC(垃圾回收)都会有相关的信息打出,包块话费的时间

     [GC 4790K->374K(15872K), 0.0001606 secs] 原本堆4790k  gc之后374K   

从log可以货到的信息:在gc之前用了4兆,gc之后用了374K,所以表示回收了将近4兆空间,而整个堆的大小空间将近16兆,同事也可以看到花费的时间

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

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

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

4、-XX:+PrintGCDetails (上面的方式打印的信息方式稍微简单,可以通过这个打印详细信息)

     打印GC详细信息

5、-XX:+PrintGCTimeStamps (打印GC发生的时间戳)

     打印CG发生的时间戳

例 Details 日志信息:

[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] 

6、-XX:+PrintGCDetails的输出  (这个是在PrintGCDetails运行后的一段描述)

    Heap                                12288K+ 1536K                           低边界               当前边界             最高边界

  def new generation   total 13824K, used 11223K [0x27e80000, 0x28d80000, 0x28d80000)    (0x28d80000-0x27e80000)/1024/1024=15M

   eden space 12288K,  91% used [0x27e80000, 0x28975f20, 0x28a80000)  

   from space 1536K,   0% used [0x28a80000, 0x28a80000, 0x28c00000)  

   to   space 1536K,   0% used [0x28c00000, 0x28c00000, 0x28d80000)

   tenured generation   total 5120K, used 0K [0x28d80000, 0x29280000, 0x34680000)    

   the space 5120K,   0% used [0x28d80000, 0x28d80000, 0x28d80200, 0x29280000)  

   compacting perm gen  total 12288K, used 142K [0x34680000, 0x35280000, 0x38680000)  

  the space 12288K,   1% used [0x34680000, 0x346a3a90, 0x346a3c00, 0x35280000)  

  ro space 10240K,  44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000)  

  rw space 12288K,  52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000)

就是在程序结束后描述,他会把程序的整个堆的信息打印出来。

首先他展示的事def new generation新生代的状况, 可看出新生代一共有13兆空间,已经使用11兆;同时可看到eden space(伊甸园)对象出生的地方,可使用的空间是2288K,那么这个空间已经有91% 被使用了;下面的from和to就是两个幸存代,对等的,他们大小一定是相等的。接着打印老年代tenured generation,再往后是持久代compacting ,也就是perm gen永久区和方法区被使用情况,他的使用时142K,相对比较小,是因为在jdk1.5后,在串行gc基础上,加上了共享空间,一些简单的类都会加载在这里。ro和rw是共享区间,供所有jvm使用,一个是只读,一个是可读可写的,他们占得比重比较大,也就是主要的类主要加载在这里面。

进一步介绍:例如:[0x27e80000, 0x28d80000, 0x28d80000) 分别介绍新生代他在内存中的位置。如上面显示,第一个代表低边界,也就是他的起始位置,第二个代表他当前所使用的,被分配的,申请到的位置,最后一个代表他是所能申请到的位置。

  (0x28d80000-0x27e80000)/1024/1024=15M  这个正好15兆,也就是新生代15兆,但他只能用13兆。而 total 13824K也就是12288K+ 1536K 这两块之和。

7、-Xloggc:log/gc.log  (如何来重定向log,因为,他默认是控制台,无法保存,所以通过重定向把他保存起来)

       指定GC log的位置,以文件输出

       帮助开发人员分析问题

8、-XX:+PrintHeapAtGC

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

{Heap before GC invocations=0 (full 0):  def new generation   total 3072K, used 2752K [0x33c80000, 0x33fd0000, 0x33fd0000)   eden space 2752K, 100% used [0x33c80000, 0x33f30000, 0x33f30000)   from space 320K,   0% used [0x33f30000, 0x33f30000, 0x33f80000)   to   space 320K,   0% used [0x33f80000, 0x33f80000, 0x33fd0000)  tenured generation   total 6848K, used 0K [0x33fd0000, 0x34680000, 0x34680000)    the space 6848K,   0% used [0x33fd0000, 0x33fd0000, 0x33fd0200, 0x34680000)  compacting perm gen  total 12288K, used 143K [0x34680000, 0x35280000, 0x38680000)    the space 12288K,   1% used [0x34680000, 0x346a3c58, 0x346a3e00, 0x35280000)     ro space 10240K,  44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000)     rw space 12288K,  52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000)

[GC[DefNew: 2752K->320K(3072K), 0.0014296 secs] 2752K->377K(9920K), 0.0014604 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] (这里是gc,上面是gc之前,下面是GC之后)

Heap after GC invocations=1 (full 0):  def new generation   total 3072K, used 320K [0x33c80000, 0x33fd0000, 0x33fd0000)   eden space 2752K,   0% used [0x33c80000, 0x33c80000, 0x33f30000)   from space 320K, 100% used [0x33f80000, 0x33fd0000, 0x33fd0000)   to   space 320K,   0% used [0x33f30000, 0x33f30000, 0x33f80000)  tenured generation   total 6848K, used 57K [0x33fd0000, 0x34680000, 0x34680000)    the space 6848K,   0% used [0x33fd0000, 0x33fde458, 0x33fde600, 0x34680000)  compacting perm gen  total 12288K, used 143K [0x34680000, 0x35280000, 0x38680000)    the space 12288K,   1% used [0x34680000, 0x346a3c58, 0x346a3e00, 0x35280000)     ro space 10240K,  44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000)     rw space 12288K,  52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000) }

9、-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]

10、-XX:+PrintClassHistogram(用于打印类的使用情况,)

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

                 num     #instances         #bytes  class name ----------------------------------------------    

                1:        890617      470266000  [B    代表byte数组,表示他占用的大小

                2:        890643       21375432  java.util.HashMap$Node      可以看出haspMAP被大量使用,里面保存比较多

                3:        890608       14249728  java.lang.Long    

                4:            13        8389712       [Ljava.util.HashMap$Node;    

                 5:          2062         371680    [C    

                6:           463          41904      java.lang.Class

分别显示:序号、实例数量、总大小、    类型

三、堆的分配参数

1、-Xmx –Xms 指定最大堆和最小堆  说java虚拟机,最大可以使用多少空间  。在系统启动时,系统默认分配最小堆空间

            -Xmx20m -Xms5m  运行代码:

                    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=19.375M

free mem=4.342750549316406M

total mem=4.875M  当前可以用的,被分配的  就是Xms5m指定5兆

2、-Xmx20m -Xms5m  运行代码    这个案例是给数组分配一兆空间大小,结果如下

                  byte[] b=new byte[1*1024*1024];  分配一兆

                  System.out.println("分配了1M空间给数组");

                  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");

分配了1M空间给数组

Xmx=19.375M  最大是不变的

free mem=3.4791183471679688M   可用则减小一兆

total mem=4.875M  总空间不变,他还没有扩展,在java运行当中,Java会尽可能维持在最小堆中运行,你指定了5兆他会想办法在5兆内执行,如果他做了gc之后无法再5兆内去运行,内存不够,那么他才会去扩容。

Java会尽可能维持在最小堆

3、-Xmx20m -Xms5m  运行代码  (来展示扩容案例)

                 b=new byte[4*1024*1024]; System.out.println("分配了4M空间给数组");

                 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");

分配了4M空间给数组

Xmx=19.375M

free mem=3.5899810791015625M

total mem=9.00390625M   总内存变了

4、-Xmx20m -Xms5m  运行代码 (来看看做个GC的情况)

                 System.gc(); System.out.println("回收内存");

                 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=19.375M

free mem=6.354591369628906M   做了gc之后,可用的内存增多。

total mem=10.75390625M    总内存来增多了

-Xmx 和 –Xms 应该保持一个什么关系,可以让系统的性能尽可能的好呢?

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

5、-Xmn  用来设置绝对值

        用来 设置新生代大小

6、-XX:NewRatio   是一个比例  》可以设置百分比

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

       4 表示 新生代:老年代=1:4,即年轻代占堆的1/5

7、-XX:SurvivorRatio  幸存代的比率

       设置两个Survivor区和eden的比

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

看一个例子:

如上设置之后,打印信息如上,如上新生代内存比较少,所以10兆的内存全部分配在老年代,老年分配52%比例,没有处罚GC。

如下,新生代值调整到15兆,如下,新生代大于10兆,所以代码运行的10兆,存入新生代,所以老年代没有使用

在如下案例,分配7兆,发生了gc

现在调整幸存代值,如下

一共处罚,三次gc.一共出发了7兆的回收

老年代,并没有使用,因为增大了from和to,导致幸存代他自己可以处理了,所以没有使用老年代;主要他是分配系统级别的对象的空间,这里使用比较简单的对象。比如线程等等

如下,进一步增大,from和to

gc一次进行了两次,剩余的内存3兆还在新生代。

下面案例: 幸存代用多了是非常浪费的,所以合理的减少幸存代大小。 把幸存代减到了2兆(2048K).,那么eden区扩展了6兆,这是gc次数下降了1次,因为空间得到了更加合理的利用。同样,gc的次数减少,好多对象就没有机会晋升到老年代,所以老年代使用为0.

8、-XX:+HeapDumpOnOutOfMemoryError  (做堆做一个转存,如果系统发生这个outofMe....问题,说明这个系统是不稳定了,那么重现是很困难的,所以在运行时可以使用这个参数,把堆信息导出来。当系统dump后来分析这个文件)

            OOM时导出堆到文件

9、-XX:+HeapDumpPath  (定义这个堆,dump后到那个位置)

           导出OOM的路径

如这个例子:

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

            Vector v=new Vector();    

            for(int i=0;i<25;i++)      

           v.add(new byte[1*1024*1024]);

下面就是dump的文件

如图上图看出,byte塞满了整个空间

11、-XX:OnOutOfMemoryError  (用来指定一个脚本,在OOM之后,可以执行任何脚本)

              在OOM时,执行一个脚本

             "-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p“     

              当程序OOM时,在D:/a.txt中将会生成线程的dump

             printstack.bat这个脚本做的事情是:  D:/tools/jdk1.7_40/bin/jstack -F %1 > D:/a.txt  就是把线程信息打印到D:/a.txt这个文件,同时这个脚本可以任意指定的,所以:

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

四、堆的分配参数 – 总结

      1.根据实际事情调整新生代和幸存代的大小(并没有最优的搭配,根据实际情况)

      2.官方推荐新生代占堆的3/8、幸存代占新生代的1/10

      4.在OOM时,记得Dump出堆,确保可以排查现场问题

五、永久区分配参数

1、-XX:PermSize  、-XX:MaxPermSize 分别 设置永久区的初始空间和最大空间

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

实验:

使用CGLIB等库的时候,可能会产生大量的类,这些类,他就会占用永久代,有可能撑爆永久区导致OOM

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

不断产生新的类,

下面可看到Perm已经满了。

可以看出持久代用了99%,说明产生OOM就是他的内存满了原因。

3、打开堆的Dump  (可以到处上面使用情况)

         堆空间实际占用非常少

         但是永久区溢出 一样抛出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();
		}
	}
}
递归调用
-Xss128K
deep of calling = 701
java.lang.StackOverflowError

-Xss256K
deep of calling = 1817
java.lang.StackOverflowError

如上图可看:分配一些大的栈空间情况,使用递归。

例如:

方式一情况:分配128K,那么在执行第701次报出内存溢出。将来遇到这个错可以看出,意味着你的函数的调用层次太深。

如果适量的增加栈空间

那么调用的次数也会增加,因为他是一个没有出口的递归函数,所以他必然的会栈溢出。

另一个情况,想让函数多调用几次:那么可以减少局部变量 ,因为在栈争里面主要的就是局部变量表,而局部变量表包含两个部分,一个是参数,一个是这个函数中局部变量,那么如果能够减少局部变量数量,那么就可以函数调用每一次消耗的栈空间,那么也能这样让函数多调用几次。

这里面主要介绍主要的参数,其他还有很多,大家有时间可以去研究。

猜你喜欢

转载自blog.csdn.net/gududedabai/article/details/81072075