深入理解_JVM内存管理内存分配和回收策略06

解决两个问题:

1、对象分配内存;

2、回收分配给对象的内存。

本节详细讲解分配的问题:

名词解释:

新生代GC(Minor GC):指发生在新生代的垃圾回收动作,非常频繁,回收速度很快。

老生代GC(Major GC/Full GC):指发生在老生代的垃圾回收动作,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对),速度一般会比Minor GC慢10倍。

打印日志说明:

<1> DefNew:串行GC方式(Serial GC)。

<2> ParNew:ParNew方式。

<3> PSYoungGen:Parallel Scavenge方式。

大原则:

(1)对象优先分配在新生代的Eden上;

(2)大对象直接进入老生代;

(3)长期存活对象将进入老生代;

(4)动态对象年龄判断;

(5)空间分配担保。

实例演示:

1、对象优先分配在新生代的Eden上,通过-XX:+PrintGCDetails收集器日志参数,打印内存回收日志。

/*

* 参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

*/

public class testAllocation {

private static final int _1MB=1024*1024;

public static void main(String[] args) {

byte[] allocation1, allocation2,allocation3 ,allocation4;

allocation1 = new byte [2*_1MB];

allocation2 = new byte [2*_1MB];

allocation3 = new byte [2*_1MB];

allocation4 = new byte [2*_1MB]; //出现一次Minor GC

}

}

运行结果:

[GC [DefNew: 6808K->434K(9216K), 0.0062476 secs] 6808K->6578K(19456K), 0.0062850 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]

Heap

def new generation   total 9216K, used 4857K [0x04770000, 0x05170000, 0x05170000)

eden space 8192K,  54% used [0x04770000, 0x04bc1fa8, 0x04f70000)

from space 1024K,  42% used [0x05070000, 0x050dc810, 0x05170000)

to   space 1024K,   0% used [0x04f70000, 0x04f70000, 0x05070000)

tenured generation   total 10240K, used 6144K [0x05170000, 0x05b70000, 0x05b70000)

the space 10240K,  60% used [0x05170000, 0x05770030, 0x05770200, 0x05b70000)

compacting perm gen  total 12288K, used 1589K [0x05b70000, 0x06770000, 0x09b70000)

the space 12288K,  12% used [0x05b70000, 0x05cfd758, 0x05cfd800, 0x06770000)

运行结果说明:

(1)[Times: user=0.02 sys=0.00, real=0.01 secs]

表示Minor GC占用的CPU user和sys的百分比,以及消耗的共时间。

(2)[GC [DefNew: 6808K->434K(9216K), 0.0062476 secs]

DefNew:新生代回收前:6808K,新生代回收后:434K 新生代可用空间为:Eden+Survivor。

Heap总量回收前:6808K,Heap总量回收后:6578K,Heap可用空间:19456K(Eden+Survivor+老年代)

(3)详解分解:

<1> 发现新生代虽然变化很大,但是Heap却没有太大变化。

<2> 程序给a1,a2,a3分配完空间之后,Eden只剩下2MB,而a4有4MB,不够,所以发生一次Minor GC。

<3> Minor GC之后,a1,a2,a3进入老生代,因为to(Survivor)无法装下他们中的任何一个。此时,tenured generation:the space 10240K,  60%。

<4> a4便进入了了Eden:eden space 8192K,  54% used。

2、变更一下实例1。

/*

* 参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

*/

public class testAllocation_B {

private static final int _1MB=1024*1024;

public static void main(String[] args) {

byte[] allocation1, allocation2,allocation3 ,allocation4;

allocation1 = new byte [4*_1MB];

allocation2 = new byte [(int)0.8*_1MB];

allocation3 = new byte [(int)0.8*_1MB];

allocation4 = new byte [(int)0.8*_1MB];

}

}

Heap

def new generation   total 9216K, used 5088K [0x04800000, 0x05200000, 0x05200000)

eden space 8192K,  62% used [0x04800000, 0x04cf8100, 0x05000000)

from space 1024K,   0% used [0x05000000, 0x05000000, 0x05100000)

to   space 1024K,   0% used [0x05100000, 0x05100000, 0x05200000)

tenured generation   total 10240K, used 0K [0x05200000, 0x05c00000, 0x05c00000)

the space 10240K,   0% used [0x05200000, 0x05200000, 0x05200200, 0x05c00000)

compacting perm gen  total 12288K, used 1616K [0x05c00000, 0x06800000, 0x09c00000)

the space 12288K,  13% used [0x05c00000, 0x05d94390, 0x05d94400, 0x06800000)

No shared spaces configured.

3、大对象直接进入老生代。

多大的对象算大对象?通过参数-XX:PretenureSizeThreshold来控制,出现大对象容易导致内存还有不少空间时就触发GC来寻求连续的空间来安置他们。

注意:

<1> 该参数不能写成-Xms 10M这样,需要精确到B,如下例子。

<2> 该参数只对Serial和ParNew两款收集器有效,Parallel Scavenge收集器不认识该参数。如非要设置该参数,考虑ParNew+CMS的组合。

例子说明:

增加-XX:PretenureSizeThreshold=3145728参数,控制大于3MB的对象直接进入老生代。

/**

* -verbose:gc - Xms20M -Xmx20M -Xmn10M - XX:+PrintGCDetails

* -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728

*/

public class testPretenureSizeThreshold {

private static final int _1MB = 1024*1024;

public static void main(String[] args) {

byte[]a;

a = new byte [4*_1MB];

}

}

Heap

def new generation   total 9216K, used 991K [0x047d0000, 0x051d0000, 0x051d0000)

eden space 8192K,  12% used [0x047d0000, 0x048c7f00, 0x04fd0000)

from space 1024K,   0% used [0x04fd0000, 0x04fd0000, 0x050d0000)

to   space 1024K,   0% used [0x050d0000, 0x050d0000, 0x051d0000)

tenured generation   total 10240K, used 4096K [0x051d0000, 0x05bd0000, 0x05bd0000)

the space 10240K,  40% used [0x051d0000, 0x055d0010, 0x055d0200, 0x05bd0000)

compacting perm gen  total 12288K, used 1616K [0x05bd0000, 0x067d0000, 0x09bd0000)

the space 12288K,  13% used [0x05bd0000, 0x05d64348, 0x05d64400, 0x067d0000)

No shared spaces configured.

运行结果说明:

<1> 新生代基本没有被使用。

<2> 老生代直接占用百分之四十,说明对象直接进入老生代。

4、长期存活的对象将进入老年代。

问题:为了保证垃圾回收时能识别哪些对象分别放在什么代上?

解决办法:虚拟机给每个对象定义了一个对象年龄(Age)计数器。

具体算法:

<1> 如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设置为1。

<2> 对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁)收,就会被晋升到老年代中。

