2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > JVM学习03-常用Java虚拟机参数

JVM学习03-常用Java虚拟机参数

时间:2019-12-14 20:41:02

相关推荐

JVM学习03-常用Java虚拟机参数

JVM学习03-常用Java虚拟机参数

一、垃圾回收日志参数

-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] 6HeapPSYoungGentotal 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 1056768Kclass space used 289K, capacity 386K, committed 512K, reserved 1048576K

-XX:+PrintHeapAtGC分别在每次GC前后分别打印堆信息。效果如下

{Heap before GC invocations=1 (full 0):PSYoungGentotal 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 1056768Kclass 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):PSYoungGentotal 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 1056768Kclass space used 288K, capacity 386K, committed 512K, reserved 1048576K}

-XX:+PrintGCTimeStamps输出GC的发生时间,时间为虚拟机启动后的时间偏移量。相当于-XX:+PrintGCDetails加了个时间

# 这个0.123就是时间偏移量,虚拟机启动后0.123秒发生了GC0.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] 7HeapPSYoungGentotal 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 1056768Kclass space used 289K, capacity 386K, committed 512K, reserved 1048576K

-XX:+PrintGCApplicationConcurrentTime打印应用程序的执行时间

-XX:+PrintGCApplicationStoppedTime打印应用程序由于GC而产生的停顿时间。

-XX:+PrintReferenceGC跟踪系统内的软引用、弱引用、虚引用Finallize队列。

0.115: Application time: 0.0382099 seconds0.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 以字节码的形式生成类的类访问器, 参数// PUTE_MAXS 如果必须自动计算最大堆栈大小和局部变量数,则为true 。// PUTE_FRAMES 如果堆栈映射帧必须从头开始重新计算。ClassWriter cw = new ClassWriter(PUTE_MAXS | PUTE_FRAMES);// 定义了一个 基于jdk1.7的 public类型的类,名为Example,继承于Objectcw.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 parable 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 /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 bytesfreeMemory=5287536 bytestoatlMemory=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 bytesfreeMemory=4640168 bytestoatlMemory=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 bytesfreeMemory=4708856 bytestoatlMemory=10358784 bytesHeapdef 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 1056768Kclass space used 289K, capacity 386K, committed 512K, reserved 1048576KProcess finished with exit code 0测试代码运行的初始堆是5m,最大堆是20m,程序运行以后1. 第一次查看的内存结果maxMemory=20316160 bytesfreeMemory=5287536 bytestoatlMemory=6094848 bytes2. 分配了1M内存以后,freeMemory减少了1M,变为4640168 bytesfreeMemory=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,约5MmaxMemory=20316160 bytesfreeMemory=4708856 bytestoatlMemory=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 /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] HeapPSYoungGentotal 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 1056768Kclass space used 289K, capacity 386K, committed 512K, reserved 1048576K

从结果里我们可以看到 ,青年代虽然输出总大小为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。

PSYoungGentotal 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的空间。

上述测试代码如果使用-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内存HeapPSYoungGentotal 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 1056768Kclass space used 289K, capacity 386K, committed 512K, reserved 1048576KProcess 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过程中,部分新生代对象晋升到老年代。

使用参数-Xmx20m -Xms20m -Xmn15m -XX:SurvivorRatio=2 -XX:+PrintGCDetails运行上述代码,得到的输出为:

HeapPSYoungGentotal 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 1056768Kclass space used 289K, capacity 386K, committed 512K, reserved 1048576K

这次执行中,新生代初始化15M的空间,eden区占用了12288K,满足10M数组的分配 。因此所有的分配行为都在eden直接运行,且没有触发任何的GC行为,因为 from/to和老年代的使用率都为0。

不同的堆的分布情况,对系统执行会产生一定影响。在实际工作中,应该根据系统的特点做合理的设置,基本的策略是:尽可能将对象预留在新生代,减少老年代的GC次数。

使用参数-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@61bbe9baHeapPSYoungGentotal 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 1056768Kclass space used 289K, capacity 386K, committed 512K, reserved 1048576KProcess 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% )的新生代空间。

JVM老年代和新生代的比例

堆溢出处理

-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 spaceDumping heap to DumpOOM.dump ...Heap dump file created [14589700 bytes in 0.025 secs]HeapPSYoungGentotal 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 1056768Kclass 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参数可以查看当前的模式

-clientClient模式

-serverServer模式,与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)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。