文档章节

JVM系列扩展:Java虚拟机日志分析

那位先生_
 那位先生_
发布于 2016/10/15 20:34
字数 2519
阅读 200
收藏 1

堆配置

堆大小设置

当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->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 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模式

© 著作权归作者所有

那位先生_

那位先生_

粉丝 131
博文 109
码字总数 242433
作品 0
深圳
后端工程师
私信 提问
加载中

评论(0)

JVM规范系列第4章:Class文件格式

这一章节讲的是字节码的整个组成格式,读懂了这一章,就读懂了字节码文件。对于这一章的学习,我更推荐作为工具书去查找。最好是找一个最简单的Hello World例子,一个字节一个字节去分析其含...

陈树义
2018/12/19
0
0
Java 10大优点—Part4—Java内存模型

在忙着参加在爱沙尼亚进行的 TEDx talk 演讲活动以及在比利时举办的一届非常忙碌的Devoxx 会议的间隙,我将继续推进 Java’s Rocking 的系列博文。 对还没有接触过这个系列博文的读者,不妨先...

foxlee
2013/12/09
453
1
JVM系列开篇:为什么要学虚拟机?

跟许多人一样,我一开始接触 Java 虚拟机只是因为面试需要用到,所以硬着头皮看看。所以很多人对于为什么要学虚拟机这个问题,他们的答案都是:因为面试。但我经过了几年的学习和实战,我发现...

陈树义
2018/11/06
0
0
JVM(二)Java虚拟机组成详解

导读:详细而深入的总结,是对知识“豁然开朗”之后的“刻骨铭心”,想忘记都难。 Java虚拟机(Java Virtual Machine)下文简称jvm,上一篇我们对jvm有了大体的认识,进入本文之后我们将具体...

王磊的博客
2019/01/14
232
0
JVM 虚拟机(对象创建,类加载器,执行引擎等),

1.揭开 Java 对象创建的奥秘? 2.class 文件结构详解? 3.详解 Java 类的加载过程? > Java 对象创建,class 文件结构 Java对象模型 。Java对象保存在堆内存中。在内存中,一个Java对象包含三...

desaco
2018/08/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

1核2G云服务哪家便宜?

前言: 又到一年续费时,我们来盘点哪些云厂商新手活动给力?有人说我又不是新手,有啥用?你要知道你作为家里唯一一位程序员,有强大的家庭后盾,比如爸爸妈妈爷爷奶奶叔叔阿姨......... 不过...

王念博客
9分钟前
142
0
JavaScript 箭头函数:适用与不适用场景

JavaScript 箭头函数:适用与不适用场景 现代 JavaScript 中最引人注目的功能之一是引入了箭头函数,用 => 来标识。 这种函数有两大优点 – 非常简洁的语法,和更直观的作用域和 this的绑定。...

王囧草
18分钟前
46
0
Docker快速入门

1 几个概念 Docker可以把开发的软件代码以及软件所依赖的所有运行时环境、依赖类库都打包成一个容器镜像,因此使用docker打包软件可以让程序员开发的程序运行在各种不同的计算机硬件环境中。...

即将秃头的Java程序员
20分钟前
68
0
Zookeeper-03-权限管理

Zookeeper-03-权限管理 用的不多,暂时先不整理了

moon888
21分钟前
36
0
渲染学习笔记——GPU应用阶段

1.GPU流水线 注:绿色可编程,橙色可控不可编程,红色完全不可控 2.顶点着色器 顶点着色器计算速度快于片元着色器,所以很多中间数据在顶点着色器计算。 3.裁剪 4.屏幕映射 5.三角形 6.片元着...

myctrd
27分钟前
61
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部