<3> 对象晋升到老年代的年龄阀值,可以通过参数:-XX:MaxTenuringThreshold来设置。

例子说明:

将-XX:MaxTenuringThreshold分别设置为1和15,来验证是否到-XX:MaxTenuringThreshold阀值之后,进入老年代。

/*

* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

* -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution

*/

public class testTenuringThreshold {

private static final int _1MB = 1024*1024;

public static void main(String[] args) {

byte[] allocation1, allocation2,allocation3 ,allocation4;

allocation1 = new byte [_1MB/4];

allocation2 = new byte [4*_1MB];

allocation3 = new byte [4*_1MB];

allocation3 = null;

allocation4 = new byte [4*_1MB];

}

}

运行说明:

以下结果是以-XX:MaxTenuringThreshold=1运行的。

[GC [DefNew

Desired survivor size 524288 bytes, new threshold 1 (max 1)

- age   1:     706640 bytes,     706640 total

: 5015K->690K(9216K), 0.0055151 secs] 5015K->4786K(19456K), 0.0055536 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

[GC [DefNew

Desired survivor size 524288 bytes, new threshold 1 (max 1)

- age   1:        248 bytes,        248 total

: 5113K->0K(9216K), 0.0019266 secs] 9210K->4786K(19456K), 0.0019567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Heap

def new generation   total 9216K, used 4260K [0x04840000, 0x05240000, 0x05240000)

eden space 8192K,  52% used [0x04840000, 0x04c68fd8, 0x05040000)

from space 1024K,   0% used [0x05040000, 0x050400f8, 0x05140000)

to   space 1024K,   0% used [0x05140000, 0x05140000, 0x05240000)

tenured generation   total 10240K, used 4785K [0x05240000, 0x05c40000, 0x05c40000)

the space 10240K,  46% used [0x05240000, 0x056ec790, 0x056ec800, 0x05c40000)

compacting perm gen  total 12288K, used 1589K [0x05c40000, 0x06840000, 0x09c40000)

the space 12288K,  12% used [0x05c40000, 0x05dcd758, 0x05dcd800, 0x06840000)

No shared spaces configured.

<1> 从上面红色字体可以看出,from space 1024K,   0% used,而the space 10240K,  46% used。

5、动态对象年龄判断:

并不是一定要达到MaxTenuringThreshold对象才会进入老年代。

如果在Survivor空间中相同年龄所有对象大小的总和>Survivor空间的一半,年龄>=该年龄的对象就可以直接进入老年代。

例子:

6、空间分配担保

发生Minor GC时,虚拟机会检测之前每次晋升到老生代的平均大小是否大于老生代的剩余空间:

如果大于,则改为直接进行一次Full GC。

如果小于,则查看HandlePromotionFailure设置是否允许担保失败:

如果允许,则只会进行一次Minor GC;

如果不允许,则改为执行一次Full GC。

在实际完成内存回收之前是无法明确知道的,所以只好取之前每次回收到晋升老生代对象容量的平均大小值作为经验值。由于平均值仍然是动态的,可能导致担保失败(HandlePromotionFailure),那就只好在失败之后重新发起一次Full GC。

虽然担保失败绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure开关打开,避免Full GC过于频繁。

时间: 2024-12-25 19:49:23

深入理解_JVM内存管理内存分配和回收策略06的相关文章

内存管理-分配,回收策略

jvm启动的时候会固定分配内存,将其分为程序计数器,堆内存,栈内存. 程序计数器记录每个线程下一条指令的地址. 堆内存存放类的实例变量和数组,空间是共享的 栈内存存放局部变量,每个线程拥有私有的栈空间 垃圾回收器(gc)主要负责堆内存的回收任务.栈内存中的内容存活的周期非常短. 堆内存中可以分为,年轻代,老年代,永久代: 内存管理-分配,回收策略

计算机操作系统学习笔记_7_内存管理 --内存管理基础

h2.western { font-family: "Liberation Sans",sans-serif; font-size: 16pt; }h2.cjk { font-family: "微软雅黑"; font-size: 16pt; }h2.ctl { font-family: "AR PL UMing CN"; font-size: 16pt; }h1 { margin-bottom: 0.21cm; }h1.western { fon

java虚拟机(3)--内存分配与回收策略

三.内存分配与回收策略 1.1 Minor GC 和 Full GC Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快. Full GC:发生在老年代上,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多. 1.2 内存分配策略 1.2.1            对象优先在 Eden 分配 大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor

垃圾收集器与内存分配策略(六)之内存分配与回收策略

垃圾收集器与内存分配策略(六)--内存分配与回收策略 对象的内存分配,一般来说就是在堆上的分配(但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象分配的细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数设置. 区分Minor GC与 Full GC: 新生代GC(Minor GC):指发生在新生代的的垃圾收集动作,因为Java对象大多具有朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快. 老年代GC(Full GC / Major GC):老

二 内存分配与回收策略

内存分配与回收策略 对象的内存分配,往大方向讲,就是在堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将线程优先在TLAB上分配,少数情况下也可能直接分配在老年代中. 对象优先在Eden分配  大多数情况下,对象在新生代Eden区中分配.当Eden区没有足够空间进行分配时,虚拟机讲发起一次MinorGC. 大对象直接进入老年代 所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字

【6】JVM-内存分配与回收策略

JAVA技术体系中的自动内存管理实际上就是自动化的解决了给对象分配内存以及回收给对象分配的内存这两个问题.回收部分通过之前的<GC设计思路分析>和<垃圾收集器>这两篇博文进行了总结,那么接下来主要就是谈谈自己对JVM是如何给对象分配内存这一部分的理解.JVM的内存空间是有限的,并且堆内存是共享的,那么不同线程共用堆内存如何保证线程安全都是需要考虑的问题. 通过之前对JVM中的内存模型的分析以及GC的学习,我们知道JAVA内存分配往大了说就是在JAVA堆上分配内存,对象主要分配在新生

JVM-内存分配与回收策略

简单介绍一下Java技术体系下的Java虚拟机内存分配与回收策略.  1.对象优先在Eden分配  大多数情况下,对象在新生代Eden区中分分配.当Eden区已没有足够空间进行分配时,虚拟机将发起一次 Minor GC. 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多数都具备朝生夕灭的特征,所以Minor GC 非常频繁,一般回收速度也比较快. 老年代GC(Major GC / Full GC):指发生在老年代的GC出现了Major GC,经常会伴随至少一次的

【深入理解JVM】:内存分配与回收策略

Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存. 对象的内存分配,往大方向讲,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配.少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置. 本文中的内存分配策略指的是Serial / Serial Old收集器下(ParNew /

《深入理解Java虚拟机》笔记 第三章 内存分配与回收策略

几条主要的最普遍的内存分配规则: ? ? 1.对象优先在Eden分配 ? 大多数情况下,对象在新生代的Eden区中分配. ? ? 当Eden区没有足够的空间进行分配时,虚拟将发起一次Minor GC,如果GC后新生代中存活的对象无法全部放入Survivor空间,则需要通过分配担保机制提前进入到老年代中,前提是老年代中不能容纳所有存活对象,即只能容纳部分. ? ? 则未能进入到老年代的存活对象将继续分配在Eden区中 ? ? 如果Eden区也还未能容纳剩余的存活对象虚拟机抛出OutOfMemory