首先,程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,即方法结束或者线程结束时,内存就跟着收集了,所以这几个区域不需要考虑不需要过多考虑回收的问题。而java堆和方法则不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分的内存分配和回收都是动态的,而垃圾收集器所关注的也是这部分内存。
一.回收方法区
方法区主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码和数据,在HotSpot虚拟机中称为永久代。永久代主要收集两部分内容:废弃常量和无用的类
二.垃圾收集算法
其中标黑加粗的是基本的算法。
算法名称 | 收集过程 | 特点 | 缺点 |
引用计数法 | 对象有一个引用计数器,每当一个地方引用它时+1,应用失效时-1,任何时刻计数器为0的对象就是不可能再被使用的 | 实现简单,判定效率高 | 很难解决对象之间循环引用的问题 |
标记清除算法 | 首先标记出所有需要收集的对象,在标记完成后统计收集所有标记的对象 | 最基本的收集算法,后续的收集算法都是基于这种思路 | 标记和清除两个过程效率都不高。标记清除后会产生大量的不连续内存碎片 |
复制算法 | 将可用内存划分为大小相等的两块,每次只使用其中一块。当这一块内存用完了,就将还存活的对象复制到另一个块上,然后把已使用过的那一块内存空间一次清理掉 | 内存分配时不用考虑内存碎片等复杂情况 | 将内存缩小为原来的一半 对象存活率较高时需要进行较多的复制操作 |
标记整理算法 (标记压缩算法) | 与标记清除算法一样,但是后续步骤不是直接对收集对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。 |
|
|
分代收集算法 | 根据对象的存活周期的不同将内存划分为几块,一般是吧java堆分为新生代和老年代,这样可以根据各个代的特点采用最适当的收集算法 | 新生代每次收集都发现大批对象死去,只有少量存活,就选用复制算法。老年代中因为对象存活率高,就采用标记清理或者标记整理算法 |
|
在以上的收集算法中多次提到“标记”,那么标记的过程是怎样的呢?即是如何判定对象是否存活的呢?
1.引用计数
2.可达性分析
通过一系列的称为"GC Roots"的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时候,则证明此对象是不可用的。
在java语言中,可作为GC Roots的对象包括以下几种
1.虚拟机栈中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI引用的对象
即使是可达性分析算法中不可达的对象,也并非“非死不可”的。要真正宣告一个一个对象死亡,要经过两个阶段:如果发现此对象没有与GC Roots相连的引用链,并且对象没有覆盖finalize方法,或者该方法已经执行过,那么该对象就被标记为死亡。否则的话,finalize方法是对象逃脱死亡的最后一次机会,如果对象在finalize方法中拯救了自己,那么该对象就不会被回收。需要注意的是任何一个对象的finalize方法都只会被系统调用一次。要尽量避免使用finalize方法。
三.垃圾收集器
如果说垃圾收集算法是内存收集的方法论,那么垃圾收集器就是内存收集的具体实现。不同厂商不同版本的虚拟机所提供的垃圾收集器都会有很大差别。并且一般都会提供用户根据自己的应用特点和要求组合出各个代所使用的垃圾收集器。
以下列出的是JDK7 Update14后的HotSpot虚拟机所包含的所有垃圾收集器
新:
图中,上为年轻代收集器,下为老年代收集器,虚线表示可以搭配使用。其中,上下表示经常搭配使用的。
jdk8默认的垃圾回收:PS + ParallelOld
收集器名称 | 作用分代 | 采用垃圾收集算法 | 特点以及不足 |
Serial收集器 | 新生代 | 复制算法 | 最基本、历史最悠久(JDK1.3.1之前),这是一个单线程的收集器,当该收集器运行时必须暂停其他所有的工作线程,直到它收集结束。 优点:简单高效,拥有很高的单线程收集效率应用:Client模式下的默认新生代收集器 |
ParNew回收器 | 新生代 | 复制算法 | Serial 的多线程版本,使用多线程进行垃圾收集,除了使用多条线程进行垃圾回收之外,其余都与Serial回收器完全一样。 优点:在CPU多的情况下,拥有比Serial更好的效果。单CPU环境下Serial效果更好 应用:许多运行在Server模式下的虚拟机中首选的新生代收集器 |
Paraller Scavenge回收器 | 新生代 | 复制算法 | Parallel Scavenge收集器的目标是达到一个可控制的吞吐量 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间) 应用:适合在后台运算而不需要太多交互的任务 |
Serial Old回收器 | 老年代 | 标记整理算法 | Serial收集器的老年代版本,也是一个单线程的收集器 收集过程:暂停所有线程 应用:主要意义是Client模式下的收集器,如果在Server模式下还有:a. JDK1.5之前的版本中与Parallel Scavenge搭配使用,b. 作为CMS收集器的后背预案在并发收集时使用 |
Parallel Old回收器 | 老年代 | 标记整理 | Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。jdk6才开始提供。 应用:在注重吞吐量及CPU资源敏感的场合,可以优先考虑Parallel Scavenge加Parallel Old收集器 |
CMS回收器(ConcurrentMarkSweep) | 老年代 | 标记清除 | 以获取最短回收停顿时间为目标 收集过程:初始标记-->并发标记-->重新标记-->并发清除 初始标记、重新标记两个步骤仍需要“Stop The World” : 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快;并发标记就是进行GC Roots Tracing的过程;重新标记是为了修正并发标记期间因用户程序继续运作,而导致标记产生变动的那一部分对象的标记记录。 整个过程中耗时最长的是并发标记和并发清除,这两个过程都可以与用户线程一起工作。所以总体上说CMS收集器内存回收过程与用户线程一起并发执行。 缺点: 1,对cpu资源敏感,默认启动的回收线程数是(cpu数量+3)/4,当cpu数较少的时候,会分掉大部分的cpu去执行收集器线程,影响用户,降低吞吐量。 2,无法处理浮动垃圾,浮动垃圾即在并发清除阶段因为是并发执行,还会产生垃圾,这一部分垃圾即为浮动垃圾,要等下次收集。 3,因为使用的是“标记-清除”算法,会产生碎片 |
G1回收器 | 新生代和老年代 |
| 特点:并行与并发、分代收集、空间整合、可预测的停顿 G1将整个java堆(包括新生代和老生代)划分为多个大小固定的独立区域,并跟踪这些区域的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域。 但这样回收有一个问题:对象分配在某个区域中,但并非只被本区域的其他对象引用,而是可以与整个Java堆任意的对象发生引用关系。在做可达性分析的时候,如何避免扫描整个堆呢? 解决:区域之间的对象引用,以及其他收集器中新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描,在Remembered Set中记录对象的引用。 收集过程:初始标记-->并发标记-->最终标记-->筛选回收。与CMS不同的是,在最终标记阶段,需要停顿线程,但是可并发执行;筛选回收阶段,对各个区域的回收价值和成本 进行排序,按照用户所期望的回收时间进行垃圾回收,这个阶段也可以并发执行 |
可以通过jdk自带的命令工具jmap来查看使用的哪种垃圾回收器
jmap -heap VMID
四.垃圾收集器参数总结
注意:jvm参数设置方式有以下3种
-XX:+<option> 开启option参数
-XX:-<option> 关闭option参数
-XX:<option>=<value> 将option参数的值设置为value
参数 | 描述 |
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 收集器时生效 |