jvm对象分配及GC代码实例

标签: jvm 对象 gc | 发表时间:2015-01-23 22:53 | 作者:crukor
出处:http://www.iteye.com

虚拟机采用的是HotSpot内核

 

对象分配规则

  1.对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。

  2.大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝。通过参数-XX:PretenureSizeThreshold=3145728控制。

  3.长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,对象每熬过了1次Minor GC对象的年龄加1,达到阀值对象进入老年区。通过参数-XX:MaxTenuringThreshold=15(默认)控制。

  4.动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold要求的年龄数。

  5.空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。 (-XX:-HandlePromotionFailure)

 

术语说明:

Young Generation(新生代):分为:Eden区和Survivor区,Survivor区有分为大小相等的From Space和To Space。

Old Generation(老年代): 当 OLD 区空间不够时, JVM 会在 OLD 区进行 major collection。

 

Minor GC:新生代GC,指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕死的特性,所以Minor GC非常频繁,一般回收速度也比较快。

    Full GC/Major GC:发生老年代的GC,对整个堆进行GC。出现Major GC,经常会伴随至少一次Minor GC(非绝对)。MajorGC的速度一般比minor GC慢10倍以上。

 

 

 

规则一:对象优先在Eden分配

 

  设置虚拟机参数为:-verbose:gc -Xms20M -Xmx20M -Xmn10M 

-XX:SurvivorRatio=8  -XX:+PrintGCDetails

 

  虚拟机提供了-XX:+PrintGCDetails 参数打印收集器日志,并且在进程退出时输出当前内存各区域的分配情况。

  通过 -Xms20M -Xmx20M -Xmn10M这3个参数限制java堆大小为20MB,且不可扩展。其中10MB分配给新生代,剩下的10MB分配给老年代。

  -XX:SurvivorRatio=8 决定了新生代中Eden区与一个Survivor区的比例为8:1,即Eden区=8MB,一个Survivor=1MB,另一个Survivor也为1MB。

 

/**
 * eden 对象通过分配担保机制提前转移到老年代去
 * vm 参数  -verbose:gc -Xms20M -Xmx20M -Xmn10M 
-XX:SurvivorRatio=8   -XX:+PrintGCDetails
*/
public class MinorGC {
	private static final int _1MB=1024*1024;
	public static void testMinorGC(){
	byte[] allocation1,allocation2,allocation3,allocation4;
	allocation1 = new byte[2 * _1MB];
	allocation2 = new byte[2 * _1MB];
	allocation3 = new byte[2 * _1MB];
	allocation4 = new byte[4 * _1MB]; //出现一次minor GC
}

public static void main(String[] args) {
	testMinorGC();
}
}

 

控制台输出信息:

