JVM & Memory (4) gc

不同的JVM实现对堆结构的设计有所不同,这里先说说共性的,然后再比较classic vm和hotspot vm在gc方面的差异。

先 大致说说gc的过程,通常有两种情形会导致gc发生,一种是显式的System.gc()调用而java进程未禁止显示gc,第二种是隐式的,即内存管理 器(MM)在alloc内存时发生failure,MM进而作gc以便释放出空间用于分配(当然,假如gc后还是没有空间可满足分配数 值,OutOfMemory就发生了)。gc过程分3步走,第1步,mark阶段,标示出allocbits和markbits,allocbits 代表当前java heap中存在的对象所占内存的bit映射,markbits代表所有reachable对象所占内存的bit映射,是allocbits的子集。这一 步,一般是要锁定java heap的,但有些gc器能做到并发/并行mark(后面有解释)。第2步,sweep阶段,即将markbits和allocbits的补集所代表的内 存区域作回收,这也包括对class的回收,假如classgc未被禁用,而jvm确实找到了未被使用的class,那么除了java heap中的Class对象被回收,method area里该class的method data也被释放。第3步,compact阶段(可选的,jvm会自行判断是否进行该步),整理碎片,两种技巧,一种是移动琐碎的对象区域,使之连续,一种是移动琐碎的free区域,也使之连续。
这步也必须要锁定java heap。compact时还要做一个工作,即将所移动的对象的原始引用值作同步更新。这时存在一个问题,假如某thread stack里的一个浮点数碰巧看起来像是一个对象引用值 (内存泄露也会因此情形而发生) ,那么jvm因为无法断定而放弃移动该对象,等待以后再移动(据称,hotspot vm对精确判定引用值的问题解决的较好)。

再 解释一下mark阶段,为了得到markbits,首先要收集root references,也就是java thread stacks refs, jni global refs,thread moniters,interned strings,and soft/weak refs等,它们是对象引用的源头,从root referent object顺藤摸瓜,即可把所有reachable object标示出来。再解释一下bits映射,每一个bit代表每一个内存GRAIN(是分配的最小粒度),假设GRAIN是8 byte,那么对于64M的堆,需要额外的1M的bits来映射,这部分额外开支需要gc器向c heap申请,当堆扩展和收缩时,markbits也要随之变动。mark阶段工作量比较大,因为要扫描和遍历,所以后期的jvm都采用多种新的方式如 concurrent(普通线程也参与mark)或parallel(多cpu有效)进行mark。

再说堆的扩张(Expansion) 和收缩(Shrinkage)。当内存不足时,gc清理一遍后还发现free空间不能满足分配的尺寸要求,那么它有两种手段继续推进,如果最大堆 (-Xmx)未达到,那么扩展堆,如果已达到,那么强制回收soft/weak referents,如果此时还不能满足,那么OutOfMemory就发生了。而当经过一段运行时间后,jvm判断free区域较多(不同的jvm判定 依据有所不同),那么就会收缩java heap。收缩的好处有两个,1是减轻OS的负担,2又减少了jvm内存管理器的维护成本。注意,不论再怎么收缩,也不可能小于最小堆(-Xms)。默认 的ms值和mx值各vm提供商有所不同,请参阅其文档。

个人认为主流的jvm主要有3种,sun classic, ibm classsic,sun hotspot。sun classsic用于1.3.0以前,之后sun的jvm都是hotspot。而ibm的jdk一直是classsic,不知其5.0的vm是否会采用 hotspot。sun的是主流无可置疑啦,为什么扯上ibm的呢,因为看过一个民间的性能评测结果,ibm 1.42vm比sun的1.42vm甚至略好。sun classic使用间接句柄分配对象,所以堆分为两部分,一块放object,一块放handle。这个好处是compact方便得很,缺点也是很严重的 缺点,是对象访问不直接,要多一层指针转换,对性能大有影响。ibm classic的具体细节的资料比较缺乏,但根据其white paper,似乎使用的是直接句柄(否则性能无法比sun的好),另外它将java heap划分成system heap和一般heap,仅将jvm的系统class和对象分配到system heap里,它还支持并行和并发的gc。sun hotspot使用直接句柄,似乎又采用了一些高级手段,做到了精确辨别,因此快而且回收准确,另外提供了多种的gc器,适用于不同场合。值得一提的是它 的代生gc器(Generational Copying Collection),根据对象生命周期模型(小对象居多,生命周期短),使用类似stack的方式,将同一时期产生的对象放在同一容器 (nursery)中,这批对象基本上会同时消亡,这样,回收时可直接将
容器 释放,避免了繁琐的逐个对象释放的过程,因此可以减少gc的花费时间(duration)和频度(frequency),并且 容器 是现成的,所以分配时,寻找free空间很方便,所以提高了分配的效率。

本篇再加上前面的几篇,已经把JVM和Memory相关的主要机理和过程都已经覆盖了,下一篇主要是关于tuning和trouble shooting,请关注。

补: 再说一下内存泄露(memory leak),与c/c++的内存泄露的概念不同,java的memory leak是指无用(unused)的对象因为仍然是reachable的,所以不能被gc,因而造成内存的被白白占用。造成memory leak的原因是多样的,比如程序未及时给变量赋null,再比如上文提到的浮点数被碰巧当成对象引用,或者jni程序里声明的global ref忘了做撤销。对于服务器程序来说,memory leak的逐渐积累,只要运行时间足够,必然造成OutOfMemory。如何确定jvm是否有内存泄露呢,假使你没有监测工具也不要紧,打开- verbose:gc开关,运行系统足够长的时间(约收集20到50次gc的数据),然后将verbosegc的信息作收集,察看java heap的谷值(gc后java heap的大小,峰值则表示gc前java heap的大小)是否有递增的倾向,如果倾向明显,那么很有可能是memory leak问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值