目录
3. Concurrent Mark and Sweep(CMS)
一、自动垃圾回收算法
1.引用计数法(Reference Counting)
缺点:循环引用,导致红色部分引用计数一直大于零 ;
2. 标记-清除(Mark and Sweep)
缺点:STW(Stop the World),需要暂停应用程序的所有线程;
二、Java中的垃圾回收
1. 内存池的划分
2.内存分配
Eden区
1)大部分对象直接由JVM 在对应线程的TLAB(Thread Local Allocation Buffer,线程本地分配缓存区)中分配,避免与其他线程的同步操作。
2)如果 TLAB 中没有足够的内存空间,就会在共享Eden区之中分配。
3)如果共享Eden区也没有足够的空间,就会触发一次年轻代GC 来释放内存空间。
4)如果GC之后 Eden 区依然没有足够的空闲内存区域,则对象就会被分配到老年代空间。
Survivor区
1)from 区
和 to 区,
任意时刻总有一个存活区是空的(empty),空的那个存活区,用于在下一次年轻代GC时存放收集的对象。
2)年轻代中所有的存活对象(包括Edenq区和非空的那个 “from” 存活区)都会被 复制 到 ”to“ 存活区。
3)存活的对象会在两个存活区之间复制多次,直到某些对象的存活 时间达到一定的阀值(提示场景1)会被提升到老年代。提升的时候,存活区的对象不再是复制到另一个存活区,而是 迁移 到老年代。
提升阈值
1)具体的提升阈值由JVM动态调整,但也可以用参数-XX:+MaxTenuringThreshold来指定上限。如果设置-XX:+MaxTenuringThreshold = 0 ,则GC时存活对象不在存活区之间复制,直接提升到老年代。
2)现代 JVM 中这个阈值默认设置为15个 GC周期,这也是HotSpot中的最大值。
3)如果存活区空间不够存放年轻代中的存活对象(提升场景2),提升也可能更早地进行。
永久代
1)在Java 8 之前有一个特殊的空间,称为“永久代”,这是存储元数据(metadata)的地方,比如 class 信息等。此外,这个区域中也保存有其他的数据和信息, 包括 内部化的字符串等等。
2)实际上这给Java开发者造成了很多麻烦,因为很难去计算这块区域到底需要占用多少内存空间,预测失败导致的结果就是产生 OOM。
元数据区(Metaspace)
1)Java 8直接删除了永久代(Permanent Generation),改用 Metaspace。Java 中很多杂七杂八的东西都放置到普通的堆内存里。
2)像类定义(class definitions)之类的信息会被加载到 Metaspace 中。元数据区位于本地内存(native memory),不再影响到普通的Java对象。默认情况下, Metaspace的大小只受限于 Java进程可用的本地内存。
Minor GC / Major GC / Full GC
Minor GC:回收年轻代的内存;
major GC:回收的是老年代的内存;
Full GC:回收的是整个堆内存;
三、GC算法
1.标记可达对象(Mark)
2.删除不可达对象
1)清除
Mark and Sweep(标记-清除)
缺点:
- 内存碎片化(内存不连续,内存充足,但无法分配给超出连续内存大小的对象)
- 维护空闲表 (需要用那个空闲表记录内存区域的位置和大小)
2)整理
Mark-Sweep-Compact(标记-清除-整理算法)
优点:
- 内存连续;
缺点:
- STW时间增加;
复制
Mark and Copy(标记-复制算法)
优点:
- 内存连续;
- 标记和复制可以同时进行;
缺点:
- 内存使用率低;
四、GC算法实现
Java 8中各种组合的垃圾收集器
1. Serial GC
串行GC:
1)Serial GC 对年轻代使用标记-复制(Mark and Copy)算法, 对老年代使用 标记-清除-整理(Mark - Sweep - Compat)算法;
2)触发全线暂停(STW),停止所有的应用线程;
3)两者都是 单线程 的垃圾收集器,不能进行并行处理,不能充分利用多核CPU;
2. Parallel GC
并行GC
1)Parellel GC 对年轻代使用标记-复制(Mark and Copy)算法, 对老年代使用 标记-清除-整理(Mark - Sweep - Compat)算法;
2)触发STW事件,暂停所有的应用线程;
3)在执行 标记和 复制/整理阶段时都使用 多个线程 ;通过并行执行, 使得GC时间大幅减少;
3. Concurrent Mark and Sweep(CMS)
并发标记清除
1)CMS对年轻代采用并行标记-复制(Mark and Copy)算法,,对老年代主要使用并发标记-清除(Mark and Sweep)。
2)不对老年代进行整理, 而是使用空闲列表(free-lists)来管理内存空间的回收;
3)在老年代标记-清除(Mark and Sweep) 阶段的大部分工作和应用线程一起并发执行;
3)在这些阶段并没有明显的应用线程暂停,仍然和应用线程争抢CPU时间。默认情况下, CMS 使用的并发线程数等于CPU内核数的 1/4;
CMS中Full GC的各个阶段
1)初始标记( 需要STW )
标记老年代中, GC ROOT 的直接引用, 以及由年轻代中存活对象所引用的对象;
只标记GC ROOT和年轻代的直接引用对象,所以时间短;
2)并发标记
垃圾收集器遍历老年代, 标记所有的存活对象;
“并发标记”阶段,不用暂停暂停应用程序。
并非所有老年代中存活的对象都会被标记, 因为在标记过程中对象的引用关系还在发生变化。
3)并发预清理
与应用线程并行执行的, 不需要停止应用线程;
因为前一阶段是与程序并发进行的,可能有一些引用已经改变;
如果在并发标记过程中发生了引用关系变化,JVM会(通过“Card”)将发生了改变的区域标记为“脏”区(卡片标记);
在预清理阶段,这些脏对象会被统计出来,从他们可达的对象也被标记下来。标记完成后, 清空Card;
4)并发可取消的预清理
与应用线程并行执行的, 不需要停止应用线程;
本阶段尝试在 STW 的 Final Remark 之前尽可能地多做一些工作;
本阶段的具体时间取决于多种因素,,因为它循环做同样的事情,直到满足某个退出条件( 如迭代次数, 有用工作量, 消耗的系统时间等等);
5)最终标记( 需要STW )
这是GC事件中第二次(也是最后一次)STW阶段;
目标是完成老年代中所有存活对象的标记;
因为之前的 preclean 阶段是并发的, 有可能无法跟上应用程序的变化速度,所以需要 STW暂停来处理复杂情况;
6)并发清除
与应用程序并发执行,不需要STW停顿;
基于标记结果,删除未使用的对象,并收回他们占用的空间;
7)并发重置
与应用线程并行执行的, 不需要停止应用线程;
重置CMS算法相关的内部数据, 为下一次GC做准备;
4. G1
Garbage Fist(垃圾优先算法)
将STW停顿的时间和分布变成可预期以及可配置的。可以为其设置某项特定的性能指标,可以指定: 在任意 xx
毫秒的时间范围内, STW停顿不得超过 x
毫秒; Garbage-First GC 会尽力达成这个目标(有很大的概率会满足, 但并不完全确定,具体是多少将是硬实时的[hard real-time]);
1)堆不再分成连续的年轻代和老年代空间。而是划分为多个(通常是2048个)可以存放对象的 小堆区;每个小堆区都可能是 Eden区, Survivor区或者Old区。
2)GC不必每次都去收集整个堆空间, 而是以增量的方式来处理::每次只处理一部分小堆区,称为此次的回收集(collection set).;
3)每次暂停都会收集所有年轻代的小堆区, 但可能只包含一部分老年代小堆区;
4)G1的另一项创新, 是在并发阶段估算每个小堆区存活对象的总数;用来构建回收集(collection set)的原则是: 垃圾最多的小堆区会被优先收集(Garbage First)。