[GC [DefNew: 6471K->140K(9216K), 0.0074976 secs] 6471K->6284K(19456K), 0.0075433 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

Heap

 def new generation   total 9216K, used 4400K [0x029f0000, 0x033f0000, 0x033f0000)

  eden space 8192K,  52% used [0x029f0000, 0x02e18fd8, 0x031f0000)

  from space 1024K,  13% used [0x032f0000, 0x03313208, 0x033f0000)

  to   space 1024K,   0% used [0x031f0000, 0x031f0000, 0x032f0000)

 tenured generation   total 10240K, used 6144K [0x033f0000, 0x03df0000, 0x03df0000)

   the space 10240K,  60% used [0x033f0000, 0x039f0030, 0x039f0200, 0x03df0000)

 compacting perm gen  total 12288K, used 2105K [0x03df0000, 0x049f0000, 0x07df0000)

   the space 12288K,  17% used [0x03df0000, 0x03ffe6e0, 0x03ffe800, 0x049f0000)

No shared spaces configured.

 

  其中“[GC [DefNew:”后面内容为GC收集情况,DefNew为新生代GC

  Heap 后面内容为进程退出时输出当前内存各区域的分配情况。

eden space         为eden区

from space         为Survivor区

tenured generation   为年老代

compacting perm gen为永久代(方法区)

 

日志分析:在执行方法testMinorGC()分配allocation4 对象语句时,会发生Minor GC ,这次GC结果是新生代由6471K变为140K(DefNew: 6471K->140K),而总内存占用量几乎没有减少(allocation1、2、3三个对象都是存活的,虚拟机没有找到可回收的对象)。

  这次GC发生的原因是给allocation4分配内存的时候,发现eden已经被占用了6MB,剩余的空间不足以分配allocation4所需的4MB内存,因此发生Minor GC。GC期间虚拟机又发现已有的3个2MB大小的对象全部无法放入Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前转移到老年代去。

  这次GC结束后,4MB的allocation4对象被顺利分配到eden中。因此程序执行完的结果是eden占用4MB,Survivor空闲,老年代被占用6MB。

 

规则五:空间分配担保

  在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否是否大于老年代剩余空间的大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败,如果允许,则只进行Minor GC,如果不允许,则进行一次Full GC。

  老年代进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来,在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年达对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多的空间。

  如某次Minor GC存活后的对象突增,远高于平均值的话,会导致担保失败。如果出现了HandlePromotionFailure,那就只好在失败后重新发起一次Full GC。

 

/**
 * VM参数:-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 
-XX:-HandlePromotionFailure  -XX:+PrintGCDetails 
*/

public class HandlePromotiontest {
  private static final int _1MB = 1024 * 1024;
  @SuppressWarnings("unused")
  public static void testHandlePromotion() {
	byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;
	allocation1 = new byte[2 * _1MB];
	allocation2 = new byte[2 * _1MB];
	allocation3 = new byte[2 * _1MB];
	allocation1 = null;
	allocation4 = new byte[2 * _1MB];//Minor GC
	allocation5 = new byte[2 * _1MB];
	allocation6 = new byte[2 * _1MB];
	allocation4 = null;
	allocation5 = null;
	allocation6 = null;
	allocation7 = new byte[2 * _1MB];//GC
  }

  public static void main(String[] args) {
	testHandlePromotion();
  }

}

 

设置HandlePromotionFailure时

[GC [DefNew: 6471K->140K(9216K), 0.0043721 secs] 6471K->4236K(19456K), 0.0044109 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

[GC [DefNew: 6370K->6370K(9216K), 0.0000361 secs][Tenured: 4096K->4236K(10240K), 0.0045948 secs] 10466K->4236K(19456K), [Perm : 2086K->2086K(12288K)], 0.0047019 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

 

未设置HandlePromotionFailure时

[GC [DefNew: 6471K->140K(9216K), 0.0042261 secs] 6471K->4236K(19456K), 0.0042664 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

[GC [DefNew: 6370K->140K(9216K), 0.0005921 secs] 10466K->4236K(19456K), 0.0006289 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

 

 

日志分析:

设置HandlePromotionFailure参数时:

  在执行方法testHandlePromotion()给allocation4 分配对象时,会发生第一次 GC(一般第一次都为Minor GC) ,这次GC发生的原因是给allocation4分配内存的时候,发现eden已经被占用了6MB,剩余的空间不足以分配allocation4所需的2MB内存,因此发生Minor GC。而且已有的3个2MB大小的对象全部无法放入Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前转移到老年代去。

  第二次GC发生在给allocation7分配2MB对象时,此时eden已经被占用了6MB内存(allocation4、5、6),剩余内存不足需要转移到老年代。因为设置的是不允许担保失败,此时要进行一次Full GC。

注意:第二次GC开始时[GC DefNew: 6370K->6370K,进行minor GC并没有立即执行GC,先判断是否可以空间担保分配,不允许则执行Full GC。

 

未设置HandlePromotionFailure参数时:

  第一次GC同上。

  第二次GC发生在给allocation7分配2MB对象时,此时eden已经被占用了6MB内存(allocation4、5、6),剩余内存不足需要转移到老年代。虚拟机检测之前晋升到老年代的平均值(第一分配担保的值4MB即平均值)小于老年代剩余空间的大小。因为设置的是允许担保失败,只需进行一次minor GC。

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [jvm 对象 gc] 推荐:

jvm对象分配及GC代码实例

- - Java - 编程语言 - ITeye博客
虚拟机采用的是HotSpot内核.   1.对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC.   2.大对象直接进入老年代(大对象是指需要大量连续内存空间的对象). 这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝. 通过参数-XX:PretenureSizeThreshold=3145728控制.

JVM垃圾回收(GC)原理

- kill - yiihsia[互联网后端技术]_yiihsia[互联网后端技术]
引用计数(Reference Counting). 原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数. 垃圾回收时,只用收集计数为0的对象. 此算法最致命的是无法处理循环引用的问题. 标记-清除(Mark-Sweep). 第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除.

JVM的GC简介和实例

- - 搜索技术博客-淘宝
本文是一次内部分享中总结了jvm gc的分类和一些实例, 内容是introduction级别的,供初学人士参考. 成文仓促,难免有些错误,如果有大牛发现,请留言,我一定及时更正,谢谢. JVM内存布局主要包含下面几个部分:. Java Virtual Machine Stack: 也就是我们常见的局部变量栈,线程私有,保存线程执行的局部变量表、操作栈、动态连接等.

如何查看GC 及jvm配置

- - 操作系统 - ITeye博客
java虽然是自动回收内存,但是应用程序,尤其服务器程序最好根据业务情况指明内存分配限制. 表示JVM Heap(堆内存)最小尺寸128MB,初始分配. 表示JVM Heap(堆内存)最大允许的尺寸256MB,按需分配. 说明:如果-Xmx不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM不是Throwable的,无法用try...catch捕捉.

JVM内存组成和GC机制

- - Mz的博客
Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配. 堆是在 Java 虚拟机启动时创建的. 在JVM中堆之外的内存称为非堆内存(Non-heap memory). 可以看出JVM主要管理两种类型的内存: 堆和 非堆. 简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给 自己用的,所有方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法 的代码都在非堆内存中.

【JVM】HotSpot JVM内存管理和GC策略总结

- - ITeye博客
JVM的相关知识是学习java高级特性必须要去深入学习的. 平时也有一些学习和实践,不过总结比较少. 今天有时间总结一下最基础的内存模型和GC策略的知识,在此记录一下. hotspot jvm内存模型. hotspot的内存模型很多地方都有类似总结,我也简单总结了一下,大概可以用下图表示:. 1.线程栈:线程创建是会为每个线程创建一个线程栈,线程栈里面会为每个方法调用创建一个栈帧.

FULL GC有可能导致JVM暂停1分钟以上吗?

- - 高级语言虚拟机
作者: qianhd . 链接: http://hllvm.group.iteye.com/group/topic/28745 . 发表时间: 2011年12月30日. 声明:本文系ITeye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任. 与另外个程序交互有个心跳检测, 15s/次, 31s没有收到心跳检测的返回消息就认为连接断了,.

JVM初探——使用堆外内存减少Full GC

- - ImportNew
GCIH可以联想到:  将长期存活的对象(如Local Cache)移入堆外内存(off-heap, 又名. 直接内存/direct-memory), 从而减少CMS管理的对象数量, 以降低Full GC的次数和频率, 达到提高系统响应速度的目的.. 这个idea最初来源于TaobaoJVM对OpenJDK定制开发的GCIH部分(详见 撒迦的分享- JVM定制改进@淘宝), 其中GCIH就是将CMS Old Heap区的一部分划分出来, 这部分内存虽然还在堆内, 但已不被GC所管理.

JVM 调优 —— GC 长时间停顿问题及解决方法

- - ImportNew
垃圾收集器长时间停顿,表现在 Web 页面上可能是页面响应码 500 之类的服务器错误问题,如果是个支付过程可能会导致支付失败,将造成公司的直接经济损失,程序员要尽量避免或者说减少此类情况发生. 并发模式失败(concurrent mode failure). 在 CMS 启动过程中,新生代提升速度过快,老年代收集速度赶不上新生代提升速度.

JVM初探- 内存分配、GC原理与垃圾收集器

- - IT瘾-geek
JVM初探- 内存分配、GC原理与垃圾收集器. JVM内存的分配与回收大致可分为如下4个步骤: 何时分配 -> 怎样分配 -> 何时回收 -> 怎样回收. new时分配外, 我们着重介绍后面的3个步骤:. 怎样分配- JVM内存分配策略. 对象内存主要分配在新生代 Eden区, 如果启用了本地线程分配缓冲, 则 优先在TLAB上分配, 少数情况能会直接分配在老年代, 或被拆分成标量类型在栈上分配(JIT优化).