本文主要内容:html
既然学习JVM,阅读GC日志是处理Java虚拟机内存问题的基础技能,它只是一些人为肯定的规则,没有太多技术含量。java
既然如此,那么在IDE的控制台打印GC日志是必不可少的了。如今就告诉你怎么打印。算法
(1)若是你用的是Eclipse,打印GC日志的操做以下:数组
在上图的箭头处加上-XX:+PrintGCDetails这句话。因而,运行程序后,GC日志就能够打印出来了:jvm
(2)若是你用的是IntelliJ IDEA,打印GC日志的操做以下:函数
在上图的箭头处加上-XX:+PrintGCDetails这句话。因而,运行程序后,GC日志就能够打印出来了:工具
固然了,光有-XX:+PrintGCDetails这一句参数确定是不够的,下面咱们详细介绍一下更多的参数配置。性能
一、打印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左右。
二、打印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日志:
上图中,咱们先看一下用红框标注的“[0x27e80000, 0x28d80000, 0x28d80000)”的含义,它表示新生代在内存当中的位置:第一个参数是申请到的起始位置,第二个参数是申请到的终点位置,第三个参数表示最多能申请到的位置。上图中的例子表示新生代申请到了15M的空间,而这个15M是等于:(eden space的12288K)+(from space的1536K)+(to space的1536K)。
疑问:分配到的新生代有15M,可是可用的只有13824K,为何会有这个差别呢?等咱们在后面的文章中学习到了GC算法以后就明白了。
三、指定GC log的位置:
-Xloggc:log/gc.log
解释:指定GC log的位置,以文件输出。帮助开发人员分析问题。
-XX:+PrintHeapAtGC
解释:每一次GC前和GC后,都打印堆信息。
例如:
上图中,红框部分正好是一次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后,打印类的信息。
例如:
JVM 中最大堆大小有三方面限制:相关操做系统的数据模型(32-bt仍是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位操做系统虽然寻址空间大小是4G(2^32),但具体操做系统会给一个限制(通常Windows系统有2GB内核空间,故用户空间限制在1.5G~2G,Linux系统有1GB内核空间,故用户空间是2G~3G);64为操做系统对内存无限制。全部线程共享数据区大小=新生代大小 + 年老代大小 + 持久代大小。持久代通常固定大小为64m。因此java堆中增大年轻代后,将会减少年老代大小。此值对系统性能影响较大,Sun官方推荐配置为java堆的3/8。
一、-Xmx –Xms:指定java堆最大值(默认值是物理内存的1/4(<1GB))和初始java堆最小值(默认值是物理内存的1/64(<1GB))
默认(MinHeapFreeRatio参数能够调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.,默认(MaxHeapFreeRatio参数能够调整)空余堆内存大于70%时,JVM会减小堆直到 -Xms的最小限制。开发过程当中,一般会将 -Xms 与 -Xmx两个参数的配置相同的值,其目的是为了可以在java垃圾回收机制清理完堆区后不须要从新分隔计算堆区的大小而浪费资源。
注意:此处设置的是Java堆大小,也就是新生代大小 + 年老代大小
举例、当参数设置为以下时:
-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"); //当前可用的总空间
运行效果:
保持参数不变,在程序中运行以下代码:(分配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");
运行效果:
注: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"); //当前可用的总空间
运行效果:
如上图红框所示:此时,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"); //当前可用的总空间
运行效果:
二、-Xmn、-XX:NewRatio、-XX:SurvivorRatio、-XXNewSize、-XX:MaxNewSize:
设置新生代大小,大小是:eden+ 2 survivor space
全部共享数据区大小=年轻代大小 + 年老代大小 + 持久代大小。通常永久代大小固定,因此增大年轻代后,将会减少年老代大小,此值对系统性能影响较大,Sun官方推荐配置为整个java堆的3/8
新生代(eden+2*Survivor)和老年代(不包含永久区)的比值
例如:-XX:NewRatio=4,表示新生代:老年代=1:4,即新生代占整个堆的1/5。在Xms=Xmx而且设置了Xmn的状况下,该参数不须要进行设置。
设置两个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
运行效果:
总结:
没有触发GC
因为新生代的内存比较小,因此所有分配在老年代。
(2)当参数设置为以下时:(设置新生代为15M,足够大)
-Xmx20m -Xms20m -Xmn15m -XX:+PrintGCDetails
运行效果:
上图显示:
没有触发GC
所有分配在eden(蓝框所示)
老年代没有使用(红框所示)
(3)当参数设置为以下时:(设置新生代为7M,不大不小)
-Xmx20m -Xms20m –Xmn7m -XX:+PrintGCDetails
运行效果:
总结:
进行了2次新生代GC
s0 s1 过小,须要老年代担保
(4)当参数设置为以下时:(设置新生代为7M,不大不小;同时,增长幸存代大小)
-Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails
运行效果:
总结:
进行了至少3次新生代GC
s0 s1 增大
(5)当参数设置为以下时:
-Xmx20m -Xms20m -XX:NewRatio=1 -XX:SurvivorRatio=2 -XX:+PrintGCDetails
运行效果:
(6)当参数设置为以下时: 和上面的(5)相比,适当减少幸存代大小,这样的话,可以减小GC的次数
-Xmx20m -Xms20m -XX:NewRatio=1 -XX:SurvivorRatio=3 -XX:+PrintGCDetails
三、-XX:+HeapDumpOnOutOfMemoryError、-XX:+HeapDumpPath
OOM时导出堆到文件
根据这个文件,咱们能够看到系统dump时发生了什么。
导出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异常。如今咱们运行程序,控制台打印以下:
如今咱们去D盘看一下dump文件:
上图显示,通常来讲,这个文件的大小和最大堆的大小保持一致。
咱们能够用VisualVM打开这个dump文件。
注:关于VisualVM的使用,能够参考下面这篇博客:
使用 VisualVM 进行性能分析及调优:http://www.ibm.com/developerworks/cn/java/j-lo-visualvm/
或者使用Java自带的Java VisualVM工具也行:
上图中就是dump出来的文件,文件中能够看到,一共有19个byte已经被分配了。
四、-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。
五、堆的分配参数总结:
六、永久区分配参数:
设置永久区的初始空间(默认为物理内存的1/64)和最大空间(默认为物理内存的1/4)。也就是说,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()); }
上面这段代码会在永久区不断地产生新的类。因而,运行效果以下:
总结:
若是堆空间没有用完也抛出了OOM,有多是永久区致使的。
堆空间实际占用很是少,可是永久区溢出 同样抛出OOM。
一、-Xss:
设置每一个线程栈空间的大小。JDK5.0之后每一个线程堆栈大小为1M,之前每一个线程堆栈大小为256K。在相同物理内存下,减少这个值能生成更多的线程。可是操做系统对一个进程内的线程数仍是有限制的,不能无限生成,经验值在3000~5000左右
决定了函数调用的深度
每一个线程都有独立的栈空间
局部变量、参数 分配在栈上
注:栈空间是每一个线程私有的区域。栈里面的主要内容是栈帧,而栈帧存放的是局部变量表,局部变量表的内容是:局部变量、参数。
咱们来看下面这段代码:(没有出口的递归调用)
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次)
若是设置栈大小为256k:(方法被调用748次)
意味着函数调用的次数太深,像这种递归调用就是个典型的例子。
二、-XXThreadStackSize:
设置线程栈的大小(0 means use default stack size)
一、-XXThreadStackSize:
设置内存页的大小,不可设置过大,会影响Perm的大小
二、-XX:+UseFastAccessorMethods:
设置原始类型的快速优化
三、-XX:+DisableExplicitGC:
设置关闭System.gc()(这个参数须要严格的测试)
四、-XX:MaxTenuringThreshold
设置垃圾最大年龄。若是设置为0的话,则年轻代对象不通过Survivor区,直接进入年老代. 对于年老代比较多的应用,能够提升效率。若是将此值设置为一个较大值,则年轻代对象会在Survivor区进行屡次复制,这样能够增长对象再年轻代的存活时间,增长在年轻代即被回收的几率。该参数只有在串行GC时才有效.
五、-XX:+AggressiveOpts
加快编译
六、-XX:+UseBiasedLocking
锁机制的性能改善
七、-Xnoclassgc
禁用垃圾回收
八、-XX:SoftRefLRUPolicyMSPerMB
设置每兆堆空闲空间中SoftReference的存活时间,默认值是1s 。(softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap)
九、-XX:PretenureSizeThreshold
设置对象超过多大时直接在旧生代分配,默认值是0。
十、-XX:TLABWasteTargetPercent
设置TLAB占eden区的百分比,默认值是1% 。
十一、-XX:+CollectGen0First
设置FullGC时是否先YGC,默认值是false。
原文出处:http://www.cnblogs.com/smyhvae/p/4736162.html
参考文献:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html(含JVM详细参数说明)
http://www.cnblogs.com/edwardlauxh/archive/2010/04/25/1918603.html