醉卧草庐听风雨

君子藏器于身,待时而动

0%

Jvm分代及分配

在上篇粗浅的杂谈中介绍了Jvm垃圾回收概念和指标,那么这期就来介绍下Jvm垃圾回收中的分代和分配。

分代回收

Jvm垃圾回收使用的是一种叫做分代回收的技术,即:将内存分为几代,不同的代持有不同年龄的对象。目前广泛分为两代:年轻代, 年老代。在不同的内存代上会对相应的特征进行回收算法优化:

  • 大多数对象不会被长时间引用,通常很快消亡
  • 年老对象引用年轻对象比较少

由于年轻代空间通常很小,并且包含许多不再引用的垃圾对象,所以年轻代回收相对频繁高效且快速,对象在若干次年轻代回收依然存活的则会移动到年老代中。
年老代的空间通常比年轻代更大,占用内存增长也较慢,年老代回收是低频且耗时的。
年轻代回收较为频繁,因此年轻代的垃圾回收算法通常关注于提高速度。年老代则由空间效率更高的算法来管理,因为年老代占用了大部分堆,年老代算法也必须在垃圾密度较低下工作良好。

分代结构

先来看看堆的划分,在JDK 8 之前,堆分为年轻代young generation)、年老代old generation)、永久代permanent generation),但JDK8之后,堆分为年轻代young generation)、终身代tenured generation)。永久代permanent generation)由元空间Metaspace)代替。虽然不一样,但不影响理解,本文采用JDK8的划分来介绍。
大多数对象最初在年轻代上分配,终身代则包含了经历数次年轻代回收幸存的对象,不过有些大对象由于体积比较大可能直接就在终身代上分配了。JDK8之前的永久代包含了类的元信息和类变量,在JDK8中被拆分,类的元信息存储在了元空间,类变量存入堆中。
年轻代由一个伊甸区(Eden)和两个小幸存者区(survivor)组成,如下图所示

大多数对象分配在伊甸区(大对象可能直接在终身代上分配),经历过年轻代回收后幸存的对象则会被移动至幸存者区,几次年轻代回收仍然幸存的对象则会被认为足够老从而晋升到终身代。在任何时间里,两个幸存者区中的一个都是空的以用作下一次年轻代回收使用。

回收类型

在不同的分代上执行的回收类型也是不一样的:

  • 当年轻代被装满时,进行回收被称为:年轻代回收young generation collection)有时也被称为次要回收minor collection),仅仅在年轻代进行。
  • 当终身代被装满时,进行回收被称为:全量回收full collection),有时也被称为主要回收major collection),因为会在全部的分代上回收。

全量回收时会先执行年轻代回收堆年轻代进行处理,接着在终身代上由终身代算法进行回收,若是出现压缩,则会在每个分代上独立进行。
全量回收时如果终身代无法容纳来自年轻代回收晋升的对象,那么年轻代回收则会终止,交由终身代回收算法对全部堆进行回收。但是CMS收集器不会这么处理,CMS收集器终身代算法不会去处理年轻代。

快速分配

Jvm使用了一种叫做bump-the-pointer技术来进行对象的快速分配,其原理是始终跟踪先前分配对象末端的指针位置,当处理新的分配请求时,检查分代剩余空间是否满足,满足则更新末端指针位置并初始化分配请求中的对象。
但在多线程下若使用全局锁来保证末端指针的线程安全,那么对象分配就会成为瓶颈而降低性能,为此,Jvm使用了Thread-Local Allocation Buffers技术,简称TLABs。该技术通过在分代中为每个线程划分一小块区域用于对象分配,来提高多线程分配的吞吐量。由于每个线程只在自己的TLAB区域中进行对象分配,这时便可无锁的使用bump-the-pointer进行对象分配。在线程分配使用完TLAB区域需要获取一个新的时,必须使用同步,不过这种情况极少发生。

写在最后

由此知道了,Jvm在分代和分配中使用的技术和策略,加深了对Jvm的认识,那么下一篇将会介绍不同垃圾回收器的回收垃圾的具体过程。