内存分代策略
简介
HotSpot 中为了提高内存对象内存分配和垃圾回收的效率,将内存分为了新生代(Eden+From Survivor+To Survivor)、老年代(OldGen)和永久代(PermGen)。新创建的对象分配在新生代,多次回收还存活的对象放在老年代,类信息、静态变量和字符串常量等存放在永久代中。新生代中需要频繁执行垃圾回收,老年代中不需要频繁垃圾回收,永久代一般来说不实现垃圾回收。这样对不同的区域实行不同的垃圾回收算法,可以提高垃圾回收效率。
在 Java7 之前,方法区在永久代,永久代和堆隔离,使用的是堆空间,永久代大小在启动 JVM 时设置一个固定的值,不可变;Java7 中将 static 从永久代移到堆中;Java8 中取消永久代,使用元空间(Metaspace)替代,与堆共享物理内存,逻辑上可以认为和堆在一起。元空间使用的是系统内存,因此有足够空间。
新生代
新生代垃圾回收效率高,Minor GC 是新生代的 GC,回收速度快,Eden 空间不足时会触发 Minor GC 进行回收操作。新生代分为三块空间,一个 Eden 和两个 Survivor(通常称为 To Survivor 和 From Survivor),当 Eden 被填满,会执行 Minor GC,将存活下来的对象转移到 To Survivor,From Survivor 中存活的对象,判断年龄阈值(默认 15,一轮 GC 表示增加一岁),如果超过阈值就放到老年代中,没超过的存到 To Survivor 中,然后将 To Survivor 和 From Survivor 互换,也就是 To Survivor 在 GC 之后永远是空的。
老年代
老年代包含长期存活的对象和多次 Minor GC 后存活下来的对象,通常老年代被占满或者显式调用 System.gc()方法才开始垃圾回收,老年代垃圾回收也就叫 Full GC/Major GC,
JVM 常用参数配置
了解了内存分代策略,下面可以学习一下常用的参数配置,并查看不同内存区域的变化情况。
怎样设置 JVM 参数
方法很多,可以上网查询,我这里说我使用的 Eclipse 中设置方式。
在运行 Java 的按钮下面,Run As 下面,有一个 Run Configurations->Arguments->VMarguments
Trace 跟踪参数
Trace 跟踪参数就是跟踪 GC 运行的参数。
打印 GC 简要信息
-verbose:gc
-XX:+PrintGC
-verbose:gc 表示输出虚拟机中 GC 的情况
-XX:+PrintGC 功能和-verbose:gc 一样
Object obj = new byte[1*1024*1024];
obj = null;
System.gc();
/* 输出:
[GC (System.gc()) 3020K->704K(125952K), 0.0008022 secs]
[Full GC (System.gc()) 704K->514K(125952K), 0.0049984 secs]
*/
上面的代码,将 obj 设置为 null,让 GC 回收创建的 BYTE 数组,是属于新生代的 Minor GC。调用 System.gc(),是 Full GC
打印 GC 详细信息
-XX:+PrintGCDetails //打印GC详细信息
-XX:+PrintGCTimeStamps //打印GC发生的时间戳
-XX:+PrintGCDetails的输出:
Heap
PSYoungGen total 38400K, used 3686K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)
eden space 33280K, 11% used [0x00000000d5f00000,0x00000000d6299b30,0x00000000d7f80000)
from space 5120K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8980000)
to space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000)
ParOldGen total 87552K, used 0K [0x0000000081c00000, 0x0000000087180000, 0x00000000d5f00000)
object space 87552K, 0% used [0x0000000081c00000,0x0000000081c00000,0x0000000087180000)
Metaspace used 2633K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 280K, capacity 386K, committed 512K, reserved 1048576K
下面我简单介绍一下里面的参数:
PSYoungGen total 38400K, used 3686K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)
第一行的 PSYoungGen 表示新生代;total 38400K 表示总大小为 38400K;used 3686K 表示已经使用了 3686K;[0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)分别表示当前区域在内存中的位置,分别是低边界,当前边界和高边界,(0x00000000d8980000-0x00000000d5f00000)/1024/1024 = 42M,
33280K+5120K+5120K=42M(eden space+from space + to space)
文件输出 GC log
有时候在运行环境下,需要查看错误信息,就需要将 log 文件保存在本地,应该使用下面的命令:
-Xloggc:F:/gc.log
这样在 F 盘就出现 gc.log 文件,打开信息为:
Java HotSpot(TM) 64-Bit Server VM (25.192-b12) for windows-amd64 JRE (1.8.0_192-b12), built on Oct 6 2018 17:12:23 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 8272984k(2132632k free), swap 16544068k(9949684k free)
CommandLine flags: -XX:InitialHeapSize=132367744 -XX:MaxHeapSize=2117883904 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
0.100: [GC (System.gc()) [PSYoungGen: 3020K->664K(38400K)] 3020K->672K(125952K), 0.0009640 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.101: [Full GC (System.gc()) [PSYoungGen: 664K->0K(38400K)] [ParOldGen: 8K->514K(87552K)] 672K->514K(125952K), [Metaspace: 2627K->2627K(1056768K)], 0.0054643 secs] [Times: user=0.08 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 38400K, used 333K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)
eden space 33280K, 1% used [0x00000000d5f00000,0x00000000d5f534a8,0x00000000d7f80000)
from space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000)
to space 5120K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8980000)
ParOldGen total 87552K, used 514K [0x0000000081c00000, 0x0000000087180000, 0x00000000d5f00000)
object space 87552K, 0% used [0x0000000081c00000,0x0000000081c80ba0,0x0000000087180000)
Metaspace used 2633K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 280K, capacity 386K, committed 512K, reserved 1048576K
监控加载了哪些类
-XX:+TraceClassLoading
使用这个参数能获取到 Java 加载到的类。
[Opened D:\myeclipse\binary\jdk1.8\jre\lib\rt.jar]
[Loaded java.lang.Object from D:\myeclipse\binary\jdk1.8\jre\lib\rt.jar]
[Loaded java.io.Serializable from D:\myeclipse\binary\jdk1.8\jre\lib\rt.jar]
[Loaded java.lang.Comparable from D:\myeclipse\binary\jdk1.8\jre\lib\rt.jar]
[Loaded java.lang.CharSequence from D:\myeclipse\binary\jdk1.8\jre\lib\rt.jar]
[Loaded java.lang.String from D:\myeclipse\binary\jdk1.8\jre\lib\rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from D:\myeclipse\binary\jdk1.8\jre\lib\rt.jar]
[Loaded java.lang.reflect.GenericDeclaration from D:\myeclipse\binary\jdk1.8\jre\lib\rt.jar]
[Loaded java.lang.reflect.Type from D:\myeclipse\binary\jdk1.8\jre\lib\rt.jar]
.......
打印类的信息
-XX:+PrintClassHistogram
按下 Ctrl+Break 后,打印类的信息(四个参数分别是:序号、实例数量、总大小、类型
):
num #instances #bytes class name
----------------------------------------------
1: 890617 470266000 [B
2: 890643 21375432 java.util.HashMap$Node
3: 890608 14249728 java.lang.Long
4: 13 8389712 [Ljava.util.HashMap$Node;
5: 2062 371680 [C
6: 463 41904 java.lang.Class
堆的分配参数
指定最大堆和最小堆
指定参数:
-Xmx
-Xms
//下面表示最大堆为20m,最小堆是5m
-Xmx20m
-Xms5m
获取 JVM 最大堆数据:
//获取最大堆大小
System.out.print("Xmx=");
System.out.println(Runtime.getRuntime().maxMemory()/1024.0/1024+"M");
//获取空闲内存大小
System.out.print("free mem=");
System.out.println(Runtime.getRuntime().freeMemory()/1024.0/1024+"M");
//获取总计内存大小
System.out.print("total mem=");
System.out.println(Runtime.getRuntime().totalMemory()/1024.0/1024+"M");
输出结果:
Xmx=1796.0M
free mem=121.04998016357422M
total mem=123.0M
也就是说,我的 JVM 设置最大堆是 1796.0M ,总计 123.0M ,还有 121.04998016357422M 可用。那么设置一下-Xmx20m -Xms5m 参数,再看一下结果:
Xmx=18.0M
free mem=4.761444091796875M
total mem=5.5M
如果分配 1M 的数组,再次执行:
byte[] b = new byte[1*1024*1024];
分配 1M 数组后,空闲空间变少:
Xmx=18.0M
free mem=3.76141357421875M
total mem=5.5M
如果空闲空间全部使用完会怎么样呢?那我们创建一个 5M 的数组,看一下结果:
byte[] b = new byte[5*1024*1024];
输出的结果变成了:
Xmx=18.0M
free mem=5.26141357421875M
total mem=11.0M
这里的最大堆没变,还是 18M,总的内存大小从 5.5M 变成了 11M,也就是说如果空闲空间不能支撑对象所需容量,那么就会扩容。
堆的其他参数
设置新生代大小(绝对参数,设置多少就是多少):
-Xmn
新生代(eden+2*s)和老年代(不包含永久区)的比值:
4 表示 新生代:老年代=1:4,即年轻代占堆的 1/5
-XX:NewRatio
设置两个 Survivor 区和 eden 的比:
8 表示 两个 Survivor :eden=2:8,即一个 Survivor 占年轻代的 1/10
-XX:SurvivorRatio
我们测试一下堆内空间的变化,创建一个 10M 的数组,数组是循环创建:
byte[] b=null;
for(int i=0;i<10;i++)
b=new byte[1*1024*1024];
先看一下新生代分配 1M 空间:
配置参数:-Xmx20m -Xms20m -Xmn1m -XX:+PrintGCDetails
[GC (Allocation Failure) [PSYoungGen: 507K->504K(1024K)] 507K->504K(19968K), 0.0010025 secs] [Times: user=0.05 sys=0.02, real=0.00 secs]
Heap
PSYoungGen total 1024K, used 721K [0x00000000ffe80000, 0x0000000100000000, 0x0000000100000000)
eden space 512K, 42% used [0x00000000ffe80000,0x00000000ffeb6790,0x00000000fff00000)
from space 512K, 98% used [0x00000000fff00000,0x00000000fff7e030,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 18944K, used 10240K [0x00000000fec00000, 0x00000000ffe80000, 0x00000000ffe80000)
object space 18944K, 54% used [0x00000000fec00000,0x00000000ff6000a0,0x00000000ffe80000)
Metaspace used 2633K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 280K, capacity 386K, committed 512K, reserved 1048576K
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.
我用的 JDK1.8,这里还报了 Java HotSpot(TM) 64-Bit Server VM warning,主要是因为新生代的空间不足,不能分配。我们可以看到,新生代空间不足 1M,因此新生代无法分配,就在老年代里面分配了 10M 空间。新生代发生了一次 GC,回收了 3K 空间,可以忽略不计。那么继续增加新生代空间到 15M:
执行参数:-Xmx20m -Xms20m -Xmn15m -XX:+PrintGCDetails
Heap
PSYoungGen total 13824K, used 11525K [0x00000000ff100000, 0x0000000100000000, 0x0000000100000000)
eden space 12288K, 93% used [0x00000000ff100000,0x00000000ffc41760,0x00000000ffd00000)
from space 1536K, 0% used [0x00000000ffe80000,0x00000000ffe80000,0x0000000100000000)
to space 1536K, 0% used [0x00000000ffd00000,0x00000000ffd00000,0x00000000ffe80000)
ParOldGen total 5120K, used 0K [0x00000000fec00000, 0x00000000ff100000, 0x00000000ff100000)
object space 5120K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff100000)
Metaspace used 2633K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 280K, capacity 386K, committed 512K, reserved 1048576K
这里没有发生 GC,新生代使用了 11525K 空间,说明新生代空间足够分配,因此没有触发 GC。那么将新生代空间调整到 7M 会发生什么呢?
执行参数:-Xmx20m -Xms20m -Xmn7m -XX:+PrintGCDetails
[GC (Allocation Failure) [PSYoungGen: 6036K->480K(6656K)] 6036K->1648K(19968K), 0.0012152 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 6656K, used 5782K [0x00000000ff900000, 0x0000000100000000, 0x0000000100000000)
eden space 6144K, 86% used [0x00000000ff900000,0x00000000ffe2d900,0x00000000fff00000)
from space 512K, 93% used [0x00000000fff00000,0x00000000fff78020,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 13312K, used 1168K [0x00000000fec00000, 0x00000000ff900000, 0x00000000ff900000)
object space 13312K, 8% used [0x00000000fec00000,0x00000000fed24020,0x00000000ff900000)
Metaspace used 2633K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 280K, capacity 386K, committed 512K, reserved 1048576K
这里发生了一次新生代 GC,回收了 6036K-1648k= 4388k 空间,剩下的 5782k 在新生代。继续调整,这次调整一下新生代中 eden 和 Survivor 的比例,设置-XX:SurvivorRatio=2,增大了 Survivor 的大小。
执行参数:-Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails
[GC (Allocation Failure) [PSYoungGen: 3922K->1504K(5632K)] 3922K->1624K(18944K), 0.0025991 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 4656K->1504K(5632K)] 4776K->1624K(18944K), 0.0114028 secs] [Times: user=0.08 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 4646K->1504K(5632K)] 4766K->1632K(18944K), 0.0024505 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 5632K, used 2610K [0x00000000ff900000, 0x0000000100000000, 0x0000000100000000)
eden space 4096K, 27% used [0x00000000ff900000,0x00000000ffa14930,0x00000000ffd00000)
from space 1536K, 97% used [0x00000000ffd00000,0x00000000ffe78030,0x00000000ffe80000)
to space 1536K, 0% used [0x00000000ffe80000,0x00000000ffe80000,0x0000000100000000)
ParOldGen total 13312K, used 128K [0x00000000fec00000, 0x00000000ff900000, 0x00000000ff900000)
object space 13312K, 0% used [0x00000000fec00000,0x00000000fec20010,0x00000000ff900000)
Metaspace used 2633K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 280K, capacity 386K, committed 512K, reserved 1048576K
发生了 3 次新生代的 GC,都是新生代的 Minor GC,第一次 GC 回收了 3922K-1624K=2298k,第二次 GC 回收了 4776K-1624K=3152K,第三次 GC 回收了 4766K-1632K=3134k,剩下的 2610k 在新生代中,还有一部分去了 From Survivor 中。再看一下,-XX:NewRatio=1 的情况:
执行参数:-Xmx20m -Xms20m -XX:NewRatio=1 -XX:SurvivorRatio=2 -XX:+PrintGCDetails
[GC (Allocation Failure) [PSYoungGen: 4932K->1688K(7680K)] 4932K->1696K(17920K), 0.0011783 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 5884K->1592K(7680K)] 5892K->1600K(17920K), 0.0009533 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 7680K, used 3834K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 5120K, 43% used [0x00000000ff600000,0x00000000ff830ad8,0x00000000ffb00000)
from space 2560K, 62% used [0x00000000ffd80000,0x00000000fff0e040,0x0000000100000000)
to space 2560K, 0% used [0x00000000ffb00000,0x00000000ffb00000,0x00000000ffd80000)
ParOldGen total 10240K, used 8K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 0% used [0x00000000fec00000,0x00000000fec02000,0x00000000ff600000)
Metaspace used 2633K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 280K, capacity 386K, committed 512K, reserved 1048576K
可以看到,提高新生代空间之后,发生了两次 GC,因为新生代能分配更多的空间,避免 GC,也没有是用老年代的空间。那么再增加 eden 空间会怎么样呢?
执行参数:-Xmx20m -Xms20m -XX:NewRatio=1 -XX:SurvivorRatio=3 -XX:+PrintGCDetails
[GC (Allocation Failure) [PSYoungGen: 6036K->1720K(8192K)] 6036K->1728K(18432K), 0.0059791 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 8192K, used 7022K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 6144K, 86% used [0x00000000ff600000,0x00000000ffb2d900,0x00000000ffc00000)
from space 2048K, 83% used [0x00000000ffc00000,0x00000000ffdae040,0x00000000ffe00000)
to space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
ParOldGen total 10240K, used 8K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 0% used [0x00000000fec00000,0x00000000fec02000,0x00000000ff600000)
Metaspace used 2633K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 280K, capacity 386K, committed 512K, reserved 1048576K
提高 Eden 空间之后,只发生了一次 GC,GC 越少,说明系统执行效率越高。
OOM 内存溢出
-XX:+HeapDumpOnOutOfMemoryError
OOM时导出堆到文件
-XX:+HeapDumpPath
导出OOM的路径
-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump
当发生OOM时,导出到文件a.dump
-XX:OnOutOfMemoryError
在OOM时,执行一个脚本
"-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p"
当程序OOM时,在D:/a.txt中将会生成线程的dump
可以在OOM时,发送邮件,甚至是重启程序
默认配置
新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2
Edem : from : to = 8 : 1 : 1
永久区分配参数
-XX:PermSize -XX:MaxPermSize
设置永久区的初始空间和最大空间
使用 CGLIB 等库的时候,可能会产生大量的类,这些类,有可能撑爆永久区导致 OOM。
栈空间分配
-Xss
通常只有几百 K,决定了函数调用的深度;
每个线程都有独立的栈空间,局部变量、参数 分配在栈上.
因为每个线程都需要分配一个栈空间,因此如果想多跑一些线程,就需要将这个值调小,容纳更多线程。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于