窥探JVM内存分配和回收的过程
一、环境
| JDK | 垃圾收集器 | 是否启用TLAB | 通用JVM参数(堆内存分配见下图) |
|---|---|---|---|
1.6.0_65 |
Serial + Serial Old |
否 | -Xms20m -Xmx20m -Xmn10m -XX:SurvivorRatio=8 |

二、说明
Minor GC
- 发生在
新生代,当Eden区域没有足够空间进行分配- Java对象大多具有
短命的特性- Minor GC
非常频繁,速度也比较快Major GC / Full GC
发生在老年代- 出现Major GC,
经常伴随至少一次Minor GCSpeedOf (Minor GC) ≈ 10 * SpeedOf (Major GC)
三、示例
1. 对象优先分配在Eden区
1.1 说明
- 新对象,优先考虑分配在
Eden区域- 如果Eden区域没有
足够的空间容纳新对象,进行GC
- 如果老年代有
足够的连续空间用来存储所有新生代对象(或历次晋升的平均大小) ⇒Minor GC
- 如果对象太大,以至于
Survivor区域无法容纳,对象直接晋升到老年代- 否则使用复制算法,
复制到Survivor区域- 否则 ⇒ 先进行一次
Minor GC,若仍不满足上述条件,进行Full GC
- 若
Full GC后依然内存不足,
1.2 代码
# 代码
public class TestAllocation {
private static final int _1MB = 1024 * 1024;
// JVM Args:
// -XX:+PrintGCDetails
// -XX:+UseSerialGC
// -Xms20m -Xmx20m
// -Xmn10m -XX:SurvivorRatio=8
public static void main(String[] args) throws InterruptedException {
System.out.println("===1. start full gc start===");
System.gc();
System.out.println("===1. start full gc end===\n");
System.out.println("===2. gc logs===");
byte[] a1 = new byte[_1MB / 4];
byte[] a2 = new byte[4 * _1MB];
byte[] a3 = new byte[4 * _1MB];// 一次Minor GC
byte[] a4 = new byte[4 * _1MB];// 一次Minor GC
byte[] a5 = new byte[4 * _1MB];// 一次Minor GC + 一次Full GC ⇒ OOM
}
}
1.3 运行结果
# 仅保留关键信息
===1. start full gc start=== [Full GC (System) [Tenured: 0K->426K(10240K)] 1974K->426K(19456K) ===1. start full gc end=== ===2. gc logs=== [GC [DefNew: 4843K->256K(9216K)] 5270K->4778K(19456K)] [GC [DefNew: 4352K->256K(9216K)] 8874K->8874K(19456K)] [GC [DefNew: 4436K->4436K(9216K)][Tenured: 8618K->8618K(10240K)] 13055K->12970K(19456K) [Full GC [Tenured: 8618K->8549K(10240K)] 12970K->12902K(19456K) Exception in thread "main" java.lang.OutOfMemoryError: Java heap space Heap def new generation total 9216K, used 4657K eden space 8192K, 56% used from space 1024K, 0% used to space 1024K, 0% used tenured generation total 10240K, used 8549K the space 10240K, 83% used
1.4 运行示意图

1.5 运行过程解析
- System.gc() ⇒
Full GC
新生代已分配内存:0K老年代已分配内存:426K- byte[] a1 = new byte[_1MB / 4];
- Eden空间充足,为
a1分配256K内存- byte[] a2 = new byte[4 * _1MB];
- Eden空间充足,为
a2分配4096K内存- byte[] a3 = new byte[4 * _1MB];
- Eden空间不足,不能为a3分配4096K内存,需要进行GC
- 4.1 判定是否需要Full GC
- 老年代
未分配的连续空间:10240-427 =9813K- 所有的新生代对象:sizeof(a1)+size(a2)=256+4096 =
4352K9813≫4352⇒Minor GC- 4.2 进行Minor GC
- 1024 > 256 ⇒ remainingSpaceOf(Survivor) > sizeof(a1) ⇒
a1复制到to(Survivor区)- 4096 > 1024 ⇒ sizeof(a2) > sizeof(Survivor) ⇒
a2晋升到Tenured区- 4.3
Minor GC之后,Eden空间充足,为a3分配4096K内存
- 新生代 – 4352K
- Eden:4096K(
a3)- from(Survivor):256K(
a1)- to(Survivor):0K
- 老年代 – 4522K
- Tenured:426K + 4096K(
a2)- byte[] a4 = new byte[4 * _1MB];
- Eden空间不足,不能为a4分配4096K内存,需要进行GC
- 5.1 判定是否需要Full GC
- 老年代
未分配的连续空间:10240-427-4096 =5717K- 所有的新生代对象:sizeof(a1)+size(a2)=256+4096 =
4352K5717≫4352⇒Minor GC- 5.2 进行Minor GC
- 第二次GC ⇒
a1由from复制到to- 4096 > 1024 ⇒ sizeof(a3) > sizeof(Survivor) ⇒
a3晋升到Tenured区- 5.3
Minor GC之后,Eden空间充足,为a4分配4096K内存
- 新生代 – 4436K
- Eden:84K + 4096K(
a4)- from(Survivor):0K
- to(Survivor):256K(
a1)- 老年代 – 8618K
- Tenured:426K + 4096K(
a2) + 4096K(a3)- byte[] a5 = new byte[4 * _1MB];
- Eden空间不足,不能为a5分配4096K内存,需要进行GC
- 6.1 判定是否需要Full GC
- 老年代
未分配的连续空间:10240-427-4096-4096 =1621K- 所有的新生代对象:sizeof(a1)+size(a2)=256+4096 =
4352K1621≪4352⇒Minor GC+Full GC- 6.2 进行Minor GC
- 第三次GC ⇒
a1由to复制到from- 4096 > 1621 ⇒ sizeof(a4) > remainingSpaceOf(Tenured) ⇒
a4无法晋升到Tenured区- 6.3
Minor GC之后,堆空间内存布局没有变化,Eden空间依旧不足,进行Full GC
- 老年代减少了69K,忽略不计,依然保留着
a2和a3- 新生代增加了17k,忽略不计,依然保留着
a1和a4- 6.4
Full GC之后,新生代和老年代内存都不足以为a5分配内存,抛出OOM异常
2. 大对象直接进入老年代
2.1 说明
- 大对象:需要
大量连续内存空间的Java对象,如长字符串和大数组
- 代码中尽量避免使用
短命大对象-XX:PretenureSizeThreshold=?(Byte)
- 新对象的大小大于PretenureSizeThreshold,直接分配在
老年代- 作用
- 新生代采用的是复制算法,
避免Eden和Survivor之间的内存复制降低GC频率
2.2 代码
public class TestPretenureSizeThreshold {
private static final int _1MB = 1024 * 1024;
// JVM Args
// -XX:+PrintGCDetails
// -XX:+UseSerialGC
// -Xms20m -Xmx20m
// -Xmn10m -XX:SurvivorRatio=8
// -XX:PretenureSizeThreshold=3145728
public static void main(String[] args) {
byte[] a1 = new byte[4 * _1MB]; // 直接分配到tenured
byte[] a2 = new byte[4 * _1MB]; // 直接分配到tenured,无需GC
}
}
2.3 运行结果
# 仅保留关键信息 Heap def new generation total 9216K, used 2302K eden space 8192K, 28% used from space 1024K, 0% used to space 1024K, 0% used tenured generation total 10240K, used 8192K the space 10240K, 80% used
2.4 运行过程解析
- sizeof(a1) > PretenureSizeThreshold ⇒ 直接分配在
Tenured- sizeof(a2) > PretenureSizeThreshold ⇒ 直接分配在
Tenured
- 如果不设置PretenureSizeThreshold,在分配a2前会产生一次
Minor GC,最终a1在Tenured,a2在Eden
3. 长期存活的对象晋升到老年代
3.1 说明
- 对象年龄:对象每经历一次
Minor GC,对象年龄加1- JVM参数:
-XX:MaxTenuringThreshold=?,默认是15
3.2 代码
public class TestTenuringThreshold {
private static final int _1MB = 1024 * 1024;
private static final int MAX_OBJ_AGE = 2;
// JVM Args
// -XX:+PrintGCDetails
// -XX:+UseSerialGC
// -Xms20m -Xmx20m
// -Xmn10m -XX:SurvivorRatio=8
// -XX:MaxTenuringThreshold=3
public static void main(String[] args) {
System.out.println("===1. start full gc start===");
System.gc();
System.out.println("===1. start full gc end===\n");
System.out.println("===2. gc logs===");
byte[] a1 = new byte[_1MB / 4];
byte[] a2 = new byte[4 * _1MB];
byte[] a3 = null;
for (int i = 0; i < MAX_OBJ_AGE; ++i) {
// 执行一次, a1的年龄加1 , 最终a1的年龄为MAX_OBJ_AGE
// 如果 MAX_OBJ_AGE > MaxTenuringThreshold ⇒ a1 晋升到 Tenured
// 如果 MAX_OBJ_AGE ≦ MaxTenuringThreshold ⇒ a1 停留在 Survivor
a3 = null;
a3 = new byte[4 * _1MB];
}
}
}
3.3 运行结果
# 仅保留关键信息 # MAX_OBJ_AGE = 3时 ===1. start full gc start=== [Full GC (System) [Tenured: 0K->430K(10240K)] 1974K->430K(19456K) ===1. start full gc end=== ===2. gc logs=== [GC [DefNew: 4679K->256K(9216K)] 5110K->4783K(19456K)] [GC [DefNew: 4352K->256K(9216K)] 8879K->4783K(19456K)] [GC [DefNew: 4352K->256K(9216K)] 8879K->4783K(19456K)] Heap def new generation total 9216K, used 4516K eden space 8192K, 52% used from space 1024K, 25% used to space 1024K, 0% used tenured generation total 10240K, used 4526K the space 10240K, 44% used # MAX_OBJ_AGE = 4时 ===1. start full gc start=== [Full GC (System) [Tenured: 0K->426K(10240K)] 1974K->426K(19456K) ===1. start full gc end=== ===2. gc logs=== [GC [DefNew: 4843K->256K(9216K)] 5270K->4779K(19456K)] [GC [DefNew: 4437K->256K(9216K)] 8960K->4779K(19456K)] [GC [DefNew: 4409K->257K(9216K)] 8931K->4780K(19456K)] [GC [DefNew: 4391K->0K(9216K)] 8913K->4780K(19456K)] Heap def new generation total 9216K, used 4449K eden space 8192K, 54% used from space 1024K, 0% used to space 1024K, 0% used tenured generation total 10240K, used 4779K the space 10240K, 46% used
3.4 运行过程解析
a1和a2现在Eden分配内存- 第一次为
a3分配内存时,必须进行Minor GC,a2被晋升Tenured,a3分配在Eden,a1被复制到from(Survivor),此时a1的对象年龄为1
- 随后每次循环中,先将
a3置为null,a3原先引用的内存变成了垃圾,后续可回收- 再次为
a3分配内存,此时Eden区内存不足,进行Minor GC
- 目前存活的对象仅仅是
a1,将其复制到to(另一块Survivor),a1的对象年龄加1- 将
from和Eden区清空,并在Eden为a3分配内存- MAX_OBJ_AGE = 3
≦MaxTenuringThreshold时
- from space 1024K,
25%used ⇒a1依旧停留在Survivor- MAX_OBJ_AGE = 4
>MaxTenuringThreshold时
- [GC [DefNew:
4391K->0K(9216K)] 8913K->4780K(19456K)] ⇒a1晋升到Tenured- from space 1024K,
0%used ⇒Survivor已经无存活对象
4. 动态对象年龄判定
4.1 说明
- 在Survivor中
相同年龄(对象年龄 ≧2)的所有对象大小之和≧0.5 * sizeof(Survivor)⇒大于或等于该年龄的对象直接晋升到老年代,无须考虑MaxTenuringThreshold
4.2 代码一(单个1/2对象)
public class TestDynamicObjectAge {
private static final int _1MB = 1024 * 1024;
private static final int MAX_OBJ_AGE = 1;
// JVM Args
// -XX:+PrintGCDetails
// -XX:+UseSerialGC
// -Xms20m -Xmx20m
// -Xmn10m -XX:SurvivorRatio=8
// -XX:MaxTenuringThreshold=3
public static void main(String[] args) {
int al;
System.out.println("===1. start full gc start===");
System.gc();
System.out.println("===1. start full gc end===\n");
System.out.println("===2. gc logs===");
byte[] a1 = new byte[_1MB / 2]; # 单个1/2对象
byte[] a2 = new byte[4 * _1MB];
byte[] a3 = null;
for (int i = 0; i < MAX_OBJ_AGE; ++i) {
// 相同年龄(对象年龄 ≧ 2)的所有对象大小之和 ≧ 0.5 * sizeof(Survivor)
// ⇒ 大于或等于该年龄的对象直接晋升到老年代,无需考虑MaxTenuringThreshold
a3 = null;
a3 = new byte[4 * _1MB];
}
}
}
4.3 代码一运行结果
# 仅保留关键信息 # MAX_OBJ_AGE = 1时 ===1. start full gc start=== [Full GC (System) [Tenured: 0K->429K(10240K)] 1974K->429K(19456K) ===1. start full gc end=== ===2. gc logs=== [GC [DefNew: 5099K->513K(9216K)] 5529K->5039K(19456K)] Heap def new generation total 9216K, used 4773K eden space 8192K, 52% used from space 1024K, 50% used # a1的对象年龄为1,依旧停留在Survivor区域 to space 1024K, 0% used tenured generation total 10240K, used 4525K the space 10240K, 44% used # MAX_OBJ_AGE = 2时 ===1. start full gc start=== [Full GC (System) [Tenured: 0K->426K(10240K)] 1974K->426K(19456K) ===1. start full gc end=== ===2. gc logs=== [GC [DefNew: 5099K->513K(9216K)] 5526K->5036K(19456K)] [GC [DefNew: 4694K->1K(9216K)] 9216K->5037K(19456K)] # a1的对象年龄为2 < MaxTenuringThreshold,且a2等于0.5 * sizeof(Survivor),直接晋升到老年代 Heap def new generation total 9216K, used 4317K eden space 8192K, 52% used from space 1024K, 0% used # a1的对象年龄为2 < MaxTenuringThreshold,且a2等于0.5 * sizeof(Survivor),直接晋升到老年代 to space 1024K, 0% used tenured generation total 10240K, used 5036K the space 10240K, 49% used
4.4 代码二(四个1/8对象)
public class TestDynamicObjectAge {
private static final int _1MB = 1024 * 1024;
private static final int MAX_OBJ_AGE = 1;
// JVM Args
// -XX:+PrintGCDetails
// -XX:+UseSerialGC
// -Xms20m -Xmx20m
// -Xmn10m -XX:SurvivorRatio=8
// -XX:MaxTenuringThreshold=3
public static void main(String[] args) {
int al;
System.out.println("===1. start full gc start===");
System.gc();
System.out.println("===1. start full gc end===\n");
System.out.println("===2. gc logs===");
byte[] a1_1 = new byte[_1MB / 8]; # 四个1/8对象
byte[] a1_2 = new byte[_1MB / 8];
byte[] a1_3 = new byte[_1MB / 8];
byte[] a1_4 = new byte[_1MB / 8];
byte[] a2 = new byte[4 * _1MB];
byte[] a3 = null;
for (int i = 0; i < MAX_OBJ_AGE; ++i) {
// 相同年龄(对象年龄 ≧ 2)的所有对象大小之和 ≧ 0.5 * sizeof(Survivor)
// ⇒ 大于或等于该年龄的对象直接晋升到老年代,无需考虑MaxTenuringThreshold
a3 = null;
a3 = new byte[4 * _1MB];
}
}
}
4.5 代码二运行结果
# 仅保留关键信息 # MAX_OBJ_AGE = 1时 ===1. start full gc start=== [Full GC (System) [Tenured: 0K->427K(10240K)] 1974K->427K(19456K) ===1. start full gc end=== ===2. gc logs=== [GC [DefNew: 4971K->514K(9216K)] 5399K->5038K(19456K)] Heap def new generation total 9216K, used 4859K eden space 8192K, 53% used # a1_1、a1_2、a1_3、a1_4的对象年龄为1,依旧停留在Survivor区域 from space 1024K, 50% used to space 1024K, 0% used tenured generation total 10240K, used 4523K the space 10240K, 44% used # MAX_OBJ_AGE = 2时 ===1. start full gc start=== [Full GC (System) [Tenured: 0K->427K(10240K)] 1974K->427K(19456K) ===1. start full gc end=== ===2. gc logs=== [GC [DefNew: 4971K->514K(9216K)] 5399K->5037K(19456K)] [GC [DefNew: 4694K->0K(9216K)] 9218K->5037K(19456K)] # a1_1、a1_2、a1_3和a1_4的对象年龄为2 < MaxTenuringThreshold,且它们之和等于0.5 * sizeof(Survivor),晋升到老年代 Heap def new generation total 9216K, used 4316K eden space 8192K, 52% used from space 1024K, 0% used # a1_1、a1_2、a1_3和a1_4的对象年龄为2 < MaxTenuringThreshold,且它们之和等于0.5 * sizeof(Survivor),晋升到老年代 to space 1024K, 0% used tenured generation total 10240K, used 5037K the space 10240K, 49% used compacting perm gen total 21248K, used 4965K the space 21248K, 23% used
三、参考资料
窥探JVM内存分配和回收的过程的更多相关文章
- 最简单例子图解JVM内存分配和回收
一.简介 JVM采用分代垃圾回收.在JVM的内存空间中把堆空间分为年老代和年轻代.将大量(据说是90%以上)创建了没多久就会消亡的对象存储在年轻代,而年老代中存放生命周期长久的实例对象.年轻代中又被分 ...
- 最简单例子图解JVM内存分配和回收(转)
本文转自http://ifeve.com/a-simple-example-demo-jvm-allocation-and-gc/ http://www.idouba.net/a-simple-exa ...
- 图解JVM内存分配和回收
一.简介 JVM采用分代垃圾回收.在JVM的内存空间中把堆空间分为年老代和年轻代.将大量(据说是90%以上)创建了没多久就会消亡的对象存储在年轻代,而年老代中存放生命周期长久的实例对象.年轻代中又被分 ...
- JVM内存分配与回收
1.内存分配与回收策略 内存自动管理:自动化的解决了对象内存分配和回收对象内存的问题. 一般在堆上分配对象,也可能经过JTI编译后间接在栈上分配. 主要分配在新生代的Eden区,如果启动了本地线程分配 ...
- JVM内存分配和回收
本文内容来自<Java编程思想(第四版)>第二章<一切都是对象>和第五章<初始化与清理>.作为一个使用了好几年的Javaer,再次看编程思想的前面章节(不要问我为什 ...
- jvm内存分配和回收策略
在上一篇中,已经介绍了内存结构是什么样的. 这篇来介绍一下 内存是怎么分配的,和怎么回收的.(基本取自<深入理解Java虚拟机>一书) java技术体系中所提倡的自动内存管理最终可以归结为 ...
- JVM 内存分配和回收策略
对象的内存分配,主要是在java堆上分配(有可能经过JIT编译后被拆为标量类型并间接地在栈上分配),如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配.少数情况下也是直接分配到老年代,分配规则不 ...
- A4. JVM 内存分配及回收策略
[概述] Java 技术体系中所提倡的自动内存管理最终可以归结为自动化地解决两个问题:给对象分配内存以及回收分配给对象的内存. 对象的内存分配,往大方向讲,就是在堆上分配,对象主要分配在新生代的 Ed ...
- JVM——内存分配与回收策略
1.对象优先在Eden区分配 大多数情况下,对象在新生代Eden区分配.当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC. 虚拟机提供了 -XX:+PrintGCDetails这 ...
随机推荐
- YUV和RGB格式分析
做嵌入式项目的时候,涉及到YUV视频格式到RGB图像的转换,虽然之前有接触到RGB到都是基于opencv的处理,很多东西并不需要我们过多深入的去探讨,现在需要完全抛弃现有的算法程序,需要从内存中一个字 ...
- 深入理解Java虚拟机
1. Java内存模型,Java内存管理,Java堆和栈,垃圾回收 http://nibnait.com/6f8dd084-about-Java-Virtual-Machine/ 2. JVM性能调优 ...
- chrome控制台小技巧
对于大多数开发人员来说,chrome控制台最常用的命令就是 console.log()了,然后还有一些其他类似的命令,如: console.info() 提示信息 console.error() ...
- git中手动删除的文件如何在git中删除
在日常开发中,我们可能或手动删除(delete键删除的)一些文件,然而我们本来应该是用git rm fileName命令删除的,但是现在我们手动删除了,那么要如何在git里面讲那些手动删除的文件删除呢 ...
- Toad创建DBLINKsop
Toad创建DBLINKsop 1.创建服务: 点击“测试”,出现如下测试窗口后点击更改登录,用户名和密码数据目标主机用户名.密码; 出现如下窗口后,点击“关闭”,然后点击“完成”即可; 2.创建db ...
- bzoj2823
最小圆覆盖 有个东西叫作随机增量法,具体可以baidu 这里来说说怎么求三点共圆 这其实就是求两条线段的交点 在编程中,我们解方程是比较麻烦的一个比较好的方法是利用相似三角形 设线段AB,CD交P,则 ...
- HDU 3467 (求五个圆相交面积) Song of the Siren
还没开始写题解我就已经内牛满面了,从晚饭搞到现在,WA得我都快哭了呢 题意: 在DotA中,你现在1V5,但是你的英雄有一个半径为r的眩晕技能,已知敌方五个英雄的坐标,问能否将该技能投放到一个合适的位 ...
- Linux“Bash”漏洞大爆发
9月25日,国外曝出一个“毁灭级”的Bash漏洞,黑客可利用此漏洞远程执行任意命令,完全控制目标系统! 该漏洞编号为CVE-2014-6271,主要存在于bash 1.14 - 4.3版本中,受影响的 ...
- BZOJ2299: [HAOI2011]向量
题目:http://www.lydsy.com/JudgeOnline/problem.php?id=2299 题解:乱搞就可以了... 不妨认为有用的只有(a,b)(a,-b)(b,a)(b,-a) ...
- PNG文件结构分析 ---Png解析
PNG文件结构分析 ---Png解析 为了实现更高级的应用,我们必须充分挖掘PNG的潜力. PNG的文件结构 根据PNG文件的定义来说,其文件头位置总是由位固定的字节来描述的: 十进制数 13 ...