JVM系列扩展:Java虚拟机日志分析
2016-10-15 20:34
225 查看
堆配置
堆大小设置
当Java进程启动时,虚拟机就会分配一块初始堆空间,可以使用-Xms指定这块空间的初始大小。如果初始堆耗尽,虚拟机就会对堆进行扩展(如果可能的话),最大堆空间可以使用参数
-Xmx指定。
public class HeapAlloc{ public static void main(String []args){ System.out.println("maxMemory="+Runtime.getRuntime().maxMemory()+"bytes"); //最大可用内存 System.out.println("fee mem="+Runtime.getRuntime().freeMemory()+"bytes");//当前空闲内存 System.out.println("total mem="+Runtime.getRuntime().totalMemory()+"bytes");//当前总内存 byte[] b=new byte[1*1024*1024]; System.out.println("分配了1M空间给数组"); System.out.println("maxMemory="+Runtime.getRuntime().maxMemory()+"bytes"); System.out.println("fee mem="+Runtime.getRuntime().freeMemory()+"bytes"); System.out.println("total mem="+Runtime.getRuntime().totalMemory()+"bytes"); b=new byte[4*1024*1024]; System.out.println("分配了4M空间给数组"); System.out.println("maxMemory="+Runtime.getRuntime().maxMemory()+"bytes"); System.out.println("fee mem="+Runtime.getRuntime().freeMemory()+"bytes"); System.out.println("total mem="+Runtime.getRuntime().totalMemory()+"bytes"); } }
现在使用
java -Xmx20m -Xms5m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC HeapAlloc来运行,看看日志输出。
其中
-Xmx20设置最大堆空间为20M,
-Xms5m设置初始堆为5M,
-XX:+PrintCommandLineFlags表示输出Java虚拟机启动的参数,
+PrintGCDetails,打印详细的日志,
-XX:+UseSerialGC表示使用串行垃圾回收器。
output:
-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC maxMemory=20316160bytes //20971520(20M)-655360 fee mem=5550672bytes //5.29M total mem=6094848bytes //5.8M 分配了1M空间给数组 maxMemory=20316160bytes fee mem=4502080bytes //4.29M total mem=6094848bytes //5.8M [GC (Allocation Failure) [DefNew: 1555K->192K(1856K), 0.0015332 secs][Tenured: 1107K->1298K(4096K), 0.0014706 secs] 1555K->1298K(5952K), [Metaspace: 2598K->2598K(1056768K)], 0.0030620 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 分配了4M空间给数组 maxMemory=20316160bytes fee mem=4800984bytes //4.5M total mem=10358784bytes //9.8M Heap def new generation total 1920K, used 51K [0x00000007bec00000, 0x00000007bee10000, 0x00000007bf2a0000) eden space 1728K, 2% used [0x00000007bec00000, 0x00000007bec0cc60, 0x00000007bedb0000) from space 192K, 0% used [0x00000007bedb0000, 0x00000007bedb0000, 0x00000007bede0000) to space 192K, 0% used [0x00000007bede0000, 0x00000007bede0000, 0x00000007bee10000) tenured generation total 8196K, used 5394K [0x00000007bf2a0000, 0x00000007bfaa1000, 0x00000007c0000000) the space 8196K, 65% used [0x00000007bf2a0000, 0x00000007bf7e4858, 0x00000007bf7e4a00, 0x00000007bfaa1000) Metaspace used 2605K, capacity 4486K, committed 4864K, reserved 1056768K class space used 284K, capacity 386K, committed 512K, reserved 1048576K
首先,第一行是
-XX:+PrintCommandLineFlags命令的效果。
其次,根据程序的输出可以看出,运行时的JVM堆的最大
可用内存并不完全等于启动时设置的最大值20M,而是比20M少那么一点,原因之一是在新生代中,存在
from/to两个分区,这两个分区事实上只有一个是属于
可用的;还有一个原因是虚拟机内部并没有直接使用新生代的
from/to的大小,而是对它们做了一个对齐操作,需要占用一些堆空间。在这里,对齐操作的空间大小为655360字节。而对于
当前空闲内存和
当前总内存也是一样,都和启动时设置的参数有点差距。
在执行完分配1M数组(分配在新生代)后,可以发现,
当前空闲内存减少了1M。
[GC (Allocation Failure) [DefNew: 1555K->192K(1856K), 0.0015332 secs][Tenured: 1107K->1298K(4096K), 0.0014706 secs] 1555K->1298K(5952K), [Metaspace: 2598K->2598K(1056768K)], 0.0030620 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
当虚拟机准备分配一个4M的空间给数组时,堆的最大可用内存只有4.29M,划分下eden,to,from区,自然就无法存放一个4M的数组了。因此堆空间进行扩展。随后触发Full GC,新生代内存
1555K->192K减少,之前的1M数组对象被移到老年代
1107K->1298K。
1555K->1298K(5952K)显示的是当前整个堆大小从1555K变成1298K,其中的
1555K也就是分配完1M数组后
total mem-fee mem=(6094848-4502080)/1024=1555K
可以从最后的Heap信息看出,新生代可用内存为1920K,使用51K。老年代可用内存为8196K,使用5394K(1M,4M数组都存放在这里)。新生代和老年代可用内存总和正好为
total mem=10358784bytes。
新生代设置
Java虚拟机提供了参数-Xmn来设置新生代的大小。而参数
-XX:SurvivorRatio则可用来设置新生代中
eden/from(to)的比例关系。下面看个例子
public class NewSizeDemo{ public static void main(String[] args) { byte b[]=null; for (int i=0; i<10; i++) { b=new byte[1*1024*1024]; } } }
使用
java -Xmx20m -Xms20m -Xmn2m -XX:SurvivorRatio=2 -XX:+PrintGCDetails NewSizeDemo来执行,结果如下
Heap PSYoungGen total 1536K, used 497K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000) eden space 1024K, 48% used [0x00000007bfe00000,0x00000007bfe7c440,0x00000007bff00000) from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000) ParOldGen total 18432K, used 10240K [0x00000007bec00000, 0x00000007bfe00000, 0x00000007bfe00000) object space 18432K, 55% used [0x00000007bec00000,0x00000007bf6000a0,0x00000007bfe00000) Metaspace used 2599K, capacity 4486K, committed 4864K, reserved 1056768K class space used 284K, capacity 386K, committed 512K, reserved 1048576K
尽管新生代的
eden区正好可以存放一个1M的数组,但是从打印的日志可以看出,数组全部存放在了老年代。
使用
java -Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC NewSizeDemo命令,区别在于扩大了新生代的可用内存大小。
结果如下:
[GC (Allocation Failure) [DefNew: 2582K->1298K(5376K), 0.0015075 secs] 2582K->1298K(18688K), 0.0015396 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 4439K->1024K(5376K), 0.0011856 secs] 4439K->1296K(18688K), 0.0012097 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 4158K->1024K(5376K), 0.0007845 secs] 4430K->1296K(18688K), 0.0008061 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 5376K, used 3209K [0x00000007bec00000, 0x00000007bf300000, 0x00000007bf300000) eden space 3584K, 60% used [0x00000007bec00000, 0x00000007bee22420, 0x00000007bef80000) from space 1792K, 57% used [0x00000007bf140000, 0x00000007bf240010, 0x00000007bf300000) to space 1792K, 0% used [0x00000007bef80000, 0x00000007bef80000, 0x00000007bf140000) tenured generation total 13312K, used 272K [0x00000007bf300000, 0x00000007c0000000, 0x00000007c0000000) the space 13312K, 2% used [0x00000007bf300000, 0x00000007bf344178, 0x00000007bf344200, 0x00000007c0000000) Metaspace used 2600K, capacity 4486K, committed 4864K, reserved 1056768K class space used 284K, capacity 386K, committed 512K, reserved 1048576K
刚开始数组都分配在eden区,分配到第3次,eden装不下,触发GC,留下一个数组,
DefNew: 2582K->1298K(5376K),分配到第6个,eden装不下,触发GC,留下一个数组,
DefNew: 4439K->1024K(5376K),分配到第9个,eden装不下,触发GC,留下一个数组,
DefNew: 4158K->1024K(5376K),加上第9个数组和第10个数组,最后剩下三个数组在新生代。
使用
java -Xmx20m -Xms20m -Xmn15m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+UseSerialGC NewSizeDemo执行时,结果如下
def new generation total 13824K, used 11224K [0x00000007bec00000, 0x00000007bfb00000, 0x00000007bfb00000) eden space 12288K, 91% used [0x00000007bec00000, 0x00000007bf6f6038, 0x00000007bf800000) from space 1536K, 0% used [0x00000007bf800000, 0x00000007bf800000, 0x00000007bf980000) to space 1536K, 0% used [0x00000007bf980000, 0x00000007bf980000, 0x00000007bfb00000) tenured generation total 5120K, used 0K [0x00000007bfb00000, 0x00000007c0000000, 0x00000007c0000000) the space 5120K, 0% used [0x00000007bfb00000, 0x00000007bfb00000, 0x00000007bfb00200, 0x00000007c0000000) Metaspace used 2599K, capacity 4486K, committed 4864K, reserved 1056768K class space used 284K, capacity 386K, committed 512K, reserved 1048576K
可以看出,新生代内存足够存放10M的数组,于是所有的数组都分配在新生代,没有GC。
基本策略
尽可能将对象预留在新生代,减少老年代的GC次数,比如像上面例子中数组都分配在老年代就不好了
扩展
Java虚拟机还提供了一个参数
-XX:NewRatio,用来设置老年代/新生代的比例。
导出堆信息
当发生堆溢出时,为了及时获取堆信息,可以使用-XX:+HeapDumpOnOutOfMemoryError,在发生堆溢出时导出堆信息,并且,可以使用
-XX:+HeapDumpPath指定导出路径
例如:
public class DumpOOM{ public static void main(String[] args) { java.util.Vector vector=new java.util.Vector(); for(int i=0;i<25;i++){ vector.add(new byte[1*1024*1024]); } } }
使用
java -Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/ruochenxing/Desktop/java/out.dump DumpOOM
执行,设置最大堆为20m,所以一定会OutOfMemoryError。
除此之外,还可以在溢出后执行某个脚本
`
java -Xmx20m -Xms5m "-XX:OnOutOfMemoryError=脚本的路径 %p" -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/ruochenxing/Desktop/java/out.dump DumpOOM
获取GC信息
简要: -verbose:gc -XX:+PrintGC
详细: -XX:+PrintGCDetails
更详细: -XX:+PrintHeapAtGC
GC与应用程序相互执行耗时:-XX:+PrintGCApplicationStoppedTime -
XX:+PrintGCApplicationConcurrentTime
打印GC发生的时间: -XX:+PrintGCTimeStamps
打印新生对象晋升为老年代的实际阈值:-XX:+PrintTenuringDistribution
指定最大阈值: -XX:MaxTenuringThreshold=18
输出到文件: -Xloggc:c:\gc.log
`
非堆配置
方法区配置
JDK6,7中,可以使用-XX:PermSize(初始大小) 和 -XX:MaxPermSize(最大值)配置永久区大小JDK8中,永久区被彻底移除,使用了新的元数据区存放类的元数据。默认情况下,元数据区只受系统可用内存的限制,但仍然可以使用参数-XX:MaxMetaspaceSize指定元数据区的最大可用值。
栈配置
-Xss指定线程的栈大小直接内存配置
在Java NIO被广泛使用的直接内存(非Java堆内)也可以通过使用参数 -XX:MaxDirectMemorySize设置,如不设置,默认为最大堆空间,即-Xmx。如果直接内存达到设定值,就会触发GC,如果不能有效释放足够的空间,直接内存溢出同样会抛OOM。
关于直接内存的访问和申请测试。
import java.nio.*; public class AccessDirectBuffer{ //直接内存访问 public void directAccess(){ long startTime=System.currentTimeMillis(); ByteBuffer buffer=ByteBuffer.allocateDirect(500); for(int i=0;i<100000;i++){ for(int j=0;j<99;j++){ buffer.putInt(j); } buffer.flip(); for(int j=0;j<99;j++){ buffer.getInt(); } buffer.clear(); } long endTime=System.currentTimeMillis(); System.out.println("testDirectWrite:"+(endTime-startTime)); } //堆内存访问 public void bufferAccess(){ long startTime=System.currentTimeMillis(); ByteBuffer buffer=ByteBuffer.allocate(500); for(int i=0;i<100000;i++){ for(int j=0;j<99;j++){ buffer.putInt(j); } buffer.flip(); for(int j=0;j<99;j++){ buffer.getInt(); } buffer.clear(); } long endTime=System.currentTimeMillis(); System.out.println("testBufferWrite:"+(endTime-startTime)); } public static void main(String[] args) { AccessDirectBuffer all=new AccessDirectBuffer(); //热身代码,忽略输出 all.bufferAccess(); all.directAccess(); all.bufferAccess(); all.directAccess(); } } /* java -client AccessDirectBuffer testBufferWrite:36 testDirectWrite:22 testBufferWrite:16 testDirectWrite:12 直接内存访问比堆内存访问快 java -server AccessDirectBuffer testBufferWrite:36 testDirectWrite:25 testBufferWrite:18 testDirectWrite:31 直接内存访问比堆内存访问慢 */
import java.nio.*; public class AllocDirectBuffer{ public void directAlloc(){ long startTime=System.currentTimeMillis(); for(int i=0;i<200000;i++){ ByteBuffer buffer=ByteBuffer.allocateDirect(1000); } long endTime=System.currentTimeMillis(); System.out.println("directAllo:"+(endTime-startTime)); } public void bufferAlloc(){ long startTime=System.currentTimeMillis(); for(int i=0;i<200000;i++){ ByteBuffer buffer=ByteBuffer.allocate(1000); } long endTime=System.currentTimeMillis(); System.out.println("bufferAllo:"+(endTime-startTime)); } public static void main(String[] args) { AllocDirectBuffer all=new AllocDirectBuffer(); all.directAlloc(); all.bufferAlloc(); all.directAlloc(); all.bufferAlloc(); } } /* java -server AllocDirectBuffer directAllo:166 bufferAllo:126 directAllo:137 bufferAllo:103 直接内存空间申请比堆内存慢 java -client AllocDirectBuffer directAllo:168 bufferAllo:125 directAllo:133 bufferAllo:101 直接内存空间申请比堆内存慢 */
直接内存适合申请次数较少,访问频繁的场合;内存空间如果需要频繁申请,不适合用直接内存
工作模式:
使用java -version查看。默认情况下是
server如果想要使用client模式,则使用命令
java -client ...即可。
与client模式相比,server模式启动较慢,此模式会尝试收集更多的系统性能信息,使用更复杂的优化算法对程序进行优化。
当系统进入稳定期后,server模式的执行速度会远远快于client模式,对于后台长期运行的系统,可使用server模式,对于用户界面程序,可使用client模式。
在64位机器上,更倾向于server模式
相关文章推荐
- BlogEngine.Net架构与源代码分析系列part9:开发扩展(上)——Extension与管理上的实现
- BlogEngine.Net架构与源代码分析系列part11:开发扩展(下)——自定义Theme
- JVM系列三:JVM参数设置、分析
- JVM系列四:生产环境参数实例及分析【生产环境实例增加中】
- Cloudera Hadoop 4系列实战课程(电商业日志流量分析项目)
- Apche日志系列(4):日志分析
- JVM系列四:生产环境参数实例及分析【生产环境实例增加中】
- JVM优化系列三:JVM参数设置、分析
- jQuery源码分析系列:Extend扩展方法
- JVM系列四:生产环境参数实例及分析【生产环境实例增加中】
- 扩展log4j系列[一]DailyRollingFileAppender的按分钟生成日志修改为按n分钟生成日志
- JVM系列三:JVM参数设置、分析
- QUAKE系列引擎以及基于QUAKE扩展引擎的源代码分析
- JVM系列四:生产环境参数实例及分析【生产环境实例增加中】
- JVM系列三:JVM参数设置、分析
- BlogEngine.Net架构与源代码分析系列part10:开发扩展(中)——Widget小工具
- 网站日志分析方法系列一:聚焦式分析
- JVM崩溃日志信息分析
- JVM系列三:JVM参数设置、分析
- JVM系列三:JVM参数设置、分析