博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JVM系列扩展:Java虚拟机日志分析
阅读量:7002 次
发布时间:2019-06-27

本文共 11049 字,大约阅读时间需要 36 分钟。

hot3.png

堆配置

堆大小设置

当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)-655360fee   mem=5550672bytes  //5.29Mtotal mem=6094848bytes  //5.8M分配了1M空间给数组maxMemory=20316160bytesfee   mem=4502080bytes  //4.29Mtotal 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=20316160bytesfee   mem=4800984bytes  //4.5Mtotal mem=10358784bytes //9.8MHeap 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->1298K1555K->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 AccessDirectBuffertestBufferWrite:36testDirectWrite:22testBufferWrite:16testDirectWrite:12直接内存访问比堆内存访问快java -server AccessDirectBuffertestBufferWrite:36testDirectWrite:25testBufferWrite:18testDirectWrite: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:166bufferAllo:126directAllo:137bufferAllo:103直接内存空间申请比堆内存慢java -client AllocDirectBufferdirectAllo:168bufferAllo:125directAllo:133bufferAllo:101直接内存空间申请比堆内存慢 */

直接内存适合申请次数较少,访问频繁的场合;内存空间如果需要频繁申请,不适合用直接内存

工作模式:

使用java -version查看。默认情况下是server 如果想要使用client模式,则使用命令java -client ...即可。

  • 与client模式相比,server模式启动较慢,此模式会尝试收集更多的系统性能信息,使用更复杂的优化算法对程序进行优化。
  • 当系统进入稳定期后,server模式的执行速度会远远快于client模式,对于后台长期运行的系统,可使用server模式,对于用户界面程序,可使用client模式。
  • 在64位机器上,更倾向于server模式

转载于:https://my.oschina.net/aptx4869/blog/758965

你可能感兴趣的文章