导语 作为java工程师,对于java虚拟机内部机制不能不了解一些,这样才在分析疑难问题时有的放矢。 前段时间定了个小目标,利用晚上的时间拜读大名鼎鼎的周志明的《深入理解Java虚拟机》,才看几章,“人民的名义”火起来了。唉,时间被强行夺了去,小目标眼睁睁的被失败了。 所以在这里强行立flag吧,先把这几章的心得总结在这里,解解达康**的毒。 现在的主流虚拟机一般都采用分代回收,新生代、老年代。 一、分代 1.为什么要分代?有什么意义?这里我们假设没有分代,会怎么样呢? 答:“stop the world”,程序被卡成翔。 为什么 ? 因为gc的时候需要分析死亡对象,所以不允许这个时候对象引用关系再发生变化,这就要求“stop the world”,所以线程必然会被挂起。所以我们为了尽量让用户无感知,必然要提高gc效率。怎么提高,答案就是分代。我们把长活对象放在一块称为老年代,再把短命鬼放在另一块称为新生代。这样我们一般情况下只需要扫描新生代区域回收无用对象即可减少world被stoped的时间,让用户在gc时依然有丝滑般的顺畅感。 2.分代实现
我们可以看到新生代内存分配要比老年代更复杂一些,为什么会有这个区别呢? 答:垃圾清理算法的不同。 新生代:复制算法;老年代:标记-整理算法。 复制算法: 上图可以明显看到,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。它的优点就是不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。但有个致命缺点就是要“浪费“一半的内存,这太蛋疼了。
那可不可以少浪费一点呢? 答:可以的,就是Survivor区 ,它与eden区比例是1:8,有两个survivor区,其中一个survivor区和eden区是可用的,另一个survivor区是gc复制区,这样被浪费的区域只有1/10了,大大减少了。但是这样真的合理吗,可以满足实际需要吗,当然是可以的,IBM公司研究过,98%的Java对象都是”朝生夕死“,生命期很短的,这样每次gc之后大部分对象都over了,真正存活下来的是少数,所以我们只用1/10的空间存在这些存活对象就够了。但是,异常情况也是有的,万一存活对象所占内存多于survivor区怎么办呢,当然也是有解决方案:分配担保。就是内存不足的时候由担保方承担,这个担保方就是老年代区。所以这里可以看到老年代扮演着最后的大佬的角色,同时可以看到这种算法的致命缺点就是必须要有一个担保方。所以老年代不能采用这种方式,因为他就是最后的担保方,没有人再能给它担保,除非survivor区占一半内存,但是这又太浪费了。最后,老年代使用了”标记-整理“算法。 标记-整理算法: 分为两个阶段,首先标记所有存活对象,接着让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,这样就完全的利用了所有内存了。 到这里可能会有人疑问,新生代为什么不也采用这种算法,不就也能充分利用所有的内存了吗?其实这里是两种算法出发点的不同,所谓复制算法是空间换时间,而标记-整理算法则是时间换空间。 复制算法在工作的时候是不没有独立的“mark”与“copy”阶段的,而是合在一起做一个动作,就叫复制。也就是说,每发现一个这次收集中尚未访问过的活对象就直接copy到新地方,同时设置forwarding pointer。这样的工作方式就需要多一份空间。 标记-整理算法在工作的时候则需要分别的mark与整理阶段,mark阶段用来发现并标记所有活的对象,然后整理阶段才移动对象来达到整理内存的目的。在mark之后就可以按顺序一个个对象“滑动”到空间的某一侧。因为已经先遍历了整个空间里的对象图,知道所有的活对象了,所以移动的时候就可以在同一个空间内而不需要多一份空间。 总结一下就是:新生代存活对象少,为了快速gc我们可以浪费一点内存;而老年代存活对象多,我们更在乎内存,同时因为有效对象多,所以这块区域的gc应该比新生代少。这就是我们下面要讲的minor gc和full gc。 二、普通GC(minor GC):全局GC 从上面我们知道新生代的gc是快速而频繁的,老年代gc是缓慢而稀少的。这也是分代的目的所在,大部分“朝生夕死”的对象可以快速被清理掉,而长存对象被放在特权区,在平常的minor gc的时候是不会被扫描到的,这样就大大的提高了gc的命中率了。 那到底什么时候full gc会被触发呢,当然也是老年代内存不足的时候。所以这里再说一下什么情况下,对象会被放到老年代: i. 对象足够老,所谓足够老就是经过多次gc之后依然存活的对象,这里的缺省设置是15次。 ii.分配担保中标的对象,就是上面说的minor gc的时候当新生代survivor区不足以存放的存活对象。 iii. 大对象,我们知道堆内存并不是连续的,有可能在一段时间之后内存很碎片化了,这样即使剩余总内存依然足够,但是在eden space已经找不到一块连续区域存放这个大对象了,这时候我们就知道把大对象直接放在老年代了。这样的情况如果发生多次我们可以想象,老年很快就会被占满,导致full gc的发生,而full gc就比minor gc慢很多(大概10倍),world就真被stop了。所以我们尽量必须new 大对象尤其是很多大对象一起new。 |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|