您的位置:首页 > 编程语言 > Java开发

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模式
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: