由于之前看的容易忘记,因此特记录下来,以便学习总结与更好理解,该系列博文也是第一次记录,所有有好多不完善之处请见谅与留言指出,如果有幸大家看到该博文,希望报以参考目的看浏览,如有错误之处,谢谢大家指出与留言。
一、常用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次报出内存溢出。将来遇到这个错可以看出,意味着你的函数的调用层次太深。
如果适量的增加栈空间
那么调用的次数也会增加,因为他是一个没有出口的递归函数,所以他必然的会栈溢出。
另一个情况,想让函数多调用几次:那么可以减少局部变量 ,因为在栈争里面主要的就是局部变量表,而局部变量表包含两个部分,一个是参数,一个是这个函数中局部变量,那么如果能够减少局部变量数量,那么就可以函数调用每一次消耗的栈空间,那么也能这样让函数多调用几次。
这里面主要介绍主要的参数,其他还有很多,大家有时间可以去研究。