在 Java 运行时的几个数据区域中, 程序计数器, 虚拟机栈, 本地方法栈 3 个区域随着线程而生, 随线程而灭, 因此这几个区域的内存分配和回收具有确定性, 不需要过多考虑垃圾回收问题, 因为方法结束或者线程结束时, 内存就回收了. 但是方法区和堆区不一样, 一个接口或者实现类所需要的内存可能不一样, 一个方法的多个分支需要的内存也可能不一样, 只有程序运行时才能知道创建哪些对象, 这部分内存的分配和回收是动态的.
在进行垃圾回收时候, 首先需要判断哪些对象需要回收, 这就涉及到回收算法的问题.
一, 垃圾回收算法
1. 标记 - 清除算法
标记 - 清除算法是一种最基础的垃圾收集算法, 分为 "标记" 和 "清除" 两步."标记" 阶段标记所有需要进行垃圾回收的对象, 标记完成后统一回收被标记的对象. 这种算法的不足点在于:
(1) 效率问题, 标记和清除两个过程效率都不高;
(2) 空间问题, 标记清除后会产生大量不连续碎片, 后续如果需要为较大对象分配空间, 则又需触发垃圾回收.
2. 复制算法
为了解决标记 - 清除算法的效率问题, 出现了复制算法. 这种算法把内存按照容量划分为大小相同的两块, 每次只是用其中一块, 当这块内存用完了, 就把还存活的对象复制到另外一块中, 并将这块的内存清理掉, 然后使用另外一块, 当另外一块内存用完了, 再把存活的对象复制到这块中, 并清理另外一块内存, 依次类推.
复制算法主要用于新生代的回收, 在 HotSpot 虚拟机中, 新生代内存划分为一块较大的 Eden 空间, 和两块较小的 Survivor 空间, 每次使用 Eden 空间和其中一块 Survivor 空间. 当进行垃圾回收时, 会把 Eden 空间和 Survivor 空间中存活的对象一次性复制到另外一块 Survivor 空间上, 最后清理掉 Eden 空间和刚才使用过的 Survivor 空间. HotSpot 虚拟机中, 默认情况下 Eden 空间和 Survivor 空间的大小比例是 8:1, 即 Eden 空间占整个新生代的 80%, 每次新生代中使用的空间为 80% 10%=90%, 闲置空间 10%.
3. 标记 - 整理算法
复制算法适用于那种对象存活率较低的场景, 在对象存活率较高时, 使用复制收集算法意味着需要进行大量复制, 会使效率降低, 同时复制大量存活对象到另外一块内存, 意味着需要有足够大的内存来保存这些对象, 这势必会降低内存使用率. 根据老年代的特点, 有人提出标记 - 整理算法, 和标记 - 清除算法不同的是, 标记整理算法将存活的对象向一端移动, 然后直接清理掉端边界之外的内存.
4. 分代收集算法
目前商业虚拟机中都使用分代收集算法. 一般将 Java 堆分为新生代和老年代, 新生代进行垃圾收集发现有大量对象死去, 只有少量对象存活, 那么就使用复制算法. 老年代中对象存活率较高, 使用标记 - 清除算法或者标记 - 整理算法.
二, 垃圾收集器
垃圾收集算法提供了内存回收的方法论, 垃圾收集器是内存回收的方法论. 每个厂商对垃圾收集器的实现不一样, 这里主要讨论 Jdk1.7 Update 14 之后的 HotSpot 虚拟机. 这个虚拟机中包含的垃圾收集器有如下 7 种:
以上收集器之间如果有连线, 则表明可以搭配使用, 虚拟机所处区域, 表示他是新生代收集器还是老年代收集器.
1.Serial 收集器
Serial 收集器是一种最基本的单线程收集器, 这种收集器工作时, 必须停止其他所有工作线程, 优点在于简单高效, 但体验很不友好, 目前主要应用场合是: 虚拟机运行在 Client 模式下的默认新生代收集. 器.
2.ParNew 收集器
parNew 收集器是 Serial 收集器的多线程版本, 常用参数设置:
-XX:+UseConcMarkSweepGC : 设置 ParNew 为默认的新生代收集器;
-XX:+UseParNewGC : 指定使用 ParNew 为年轻代收集器, 强制指定;
-XX:ParallelGCThreads=n : 设置收集器的线程数为 n.
3.Parallel Scavenge 收集器
Parallel Scavenge 收集器是一个使用复制算法的新生代收集器, 这种收集器的主要目标是达到一个可控制的吞吐量 (Throughput,CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值, 即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)). 由于与吞吐量关系密切, 故而 Parallel Scavenge 收集器也称为 "吞吐量优先" 收集器. 常用参数设置:
-XX:MaxGCPauseMillis=n : 设置年轻代垃圾收集的最长时间;
-XX:GCTimeRatio=n : 设置垃圾收集总可用时长的比例, 和吞吐量直接相关;
-XX:+UseAdaptiveSizePolicy 自适应大小开关, 配置该选项之后, 每次 GC 后会重新计算 Eden,From 和 To 区的大小, 计算依据是 GC 过程中统计的 GC 时间, 吞吐量, 内存占用量, 因此设置此参数之后就不需要再设置 -XX:SurvivorRatio , -XX:PretenureSizeThreshold 等参数了.
4.Serial Old 收集器
Serial 收集器的老年版本, 也是一个单线程收集器, 使用的是 "标记 - 整理" 算法, 这种收集器的主要意义也是给 Client 模式下的虚拟机使用.
5.Parallel Old 收集器
Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本, 使用多线程和 "标记 - 整理" 算法. 在注重吞吐量以及 CPU 资源敏感的场合, 都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器的组合.
6.CMS 收集器
CMS 收集器是一种以获取最短回收停顿时间为目标的收集器. 它基于 "标记 - 清除" 算法实现, 运作过程相对于其他几种收集器更复杂一些. 分为以下四个过程:
(1) 初始标记 (CMS initial mark): 标记一下 CG Roots 能关联到的对象;
(2) 并发标记 (CMS concurrent mark): 进行 CG Roots Tracing 的过程;
(3) 重新标记 (CMS remark): 修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录.
(4) 并发清理 (CMS concurrent sweep)
CMS 收集器的优点在于并发收集, 低停顿. 其缺点在于以下三点:
(1)CMS 收集器对 CPU 很敏感, CMS 默认回收线程是 (CPU 数量 + 3)/4, 当 CPU 在 4 个以上时, 并发收集时垃圾收集线程不少于 25% 的 CPU 资源, 并随着 CPU 数量增加而下降. 但是当 CPU 不足 4 个时, CMS 对用户程序的影响就会变得很大.
(2)CMS 收集器无法处理浮动垃圾. 由于 CMS 收集器并发清理阶段用户线程还在运行着, 伴随着程序运行就会有垃圾产生, 这部分垃圾在标记过后, CMS 收集器无法在当次收集中清理这些垃圾.
(3) 由于 CMS 收集器是一种基于 "标记 - 清除" 算法的收集器, 这种算法实现的收集器在收集结束后会有大量不连续碎片产生. 碎片过多时会给大对象分配带来很大麻烦, 往往老年代还有很大空间剩余, 但是无法找到连续空间分配当前对象, 因而不得不提前触发 Full GC.
7.G1 收集器
G1 收集器是一款面向服务端应用的垃圾收集器, 与其他收集器相比, G1 收集器具有如下优点:
(1) 并发与并行: G1 能充分利用多 CPU, 多核硬件优势, 使用多个 CPU 来减少停顿时间;
(2) 分代收集: G1 不需要其他收集器配合就能独立管理整个堆的垃圾收集, 且它能采用不同方式去处理新建对象和已经存活了一段时间, 熬过多次 GC 的旧对象以获得更好的收集效果.
(3) 空间整合: 使用 G1 收集器不会产生内存碎片, 收集后能提供规整的可用内存. 这种特性有利于程序长时间运行, 分配大对象时候不会因为无法找到连续内存空间而提前触发下一次 GC.
(4) 可预测的停顿: G1 除了追求低停顿, 还能建立可预测的停顿时间模型, 能让使用着指定在长度为 M 毫秒的时间片段内, 消耗在垃圾收集上的时间不超过 N 毫秒.
三, 垃圾收集参数总结
参数 | 描述 |
---|---|
UseSerialGC | 虚拟机运行在 Client 模式下的默认值,打开此开关后,使用 Serial+Serial Old 的收集器组合进行内存回收 |
UseParNewGC | 打开此开关后,使用 ParNew + Serial Old 的收集器组合进行内存回收 |
UseConcMarkSweepGC | 打开此开关后,使用 ParNew + CMS + Serial Old 的收集器组合进行内存回收。Serial Old 收集器将作为 CMS 收集器出现 Concurrent Mode Failure 失败后的后备收集器使用 |
UseParallelGC | 虚拟机运行在 Server 模式下的默认值,打开此开关后,使用 Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收 |
UseParallelOldGC | 打开此开关后,使用 Parallel Scavenge + Parallel Old 的收集器组合进行内存回收 |
SurvivorRatio | 新生代中 Eden 区域与 Survivor 区域的容量比值,默认为 8,代表 Eden :Survivor=8∶1 |
PretenureSizeThreshold | 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 |
MaxTenuringThreshold | 晋升到老年代的对象年龄。每个对象在坚持过一次 Minor GC 之后,年龄就加 1,当超过这个参数值时就进入老年代 |
UseAdaptiveSizePolicy | 动态调整 Java 堆中各个区域的大小以及进入老年代的年龄 |
HandlePromotionFailure | 是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个 Eden 和 Survivor 区的所有对象都存活的极端情况 |
ParallelGCThreads | 设置并行 GC 时进行内存回收的线程数 |
GCTimeRatio | GC 时间占总时间的比率,默认值为 99,即允许 1% 的 GC 时间。仅在使用 Parallel Scavenge 收集器时生效 |
MaxGCPauseMillis | 设置 GC 的最大停顿时间。仅在使用 Parallel Scavenge 收集器时生效 |
CMSInitiatingOccupancyFraction | 设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集。默认值为 68%,仅在使用 CMS 收集器时生效 |
UseCMSCompactAtFullCollection | 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅在使用 CMS 收集器时生效 |
CMSFullGCsBeforeCompaction | 设置 CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理,仅在使用 CMS 收集器时生效 |
参考资料:《深入理解 Java 虚拟机 JVM 高级特性与最佳实践 第 2 版》
来源: https://www.cnblogs.com/fengweiweicoder/p/10787454.html