一、垃圾回收日志参数
-XX:+PrintGC
打印简单GC日志
只要GC就会打印日志。
[GC 1023K->565K(5632K), 0.0012699 secs]
#日志说明:GC前堆空间使用量为1023K,GC后堆空间使用量为565K,当前可用堆空间的总和为5632K,本次GC时间为0.0012699 secs
-XX:+PrintGCDetails
打印详细GC日志[GC[DefNew:9791K->9791K(9792K),0.0000350 secs][Tenured:16632K->13533K(21888K),0.4063120 secs] 26424K->13533k(31680K),[Perm : 2583k->2583k(21248K)],0.4064710 secs [Times:user=0.41 sys=0.00, real=0.40 secs]] #日志说明: #[DefNew:9791K->9791K(9792K),0.0000350 secs] 新生代回收 #[Tenured:16632K->13533K(21888K),0.4063120 secs] 老年代回收 #26424K->13533k(31680K) 堆回收:由GC前的26M到GC后的13M,堆总可用变为31M,但是这里要注意,堆回收了13M,但是老年代只回收了3M,剩下的其实是新生代的内存回收,虽然日志里面显示着新生代没有回收,但是实际是被清空了的。 #[Perm : 2583k->2583k(21248K)] 永久区回收 #以上的日志是书上的日志,我自己的没有老年代和永久区的日志,而且年轻代的名称也不是DefNew,而是PSYoungGen [GC (Allocation Failure) [PSYoungGen: 2047K->512K(2560K)] 2047K->520K(9728K), 0.0009979 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 6 Heap PSYoungGen total 2560K, used 1243K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000) eden space 2048K, 35% used [0x00000007bfd00000,0x00000007bfdb6e98,0x00000007bff00000) from space 512K, 100% used [0x00000007bff00000,0x00000007bff80000,0x00000007bff80000) to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) ParOldGen total 7168K, used 8K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000) object space 7168K, 0% used [0x00000007bf600000,0x00000007bf602000,0x00000007bfd00000) Metaspace used 2709K, capacity 4486K, committed 4864K, reserved 1056768K class space used 289K, capacity 386K, committed 512K, reserved 1048576K
-XX:+PrintHeapAtGC
分别在每次GC前后分别打印堆信息。效果如下{Heap before GC invocations=1 (full 0): PSYoungGen total 2560K, used 2047K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000) eden space 2048K, 99% used [0x00000007bfd00000,0x00000007bfeffff0,0x00000007bff00000) from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000) ParOldGen total 7168K, used 0K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000) object space 7168K, 0% used [0x00000007bf600000,0x00000007bf600000,0x00000007bfd00000) Metaspace used 2700K, capacity 4486K, committed 4864K, reserved 1056768K class space used 288K, capacity 386K, committed 512K, reserved 1048576K [GC (Allocation Failure) [PSYoungGen: 2047K->512K(2560K)] 2047K->520K(9728K), 0.0007641 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] Heap after GC invocations=1 (full 0): PSYoungGen total 2560K, used 512K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000) eden space 2048K, 0% used [0x00000007bfd00000,0x00000007bfd00000,0x00000007bff00000) from space 512K, 100% used [0x00000007bff00000,0x00000007bff80000,0x00000007bff80000) to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) ParOldGen total 7168K, used 8K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000) object space 7168K, 0% used [0x00000007bf600000,0x00000007bf602000,0x00000007bfd00000) Metaspace used 2700K, capacity 4486K, committed 4864K, reserved 1056768K class space used 288K, capacity 386K, committed 512K, reserved 1048576K }
-XX:+PrintGCTimeStamps
输出GC的发生时间,时间为虚拟机启动后的时间偏移量。相当于-XX:+PrintGCDetails
加了个时间# 这个0.123就是时间偏移量,虚拟机启动后0.123秒发生了GC 0.123: [GC (Allocation Failure) [PSYoungGen: 2047K->512K(2560K)] 2047K->520K(9728K), 0.0015470 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 7 Heap PSYoungGen total 2560K, used 1353K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000) eden space 2048K, 41% used [0x00000007bfd00000,0x00000007bfdd25e8,0x00000007bff00000) from space 512K, 100% used [0x00000007bff00000,0x00000007bff80000,0x00000007bff80000) to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) ParOldGen total 7168K, used 8K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000) object space 7168K, 0% used [0x00000007bf600000,0x00000007bf602000,0x00000007bfd00000) Metaspace used 2708K, capacity 4486K, committed 4864K, reserved 1056768K class space used 289K, capacity 386K, committed 512K, reserved 1048576K
-XX:+PrintGCApplicationConcurrentTime
打印应用程序的执行时间-XX:+PrintGCApplicationStoppedTime
打印应用程序由于GC而产生的停顿时间。-XX:+PrintReferenceGC
跟踪系统内的软引用、弱引用、虚引用Finallize队列。0.115: Application time: 0.0382099 seconds 0.115: [GC (Allocation Failure) 0.116: [SoftReference, 0 refs, 0.0000373 secs]0.116: [WeakReference, 9 refs, 0.0000084 secs]0.116: [FinalReference, 62 refs, 0.0000349 secs]0.116: [PhantomReference, 0 refs, 0 refs, 0.0000195 secs]0.116: [JNI Weak Reference, 0.0000093 secs][PSYoungGen: 2047K->496K(2560K)] 2047K->528K(9728K), 0.0018549 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 0.116: Total time for which application threads were stopped: 0.0019804 seconds, Stopping threads took: 0.0000121 seconds
Java从1.2版本开始引入了4种引用,这4种引用的级别由高到低依次为:
强引用 > 软引用 > 弱引用 > 虚引用
(1)强引用(StrongReference) 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
(2)软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
(3)弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
(4)虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
(5)FinalReference
对于重载了 Object 类的 finalize 方法的类实例化的对象(这里称为 f 对象),JVM 为了能在 GC 对象时触发 f 对象的 finalize 方法的调用,将每个 f 对象包装生成一个对应的FinalReference 对象,方便 GC 时进行处理。
FinalReference说明:FinalReference
-Xloggc
指定日志目录。-Xloggc:log/gc.log
二、类加载/制裁的跟踪
-verbos:class
跟踪类的加载和卸载
-XX:+TraceClassLoading
跟踪类加载,动态类的加载非常隐蔽,它们由代码逻辑控制,不出现在文件系统中,跟踪这些类,就需要使用-XX:+TraceClassLoading
等参数来观察系统实际使用的类。
-XX:+TraceClassUnloading
跟踪类的卸载
-verbos:class
= -XX:+TraceClassLoading
+ -XX:+TraceClassUnloading
测试代码:
package cn.shutdown.demo.jvm.trace;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* -XX:+TraceClassUnloading -XX:+TraceClassLoading
* <p>
* -verbose:class
*
* @author Dmn
*/
public class UnloadClass implements Opcodes {
public static void main(String args[]) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//ClassWriter 以字节码的形式生成类的类访问器, 参数
// ClassWriter.COMPUTE_MAXS 如果必须自动计算最大堆栈大小和局部变量数,则为true 。
// ClassWriter.COMPUTE_FRAMES 如果堆栈映射帧必须从头开始重新计算。
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
// 定义了一个 基于jdk1.7的 public类型的类,名为Example,继承于Object
cw.visit(V1_7, ACC_PUBLIC, "Example", null, "java/lang/Object", null);
//定义了一个构造方法
MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mw.visitVarInsn(ALOAD, 0);
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mw.visitInsn(RETURN);
mw.visitMaxs(0, 0);
mw.visitEnd();
// 生成main方法中的字节码指令
mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
//获取该方法
mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
//加载字符串参数
mw.visitLdcInsn("Hello world!");
//调用该方法
mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
mw.visitInsn(RETURN);
mw.visitMaxs(0, 0);
mw.visitEnd();
//生成class文件对应的二进制流
byte[] code = cw.toByteArray();
System.out.println("\n\n================================================");
for (int i = 0; i < 10; i++) {
//创建类加载器
UnloadClassLoader loader = new UnloadClassLoader();
//获取了 ClassLoader类的 defineClass方法对象
Method m = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
//设置方法的访问权限为可访问
m.setAccessible(true);
//调用 loader对象的 defineClass方法
//ClassLoader的defineClass方法的作用是:将字节数组转换为类Class的实例
//这样就可以将 刚刚生成的class文件的二进制流加载并转化为Example类的实例
m.invoke(loader, "Example", code, 0, code.length);
m.setAccessible(false);
System.gc();
}
}
}
package cn.shutdown.demo.jvm.trace;
public class UnloadClassLoader extends ClassLoader {
}
需要引用的pom依赖
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>5.0.4</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>5.0.4</version>
</dependency>
关于ASM 参考文章,写的非常清晰 Java技术专题-JVM研究系列(3)ASM库生成和修改class文件
运行后的效果:
可以看出日志输出中有引的加载和卸载的日志记录
[Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
。。。省略部分输出。。。
[Loaded cn.shutdown.demo.jvm.trace.UnloadClassLoader from file:/Users/dmn/IdeaProjects/demo/target/classes/]
[Loaded java.lang.ClassFormatError from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.io.IOException from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.AssertionStatusDirectives from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.NativeMethodAccessorImpl from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.DelegatingMethodAccessorImpl from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
#这里就开始循环中的输出了,从JVM定义的类中加载了Example
[Loaded Example from __JVM_DefineClass__]
[Loaded Example from __JVM_DefineClass__]
#从JVM定义的类中卸Example
[Unloading class Example 0x00000007c006a028]
[Loaded Example from __JVM_DefineClass__]
[Unloading class Example 0x00000007c006a828]
[Loaded Example from __JVM_DefineClass__]
[Unloading class Example 0x00000007c006a028]
。。。活力部分输出。。。
[Loaded java.lang.Shutdown from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
三、系统参数查看
-XX:+PrintVMOptions
打印虚拟机接受到的命令行的显式参数-XX:+PrintCommandLineFlags
打印传递给虚拟机的显式和隐式参数(隐式参数未必通过命令行给出 可能由虚拟机自行设置)-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintClassHistogram - XX:+PrintCommandLineFlags -XX:+PrintFlagsFinal -XX:+PrintVMOptions -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC #-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 这些都是隐式参数
-XX:+PrintFlagsFinal
查看系统的详细参数。[Global flags] intx ActiveProcessorCount = -1 {product} uintx AdaptiveSizeDecrementScaleFactor = 4 {product} uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product} uintx AdaptiveSizePausePolicy = 0 {product} uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product} uintx AdaptiveSizePolicyInitializingSteps = 20 {product} 。。。。省略大部分。。。
四、堆参数配置
- 最大堆和初始堆设置
-Xms
:初始堆
-Xmx
:最大堆
测试代码:
package cn.shutdown.demo.jvm;
/**
* -Xmx20m -Xms5m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
* @author dmn
* @date 2021/6/7
*/
public class HeapAlloc {
public static void main(String[] args) {
printMemory("");
//分配1M内存
byte[] b = new byte[1 * 1024 * 1024];
printMemory("分配了1M内存");
//分配4M内存
b = new byte[4 * 1024 * 1024];
printMemory("分配了4M内存");
}
static void printMemory(String step) {
System.out.println(step);
System.out.println("maxMemory=" + Runtime.getRuntime().maxMemory() + " bytes");
System.out.println("freeMemory=" + Runtime.getRuntime().freeMemory() + " bytes");
System.out.println("toatlMemory=" + Runtime.getRuntime().totalMemory() + " bytes");
}
}
运行结果:
-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC
maxMemory=20316160 bytes
freeMemory=5287536 bytes
toatlMemory=6094848 bytes
[GC (Allocation Failure) [DefNew: 788K->192K(1856K), 0.0012785 secs] 788K->363K(5952K), 0.0013054 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
分配了1M内存
maxMemory=20316160 bytes
freeMemory=4640168 bytes
toatlMemory=6094848 bytes
[GC (Allocation Failure) [DefNew: 1249K->0K(1856K), 0.0016099 secs][Tenured: 1387K->1387K(4096K), 0.0015703 secs] 1420K->1387K(5952K), [Metaspace: 2697K->2697K(1056768K)], 0.0032377 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
分配了4M内存
maxMemory=20316160 bytes
freeMemory=4708856 bytes
toatlMemory=10358784 bytes
Heap
def new generation total 1920K, used 69K [0x00000007bec00000, 0x00000007bee10000, 0x00000007bf2a0000)
eden space 1728K, 4% used [0x00000007bec00000, 0x00000007bec11498, 0x00000007bedb0000)
from space 192K, 0% used [0x00000007bedb0000, 0x00000007bedb0000, 0x00000007bede0000)
to space 192K, 0% used [0x00000007bede0000, 0x00000007bede0000, 0x00000007bee10000)
tenured generation total 8196K, used 5483K [0x00000007bf2a0000, 0x00000007bfaa1000, 0x00000007c0000000)
the space 8196K, 66% used [0x00000007bf2a0000, 0x00000007bf7fad20, 0x00000007bf7fae00, 0x00000007bfaa1000)
Metaspace used 2703K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 289K, capacity 386K, committed 512K, reserved 1048576K
Process finished with exit code 0
测试代码运行的初始堆是5m,最大堆是20m,程序运行以后
1. 第一次查看的内存结果
maxMemory=20316160 bytes
freeMemory=5287536 bytes
toatlMemory=6094848 bytes
2. 分配了1M内存以后,freeMemory减少了1M,变为4640168 bytes
freeMemory=4640168 bytes
进行了一次垃圾回收的结果
[GC (Allocation Failure)
[DefNew: 1249K->0K(1856K), 0.0016099 secs] 新生代可用空间为 1856K(大约是1.5M)
[Tenured: 1387K->1387K(4096K), 0.0015703 secs] 老年代可用空间为 4096K (4M)
1420K->1387K(5952K) 堆的总可用空间为 5952K(近6M)
[Metaspace: 2697K->2697K(1056768K)], 0.0032377 secs]
3. 分配4M内存后
因为从刚刚的GC记录可以看到,新生代的可用空间只有1.5M了,小于程序申请的4M空间,因此堆空间进行扩容,扩容后,总内存为约10M,剩余内存为 4708856,约5M
maxMemory=20316160 bytes
freeMemory=4708856 bytes
toatlMemory=10358784 bytes
在实际工作中,也可以直接将初始堆 -Xms与最大堆 -Xmx设置相等,好处是可以减少程序运行时进行垃圾回收的次数,从而提高程序的性能。
新生代配置
-Xmn
:设置新生代大小。设置较大的新生代会减少老年代的大小,这个参数对系统性能及GC行为有很大影响,新生代大小一般设置为整个堆空间的1/3到1/4左右。-XX:SurvivorRatio
:设置新生代中eden空间和from/to空间的比例关系。含义: -XX:SurvivorRatio=eden/from=eden/to 使用方法: -XX:SurvivorRatio=2
-XX:NewRatio
:设置新生代和老年代的比例-XX:NewRatio=老年代/新生代
测试代码:
package cn.shutdown.demo.jvm;
/**
* -Xmx20m -Xms20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails
* @author dmn
* @date 2021/6/15
*/
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 HotSpot(TM) 64-Bit Server VM warning: NewSize (1536k) is greater than the MaxNewSize (1024k). A new max generation size of 1536k will be used.
[GC (Allocation Failure) [PSYoungGen: 512K->496K(1024K)] 512K->512K(19968K), 0.0015693 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 1024K, used 738K [0x00000007bfe80000, 0x00000007c0000000, 0x00000007c0000000)
eden space 512K, 47% used [0x00000007bfe80000,0x00000007bfebc8d0,0x00000007bff00000)
from space 512K, 96% used [0x00000007bff00000,0x00000007bff7c010,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 18944K, used 10256K [0x00000007bec00000, 0x00000007bfe80000, 0x00000007bfe80000)
object space 18944K, 54% used [0x00000007bec00000,0x00000007bf6040a0,0x00000007bfe80000)
Metaspace used 2700K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 289K, capacity 386K, committed 512K, reserved 1048576K
1) 从结果里我们可以看到 ,青年代虽然输出总大小为1024k,但是 eden,from,to的空间分别都为 512k,这个的原因可以看下我的另一篇文章Java HotSpot™ 64-Bit Server VM warning: NewSize (1536k) is greater than the MaxNewSize (1024k) 里的说明,是jdk7与jdk8的区别导致的,jdk8的青年代的最小值为1536k,因为参数给定的值是1024k,所以被默认设置为了1536k,正好是eden,from,to各512k。
PSYoungGen total 1024K, used 738K [0x00000007bfe80000, 0x00000007c0000000, 0x00000007c0000000)
eden space 512K, 47% used [0x00000007bfe80000,0x00000007bfebc8d0,0x00000007bff00000)
from space 512K, 96% used [0x00000007bff00000,0x00000007bff7c010,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 18944K, used 10256K [0x00000007bec00000, 0x00000007bfe80000, 0x00000007bfe80000)
另外,由于eden区无法容纳任何一个程序中分配的1MB的数组,所以触发了一次新生代的GC,对eden区进行了部分回收,同时,这个偏小的新生代无法为1MB数组预留空间,所以,所有的数组都分配在了老年代,老年代最终占用了10256K的空间。
2) 上述测试代码如果使用-Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails
的JVM参数来运行的话,结果如下。
分配1M内存
分配1M内存
分配1M内存
[GC (Allocation Failure) [PSYoungGen: 3900K->1520K(5632K)] 3900K->1560K(18944K), 0.0016175 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
分配1M内存
分配1M内存
分配1M内存
[GC (Allocation Failure) [PSYoungGen: 4672K->1520K(5632K)] 4712K->1568K(18944K), 0.0013520 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
分配1M内存
分配1M内存
分配1M内存
[GC (Allocation Failure) [PSYoungGen: 4663K->1520K(5632K)] 4711K->1568K(18944K), 0.0007291 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
分配1M内存
Heap
PSYoungGen total 5632K, used 2626K [0x00000007bf900000, 0x00000007c0000000, 0x00000007c0000000)
eden space 4096K, 27% used [0x00000007bf900000,0x00000007bfa14990,0x00000007bfd00000)
from space 1536K, 98% used [0x00000007bfd00000,0x00000007bfe7c020,0x00000007bfe80000)
to space 1536K, 0% used [0x00000007bfe80000,0x00000007bfe80000,0x00000007c0000000)
ParOldGen total 13312K, used 48K [0x00000007bec00000, 0x00000007bf900000, 0x00000007bf900000)
object space 13312K, 0% used [0x00000007bec00000,0x00000007bec0c000,0x00000007bf900000)
Metaspace used 2702K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 289K, capacity 386K, committed 512K, reserved 1048576K
Process finished with exit code 0
从结果看出,青年代被初始值设置为7M以后,因为 -XX:SurvivorRatio=2
,所以eden区与from区比为2/1,所以空间大小为
eden space 4096K, 27% used [0x00000007bf900000,0x00000007bfa14990,0x00000007bfd00000)
from space 1536K, 98% used [0x00000007bfd00000,0x00000007bfe7c020,0x00000007bfe80000)
to space 1536K, 0% used [0x00000007bfe80000,0x00000007bfe80000,0x00000007c0000000)
每次程序分配会分区eden区的内存,分配三次以后,eden区的内存不足了,对eden区进行部分回收。由于程序每申请一次空间,也同时废弃上一次申请的内存(上次申请的内存失去了引用),所以在新生代的GC中,有效回收了失效的内在,最终结果是:所有的内在分配都在新生代进行,通过GC保证了新生代有足够的空间,而老年代没有为这些数组预留任何空间,只是在GC过程中,部分新生代对象晋升到老年代。
3) 使用参数-Xmx20m -Xms20m -Xmn15m -XX:SurvivorRatio=2 -XX:+PrintGCDetails
运行上述代码,得到的输出为:
Heap
PSYoungGen total 13824K, used 11469K [0x00000007bf100000, 0x00000007c0000000, 0x00000007c0000000)
eden space 12288K, 93% used [0x00000007bf100000,0x00000007bfc336f8,0x00000007bfd00000)
from space 1536K, 0% used [0x00000007bfe80000,0x00000007bfe80000,0x00000007c0000000)
to space 1536K, 0% used [0x00000007bfd00000,0x00000007bfd00000,0x00000007bfe80000)
ParOldGen total 5120K, used 0K [0x00000007bec00000, 0x00000007bf100000, 0x00000007bf100000)
object space 5120K, 0% used [0x00000007bec00000,0x00000007bec00000,0x00000007bf100000)
Metaspace used 2702K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 289K, capacity 386K, committed 512K, reserved 1048576K
这次执行中,新生代初始化15M的空间,eden区占用了12288K,满足10M数组的分配 。因此所有的分配行为都在eden直接运行,且没有触发任何的GC行为,因为 from/to和老年代的使用率都为0。
不同的堆的分布情况,对系统执行会产生一定影响。在实际工作中,应该根据系统的特点做合理的设置,基本的策略是:尽可能将对象预留在新生代,减少老年代的GC次数。
4) 使用参数-Xmx20M -Xms20M -XX:NewRatio=2 -XX:+PrintGCDetails
运行测试代码,输出如下:
分配1M内存[B@45ee12a7
分配1M内存[B@330bedb4
分配1M内存[B@2503dbd3
分配1M内存[B@4b67cf4d 发生GC的时候,这个对象与引用b还有关联,所以这个会放到from/to区,但是空间不足,所以给放到了老年代
[GC (Allocation Failure) [PSYoungGen: 5014K->512K(6144K)] 5014K->1560K(19968K), 0.0014339 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
分配1M内存[B@7ea987ac
分配1M内存[B@12a3a380
分配1M内存[B@29453f44
分配1M内存[B@5cad8086
分配1M内存[B@6e0be858 发生GC的时候,这个对象与引用b还有关联,所以这个会放到from/to区,但是空间不足,所以给放到了老年代
[GC (Allocation Failure) [PSYoungGen: 5742K->496K(6144K)] 6790K->2580K(19968K), 0.0011273 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
分配1M内存[B@61bbe9ba
Heap
PSYoungGen total 6144K, used 1743K [0x00000007bf980000, 0x00000007c0000000, 0x00000007c0000000)
eden space 5632K, 22% used [0x00000007bf980000,0x00000007bfab7df0,0x00000007bff00000)
from space 512K, 96% used [0x00000007bff80000,0x00000007bfffc010,0x00000007c0000000)
to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
ParOldGen total 13824K, used 2084K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000)
object space 13824K, 15% used [0x00000007bec00000,0x00000007bee09030,0x00000007bf980000)
Metaspace used 2703K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 289K, capacity 386K, committed 512K, reserved 1048576K
Process finished with exit code 0
堆大小为20M,老年代和新生代比为2:1,所以老年代大小为13824K,新生代大小为 6144K。新生代大小不够10M的数组分配,所以产生新生代的GC,新生代GC时,from/to空间不足以容纳任何一个1MB的数组,影响了新生代的正常回收,故新生代回收时需要老年代进行空间担保。
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ,1:2只是一个大概的值,比如说我分配Xms20M,输出的比例是 6:13.5 ,Xms10M,输出比例为2.5:7,是一个大概的1:2),即:新生代 ( Young ) = 1⁄3 的堆空间大小。老年代 ( Old ) = 2⁄3 的堆空间大小。其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。 默认的,Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8⁄10 的新生代空间大小,from = to = 1⁄10 的新生代空间大小。 JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。 因此,新生代实际可用的内存空间为 9⁄10 ( 即90% )的新生代空间。
- 堆溢出处理
-XX:+HeapDumpOnOutOfMemoryError
在内在溢出时导出整个堆信息
-XX:HeapDumpPath
指定导出堆的存放路径
-XX:OnOutOfMemoryError
出现OOM时触发操作,用法如下,OOM时调用 printStack.sh脚本。
"-XX:OnOutOfMemoryError=/Users/dmn/IdeaProjects/demo/printStack.sh %p"
测试代码:
import java.util.ArrayList;
import java.util.List;
/**
*
* -XX:+PrintGCDetails -Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=DumpOOM.dump
* @author dmn
*/
public class DumpOOM {
public static void main(String[] args) {
List l = new ArrayList<>();
for (int i = 0; i < 100; i++) {
l.add(new byte[1 * 1024 * 1024]);
System.out.println("分配了1M内存");
}
}
}
运行结果:
分配了1M内存
分配了1M内存
分配了1M内存
分配了1M内存
分配了1M内存
分配了1M内存
分配了1M内存
分配了1M内存
分配了1M内存
分配了1M内存
分配了1M内存
分配了1M内存
分配了1M内存
分配了1M内存
[GC (Allocation Failure) [PSYoungGen: 765K->512K(1536K)] 14077K->13856K(15360K), 0.0006640 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 512K->480K(1536K)] 13856K->13832K(15360K), 0.0006287 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 480K->0K(1536K)] [ParOldGen: 13352K->13674K(13824K)] 13832K->13674K(15360K), [Metaspace: 2696K->2696K(1056768K)], 0.0040420 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 13674K->13674K(15360K), 0.0007653 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 13674K->13662K(13824K)] 13674K->13662K(15360K), [Metaspace: 2696K->2696K(1056768K)], 0.0048541 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to DumpOOM.dump ...
Heap dump file created [14589700 bytes in 0.025 secs]
Heap
PSYoungGen total 1536K, used 31K [0x00000007bf980000, 0x00000007bfc80000, 0x00000007c0000000)
eden space 1024K, 3% used [0x00000007bf980000,0x00000007bf987c68,0x00000007bfa80000)
from space 512K, 0% used [0x00000007bfa80000,0x00000007bfa80000,0x00000007bfb00000)
to space 512K, 0% used [0x00000007bfc00000,0x00000007bfc00000,0x00000007bfc80000)
ParOldGen total 13824K, used 13662K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000)
object space 13824K, 98% used [0x00000007bec00000,0x00000007bf957930,0x00000007bf980000)
Metaspace used 2727K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 292K, capacity 386K, committed 512K, reserved 1048576K
以上的运行结果,我没看懂的一点就是,为什么老年代的空间是 13824k,按说,初始化的堆内存是5m,这样新生代的默认内存是1.5M,新老比1:2,老年代就是3M左右,然后程序运行以后,因为新生代的eden区是1024K、from区和to区是512K,理论上装不下 1M的对象,就把对象直接给干到了老年代去了,按输出结果看出来老年代空间是13824k,所以装了13个1M的对象以后,就装不下了,就OOM了,但是有个问题啊,新生代是1.5M,老年代是14M,加起来也才 16M,如果算上那个Metaspace的 2.7M的话,倒是大概能有个20M的内存,但是方法区/元数据是所有线程共享的内存区域,用于保存系统的类信息,类的字段、方法、常量池等。是与堆、栈并列存在的一块内存区域,这块的内存应该不会算在堆内存的20M里面的,那少的那4M左右的内存去哪了呢?后面再把这个坑填上。
五、非堆内存的参数配置
- 方法区配置
jdk1.6、jdk1.7等版本
-XX:PermSize
初始永久区大小
-XX:MaxPermSize
配置最大永久区大小
jdk1.8 永久区移除,改为元数据区存放类的元数据,默认情况下,元数据区只受系统可用内存限制,可以使用
-XX:MaxMetaspaceSize
指定永久区的最大可用值。
- 栈配置
-Xss
指定线程栈大小
- 直接内存配置
-XX:MaxDirectMemorySize
设置最大可用直接内存,如不设置 ,默认值为最大堆空间,即-Xmx,当直接内存使用量达到-XX:MaxDirectMemorySize
时,会触发垃圾回收
直接内存适合申请次数较少,访问较频繁的场合,如果内存空间本身需要频繁申请,则不适合使用直接内存。
访问频繁场合的测试代码:
import java.nio.ByteBuffer;
/**
* -server 模式下 差异明显
*/
public class AccessDirectBuffer {
public static void main(String[] args) {
AccessDirectBuffer alloc = new AccessDirectBuffer();
alloc.bufferAccess();
alloc.directAccess();
alloc.bufferAccess();
alloc.directAccess();
}
/**直接内存访问*/
public void directAccess() {
long starttime = System.currentTimeMillis();
//申请500个字节的直接内存
ByteBuffer b = ByteBuffer.allocateDirect(500);
for (int i = 0; i < 100000; i++) {
for (int j = 0; j < 99; j++)
//存
b.putInt(j);
//翻转
b.flip();
for (int j = 0; j < 99; j++)
//取
b.getInt();
b.clear();
}
long endtime = System.currentTimeMillis();
System.out.println("testDirectWrite:" + (endtime - starttime));
}
/**堆内存访问*/
public void bufferAccess() {
long starttime = System.currentTimeMillis();
//申请 500个字节的内存空间
ByteBuffer b = ByteBuffer.allocate(500);
for (int i = 0; i < 100000; i++) {
for (int j = 0; j < 99; j++)
//存
b.putInt(j);
//翻转
b.flip();
for (int j = 0; j < 99; j++)
//取
b.getInt();
b.clear();
}
long endtime = System.currentTimeMillis();
System.out.println("testBufferWrite:" + (endtime - starttime));
}
}
频繁申请内存场合测试代码:
import java.nio.ByteBuffer;
/**
* 直接内存分配较慢
*/
public class AllocDirectBuffer {
public static void main(String[] args) {
AllocDirectBuffer alloc = new AllocDirectBuffer();
alloc.bufferAllocate();
alloc.directAllocate();
alloc.bufferAllocate();
alloc.directAllocate();
}
public void directAllocate() {
long starttime = System.currentTimeMillis();
for (int i = 0; i < 200000; i++) {
//申请直接内存
ByteBuffer b = ByteBuffer.allocateDirect(1000);
}
long endtime = System.currentTimeMillis();
System.out.println("directAllocate:" + (endtime - starttime));
}
public void bufferAllocate() {
long starttime = System.currentTimeMillis();
for (int i = 0; i < 200000; i++) {
//申请堆内存
ByteBuffer b = ByteBuffer.allocate(1000);
}
long endtime = System.currentTimeMillis();
System.out.println("bufferAllocate:" + (endtime - starttime));
}
}
六、虚拟机工作模式
虚拟机系统会根据当前计算机环境自动选择运行模式,使用-version
参数可以查看当前的模式
-client
Client模式
-server
Server模式,与Client模式比, Server模式启动比较慢,会收集更多系统性能信息,使用更复杂的优化算法对程序进行优化。系统启动后执行速度远快于Client模式。
使用 -XX:+PrintFlagsFinal
参数查看Client模式和Server模式给定的默认参数,如以下可以看到,我的mac电脑貌似 -server
和-client
都是用Server模式运行的。64位系统中虚拟机更倾向于使用Server模式运行。
~% java -XX:+PrintFlagsFinal -server -version | grep -E ' CompileThreshold|MaxHeapSize'
intx CompileThreshold = 10000 {pd product}
uintx MaxHeapSize := 4294967296 {product}
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)
~% java -XX:+PrintFlagsFinal -client -version | grep -E ' CompileThreshold|MaxHeapSize'
intx CompileThreshold = 10000 {pd product}
uintx MaxHeapSize := 4294967296 {product}
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)