天天看点

Java虚拟机详解03----常用JVM配置参数

本文主要内容:

trace跟踪参数

堆的分配参数

栈的分配参数

零、在ide的后台打印gc日志:

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

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

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

Java虚拟机详解03----常用JVM配置参数
Java虚拟机详解03----常用JVM配置参数

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

Java虚拟机详解03----常用JVM配置参数

(2)如果你用的是intellij idea,打印gc日志的操作如下:

Java虚拟机详解03----常用JVM配置参数
Java虚拟机详解03----常用JVM配置参数
Java虚拟机详解03----常用JVM配置参数

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

一、trace跟踪参数:

1、打印gc的简要信息:

解释:可以打印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的详细信息:

解释:打印gc详细信息。

解释:打印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日志:

Java虚拟机详解03----常用JVM配置参数

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

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

3、指定gc log的位置:

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

Java虚拟机详解03----常用JVM配置参数

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

例如:

Java虚拟机详解03----常用JVM配置参数

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

解释:监控类的加载。

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

解释:按下ctrl+break后,打印类的信息。

Java虚拟机详解03----常用JVM配置参数

二、堆的分配参数:

1、-xmx –xms:指定最大堆和最小堆

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

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

 运行效果:

Java虚拟机详解03----常用JVM配置参数

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

Java虚拟机详解03----常用JVM配置参数
Java虚拟机详解03----常用JVM配置参数

运行效果:

Java虚拟机详解03----常用JVM配置参数

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

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

Java虚拟机详解03----常用JVM配置参数
Java虚拟机详解03----常用JVM配置参数
Java虚拟机详解03----常用JVM配置参数

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

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

Java虚拟机详解03----常用JVM配置参数
Java虚拟机详解03----常用JVM配置参数
Java虚拟机详解03----常用JVM配置参数

问题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

现在运行如下这段代码:

Java虚拟机详解03----常用JVM配置参数
Java虚拟机详解03----常用JVM配置参数

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

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

Java虚拟机详解03----常用JVM配置参数

总结:

  没有触发gc

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

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

Java虚拟机详解03----常用JVM配置参数

上图显示:

没有触发gc 全部分配在eden(蓝框所示) 老年代没有使用(红框所示)

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

Java虚拟机详解03----常用JVM配置参数

  进行了2次新生代gc

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

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

Java虚拟机详解03----常用JVM配置参数

    进行了至少3次新生代gc

    s0 s1 增大

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

Java虚拟机详解03----常用JVM配置参数

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

Java虚拟机详解03----常用JVM配置参数

3、-xx:+heapdumponoutofmemoryerror、-xx:+heapdumppath

-xx:+heapdumponoutofmemoryerror

    oom时导出堆到文件

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

-xx:+heapdumppath

    导出oom的路径

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

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

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

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

Java虚拟机详解03----常用JVM配置参数

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

Java虚拟机详解03----常用JVM配置参数

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

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

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

或者使用java自带的java visualvm工具也行:

Java虚拟机详解03----常用JVM配置参数
Java虚拟机详解03----常用JVM配置参数

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

4、-xx:onoutofmemoryerror:

-xx:onoutofmemoryerror

    在oom时,执行一个脚本。

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

上方参数的意思是说,执行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。于是,我们运行下面这段代码:

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

Java虚拟机详解03----常用JVM配置参数

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

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

三、栈的分配参数:

1、xss:

设置栈空间的大小。通常只有几百k   决定了函数调用的深度   每个线程都有独立的栈空间   局部变量、参数 分配在栈上

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

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

Java虚拟机详解03----常用JVM配置参数
Java虚拟机详解03----常用JVM配置参数

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

如果设置栈大小为128k:

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

Java虚拟机详解03----常用JVM配置参数

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

Java虚拟机详解03----常用JVM配置参数

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

我们在本文中介绍了jvm的一些最基本的参数,还有很多参数(如gc参数等)将在后续的系列文章中进行介绍。我们将在接下来的文章中介绍gc算法。