概述
JVM 的自动内存管理主要体现在两个方面:自动给对象分配内存、自动回收分配给对象的内存;
文章目录
- 1. 对象优先在 Eden 分配
- 2. 大对象直接进入老年代
- 3. 长期存活的对象将进入老年代
- 4. 动态对象年龄判定
- 5. 空间分配担保
1. 对象优先在 Eden 分配
VM Arguments 设置
# -verbose:gc 控制台打印 GC 情况
# -Xms20M 设置最小堆内存为 20MB
# -Xmx20M 设置最大堆内存为 20MB
# -Xmn10M 设置新生代容量为 10MB
# -XX:+PrintGCDetails 控制台打印 GC 详细情况,并在程序退出时打印 Heap 分配情况;
# -XX:SurvivorRatio=8 设置 Eden:Survivor = 8:1
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
代码示例
private static final int _1MB = 1024 * 1024;
private static void testEdenAllocation() {
byte[] allocate1 = new byte[2 * _1MB];
byte[] allocate2 = new byte[2 * _1MB];
byte[] allocate3 = new byte[2 * _1MB];
// 出现一次 Minor GC
byte[] allocate4 = new byte[3 * _1MB];
}
运行结果
[GC (Allocation Failure) [PSYoungGen: 6711K->770K(9216K)] 6711K->4866K(19456K), 0.0033385 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen
total 9216K, used 6212K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 66% used [0x00000007bf600000,0x00000007bfb507c8,0x00000007bfe00000)
from space 1024K, 75% used [0x00000007bfe00000,0x00000007bfec0890,0x00000007bff00000)
to
space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
ParOldGen
total 10240K, used 4096K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 40% used [0x00000007bec00000,0x00000007bf000020,0x00000007bf600000)
Metaspace
used 3284K, capacity 4496K, committed 4864K, reserved 1056768K
class space
used 355K, capacity 388K, committed 512K, reserved 1048576K
默认使用了 Parallel Scavenge + Parallel Old 组合 GC;
给 allocate4 分配内存时发生了 Minor GC,使得新生代从 6711K 变为 770K;
Minor GC 时,因为 Survivor 区无法容纳任意 allocate 对象,通过分配担保被直接移动到老年代;
Minor GC 结束后,新分配的对象 allocate4 还是被分配到 Eden 区;
2. 大对象直接进入老年代
大对象指需要大量连续内存的 Java 对象,这无疑是糟糕的场景,更糟糕的是一群短命的大对象
;
大对象容易提前出发 GC,复制起来开销也很高,可以通过设置 -XX:PretenureSizeThreshold
让大于该值的对象直接进入老年代,从而缓解问题;
VM Arguments 设置
# -XX:+UseParNewGC 强制使用 ParNew + Serial Old 组合 GC
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC
代码示例
private static final int _1MB = 1024 * 1024;
private static void testPretenureSizeThreshold() {
// 直接分配在老年代中
byte[] allocation = new byte[4 * _1MB];
}
运行结果
OpenJDK 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release
Heap
par new generation
total 9216K, used 2779K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K,
33% used [0x00000007bec00000, 0x00000007beeb6d08, 0x00000007bf400000)
from space 1024K,
0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
to
space 1024K,
0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
tenured generation
total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
the space 10240K,
40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)
Metaspace
used 3286K, capacity 4496K, committed 4864K, reserved 1056768K
class space
used 355K, capacity 388K, committed 512K, reserved 1048576K
4MB 的 allocation 对象大于 -XX:PretenureSizeThreshold
设置的 3MB,直接进入老年代;
3. 长期存活的对象将进入老年代
对象在 Eden 产生,GC 后被移至 Survivor,并在 Survivor 来回移动;每经过一次 Minor GC,年龄增加 1,直到年龄达到一定层度(默认 15),则从 Survivor 进入到老年代;
-XX:MaxTenuringThreshold
,设置年龄阈值;
VM Arguments 设置
# -XX:MaxTenuringThreshold=1 设置年龄阈值为 1
# -XX:+PrintTenuringDistribution 打印老年代内存分布
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution -XX:+UseParNewGC
代码演示
private static void testTenuringThreshold() {
byte[] allocation1 = new byte[_1MB / 4];
byte[] allocation2 = new byte[4 * _1MB];
byte[] allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
运行结果
[GC (Allocation Failure) [ParNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age
1:
823288 bytes,
823288 total
: 6967K->840K(9216K), 0.0069539 secs] 6967K->4936K(19456K), 0.0072200 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
[GC (Allocation Failure) [ParNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age
1:
21120 bytes,
21120 total
: 5020K->94K(9216K), 0.0034186 secs] 9116K->4904K(19456K), 0.0034656 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation
total 9216K, used 4484K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K,
53% used [0x00000007bec00000, 0x00000007bf049538, 0x00000007bf400000)
from space 1024K,
9% used [0x00000007bf400000, 0x00000007bf417b60, 0x00000007bf500000)
to
space 1024K,
0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
tenured generation
total 10240K, used 4809K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
the space 10240K,
46% used [0x00000007bf600000, 0x00000007bfab2510, 0x00000007bfab2600, 0x00000007c0000000)
Metaspace
used 3309K, capacity 4496K, committed 4864K, reserved 1056768K
class space
used 359K, capacity 388K, committed 512K, reserved 1048576K
从第二次 GC 后新生代整个变为 0K 可见,当 -XX:MaxTenuringThreshold=1 时,GC 会将 Survivor 中年龄大于 1 的对象移至老年代;
4. 动态对象年龄判定
如果 Survivor 中相同年龄的对象之和占 Survivor 的一半以上,则大于或等于该年龄的对象可以直接进入老年代,无需等到 -XX:MaxTenuringThreshold
设置的年龄;
VM Arguments 设置
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution -XX:+UseParNewGC
代码演示
private static void testTenuringThreshold2() {
// allocation1 + allocation2 大于 survivor 空间一半
byte[] allocation1 = new byte[_1MB / 4];
byte[] allocation2 = new byte[_1MB / 4];
byte[] allocation3 = new byte[4 * _1MB];
byte[] allocation4 = new byte[4 * _1MB];
allocation4 = null;
allocation4 = new byte[4 * _1MB];
}
运行结果
[GC (Allocation Failure) [ParNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age
1:
1005568 bytes,
1005568 total
: 7223K->1023K(9216K), 0.0031961 secs] 7223K->5193K(19456K), 0.0033495 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [ParNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
: 5203K->0K(9216K), 0.0007971 secs] 9373K->5162K(19456K), 0.0008187 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
Heap
par new generation
total 9216K, used 4389K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K,
53% used [0x00000007bec00000, 0x00000007bf0494e0, 0x00000007bf400000)
from space 1024K,
0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
to
space 1024K,
0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
tenured generation
total 10240K, used 5162K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
the space 10240K,
50% used [0x00000007bf600000, 0x00000007bfb0a8d8, 0x00000007bfb0aa00, 0x00000007c0000000)
Metaspace
used 3282K, capacity 4496K, committed 4864K, reserved 1056768K
class space
used 355K, capacity 388K, committed 512K, reserved 1048576K
在 -XX:MaxTenuringThreshold=15
情况下,Survivor 结果为 0%,因为 allocation1 和 allocation2 之和大于 survivor 空间的一半,GC 发生时直接进入老年代;
5. 空间分配担保
Minor GC 之前,JVM 会先检查老年代最大的可用连续空间,看其是否大于新生代所有对象总和;若不大于,则 Minor GC 会存在风险,这时根据 -XX:HandlePromotionFailure
(是否允许担保失败)决定是否继续判断老年代最大可用连续空间是否大于历次晋升老年代对象的平均大小;若不大于,或直接不允许冒险,则直接转入 Full GC;反之才会进行 Minor GC;
在 JDK 6 Update 24 之后,只要老年代的连续空间大于新生代对象之和,或大于历次晋升对象的平均大小,就会进行 Minor GC,否则转入 Full GC;
VM Arguments 设置
# -XX:-HandlePromotionFailure 是否允许担保失败
-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure
代码演示
private static void testHandlePromotion() {
byte[] allocation1 = new byte[2 * _1MB];
byte[] allocation2 = new byte[2 * _1MB];
byte[] allocation3 = new byte[2 * _1MB];
allocation1 = null;
byte[] allocation4 = new byte[2 * _1MB];
byte[] allocation5 = new byte[2 * _1MB];
byte[] allocation6 = new byte[2 * _1MB];
allocation4 = null;
allocation5 = null;
allocation6 = null;
byte[] allocation7 = new byte[2 * _1MB];
}
运行结果
// 在 JDK 6 Update 24 之前的版本运行
// -XX:-HandlePromotionFailure
[GC [DefNew: 6651K->148K(9216K), 0.0078936 secs] 6651K->4244K(19456K), 0.0079192 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]
[GC [DefNew: 6378K->6378K(9216K), 0.0000206 secs][Tenured: 4096K->4244K(10240K), 0.0042901 secs] 10474K->4244K(19456K), [Perm : 2104K->2104K(12288K)], 0.0043613 secs] [Times: user=0.00 sys=0.00 real=0.00 secs]
// -XX:+HandlePromotionFailure
[GC [DefNew: 6651K->148K(9216K), 0.0054913 secs] 6651K->4244K(19456K), 0.0055327 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6378K->148K(9216K), 0.0006584 secs] 10474K->4244K(19456K), 0.0006857 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
在 JDK 6 Update 24 之后的版本只会看到 -XX:+HandlePromotionFailure
时的情况;
上一篇:「JVM 内存管理」GC 评估与选择
下一篇:「JVM 故障诊断」命令行工具
PS:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!
参考资料:
- [1]《深入理解 Java 虚拟机》
最后
以上就是震动夕阳为你收集整理的「JVM 内存管理」内存分配与回收策略的全部内容,希望文章能够帮你解决「JVM 内存管理」内存分配与回收策略所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复