前言
在本系列的第一篇文章中,我们已经聊过关于内存垃圾的回收问题了。
但是我们对不同的对象,采用的回收策略也应该有所不同。
比如刚创建出来的对象更有可能被回收,而已经存在了很久的对象很有可能继续存在下去。
那么对所有的内存中的对象采用一样的回收态度就会造成资源的浪费。
JVM内存分代模型就出现了,就是将内存分为年轻代和老年代,刚刚创建的对象被分配进年轻代内存中,而存在比较久的对象则被分配进老年代内存中,其中年轻代的内存会更频繁的触发垃圾回收。
下面我们就来更详细的介绍一下这个内存分代模型。
内存分代模型
在JVM内存分代模型中,整个堆内存被分为了两块,分别是年轻代
和老年代
。
年轻代和老年代的默认大小比例为1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 )。
年轻代
年轻代内部又分为一个Eden区
和两个Survivor区
,比例是8:1:1,两个Survivor区分别被命名为from
和to
,以示区分。
JVM每次只会使用Eden区和某一个Survivor区。
当年轻代内存经历了一次垃圾回收后,所有的存活对象会进入另一个空置的Survivor区。
JVM则开始使用Eden区和存活对象进入的这个Survivor区,另一个Survivor区进入空置状态,如此反复。
注意,由于年轻代中内存对象失效会很快,存活下来的对象很少,所以适用于第一篇文章中讲的copying垃圾回收算法。
如果一个对象达到了设定的年龄,或者Survivor区装不下了,就会进入老年代。
老年代
老年代装的都是一些经历了好多次内存回收依然坚挺的对象,这些对象很有可能在未来的垃圾回收中继续存活下去。
相比与年轻代只要Eden区满了就会触发只针对年轻代内存的垃圾回收,老年代的垃圾回收被触发的频率明显要低。
只有当老年代的内存满了,才会进行整个内存的垃圾回收,也叫FGC(Full Garbage Collection)。虽然触发频率低,但是一旦触发就很耗时。
一个对象在内存中的旅程
一个对象从new出来开始,到进入老年代成为“顽固对象”,都经历了什么呢?
当一个对象刚刚new出来,被分配到哪块儿内存就已经曲折离奇了。
如果这个对象能被分配到栈上,就被分配到栈上。
如果这个对象不能被分配到栈上,就要看该对象是不是一个大对象,如果是大对象则直接进入老年代。
如果这个对象既不能分配到栈上,又不是大对象,就会被分配到年轻代中的Eden区。
注意,私货来了,分配进Eden区的时候会检查符不符合TLAB(Thread Local Allocate Buffer),这个机制是解决线程竞争内存的问题。每个线程独享Eden区一部分(默认1%),如果这个对象满足TLAB的限制,就优先存在当前线程对应的TLAB。
当Eden区满了,会首先触发YGC(Young Garbage Collection),如果这个对象侥幸撑过了这次GC,会进入某一个Survivor区。
之后Eden区每次满了都会触发YGC,而这个对象在每次YGC中就会从一个Survivor区进入另一个Survivor区,直到这个对象的年龄足够进入老年代。
然后这个对象就会进入老年代,等老年代的内存满了会触发FCG,如果这个对象成为垃圾就会被结束罪恶的一生,如果还存活着就继续坚挺下去。
用一张流程图来表示上述过程:
总结
本文主要介绍了JVM中的内存分代模型,根据对象的存活时间,分别存放在年轻代内存(Young)和老年代内存(Old)。
并且通过追踪一个对象从刚被创建出来到进入老年代的状态变迁,介绍了JVM中的内存分代模型是如何工作的。