-
程序计数器、虚拟机栈、本地方法栈都是随着线程而生,随着线程而灭的,因此不需要过多考虑回收问题。而Java堆和方法区内存分配和回收是动态的,垃圾收集器主要关注的就是这部分内存的分配和回收。
-
判断对象是否存活:
- 引用计数算法: 给对象添加一个引用计数器,引用时加一,失效减一,为零即不再使用。但主流Java虚拟机都没有使用该算法,因为有很多例外情况要考虑,比如对象之间相互循环引用。
- 可达性分析算法: 通过一系列称为"GC Roots"的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,经过的路径称为“引用链”,如果某个对象到GC Roots之间没有引用链,即不可达时,说明该对象不再使用。
-
强引用、软引用、弱引用、虚引用:
-
判断对象死亡要进行两次标记,第一次为可达性分析算法,第二次为筛选是否有必要执行finalize()方法,若有必要,则将该对象放置到一个名为F-Queue的队列中,并在稍后由一条虚拟机建立的、低调度优先级的线程去执行它们的finalize()方法。这里执行只触发开始运行,并不保证结束。如果要在finalize()中拯救自己,只要重新与引用链上的任何一个对象建立关联即可。尽量避免使用finalize()方法。
-
方法区回收: 包括废弃的常量和不再使用的类型。废弃的常量:如果没有任何对象引用常量池中的常量,且虚拟机中没有地方引用这个字面量。不再使用的类型:
-
垃圾回收算法:
-
分代收集理论: 一般至少会把Java堆划分为新生代和老年代,新生代每次回收都有大量对象死去,存活的放到老年代中。
- 跨代引用假说:跨代引用相对于同代引用来说仅占极少数。
-
-
标记-清除算法: 首先标记出存活的或要清除的对象,然后统一回收掉要清除的对象。缺点:① 如果Java堆中有大量对象需要被回收,则标记和清除两个动作的执行效率都随着对象数量增长而降低。② 会产生大量不连续的内存碎片。
-
标记-复制算法: 将内存分为大小相等的两块,每次只用一块,当一块用完时,将还存活的对象复制到另外一块上,剩余的直接清理掉。如果大多数对象都存活,则产生大量内存复制开销。优点:实现简单,运行高效。缺点:空间缩小一半,浪费。(商用Java虚拟机大多采用这种收集算法去回收新生代,但不需按1:1比例)
-
标记-整理算法: 针对老年代特征,标记过程和标记-清除算法一样,但后续是将所有存活对象向内存空间一端移动,然后清理掉边界以外的内存。缺点:移动存活对象并更新引用这些对象的地方将会是一种极为负重的操作,并且移动操作必须全程暂停用户应用程序才能进行。
-
经典垃圾收集器:
两个收集器之间存在连线,说明可以搭配使用。
- Serial收集器: 单线程工作。在进行垃圾收集时,必须暂停其他所有工作线程,即在用户不可知、不可控的情况下把正常工作的线程全都停掉。优点:简单高效,内存消耗小,仍然是HotSpot虚拟机运行在客户端模式下默认的新生代收集器。
- ParNew收集器: 相当于Serial收集器的多线程并行版本。它是服务端模式首选的新生代收集器的原因:除了Serial之外,只有它能与CMS收集器配合工作。JDK9开始逐渐退出历史舞台。
- Parallel Scavange收集器: 同样是新生代+标记-复制算法+多线程并行。它的关注点与其他垃圾收集器不同,其他垃圾收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavange收集器关注点则是吞吐量,即处理器用于运行用户代码时间与处理器总消耗时间的比值。良好的响应速度能提升用户体验,而高吞吐量可以高效利用处理器资源(主要适合在后台运算而不需要太多交互的分析任务)。使用了两个参数精确控制吞吐量:最大垃圾收集停顿时间MaxGCPauseMillis和直接设置吞吐量大小的GCTimeRatio。还有一个开关参数,激活之后就不需要人工指定新生代大小、Eden与Survivor区的比例等细节参数,虚拟机会根据当前情况动态调整,称为垃圾收集器的自适应调节策略。
- Serial Old收集器: Serial收集器的老年代版本,单线程,标记-整理算法。主要供客户端模式下的HotSpot虚拟机使用。
- Parallel Old收集器: Parallel Scavange收集器的老年版本,多线程并发收集,标记-整理算法实现。适用于注重吞吐量或处理器资源稀缺的场合。
- CMS收集器: 并发收集、低停顿。是一种以获取最短停顿时间为目标的收集器。目前很多Java应用都集中在互联网的网站或基于浏览器的B/S系统的服务端上,比较关注服务的响应速度,希望系统停顿时间尽可能短,给用户良好体验,CMS收集器非常符合这类需求。基于标记-清除算法。过程包括:①初始标记:标记GC Roots能直接关联到的对象,速度很快。②并发标记:从直接关联对象遍历整个对象图,耗时较长但不需要停顿用户线程。 ③重新标记:修正并发标记期间因用户程序运行产生变动的记录,速度较快。④并发清除:清理死亡对象,可以与用户线程并发。缺点:①对处理器资源敏感,在并发阶段,虽然不会导致用户线程停顿,但因占用一部分线程导致程序变慢、吞吐量降低。②无法处理“浮动垃圾”,在并发标记和并发清理阶段,用户线程还在运行,垃圾还在产生,CMS无法在这一次收集时处理它们,只能等下一次。③标记-清除算法所导致的空间碎片过多,不得不进行Full GC。
- Garbage First收集器: 面向服务端应用,可以面向堆内存任何部分来组成回收集进行回收,衡量标准不再是属于哪个分代,而是哪块内存中垃圾数量最多,回收效益最大。G1是把连续的Java堆划分成多个大小相等的独立区域(Region),每一个Region都可以根据需要来扮演新生代的Eden、Survivor空间或老年代空间,收集器对扮演不同角色的Region用不同策略处理。G1还有特殊的区域用来存储大对象,大多数行为把这个区域看作老年代的一部分来处理。G1的新生代和老年代不再是固定的了,而是一系列区域的动态集合。具体做法是:让G1收集器去跟踪每个Region垃圾价值的大小,在后台维护一个优先级列表,每次根据用户设置允许的停顿时间,优先处理价值大的Region,从而保证在有限时间内尽可能高的收集效率。缺点:①跨Region的引用对象(使用记忆集),G1的记忆集本质上是一个哈希表。②收集线程和用户线程互不干扰问题。回收过程:
- G1相比于CMS:与CMS的标记-清除算法不同,G1从整体上是基于标记-整理算法,局部上即两个Region之间又是基于标记-复制算法,意味着G1运作期间不会产生内存碎片,但在用户程序运行过程中,G1无论是垃圾收集产生的内存占用还是程序运行时的额外执行负载都比CMS要高。一般在小内存应用上CMS更优,大内存应用G1更优。
- 低延迟垃圾收集器: 衡量垃圾收集器的三个重要指标:内存占用、吞吐量、延迟。
- Shenandoah收集器: 和G1一样基于Region并优先处理回收价值大的Region,但管理堆内存方面有很多不同之处:①支持并发的整理算法。②默认不使用分代收集。③摒弃了记忆集,采用名为“连接矩阵”的全局数据结构来记录跨Region的引用关系。Shenandoah收集器的工作过程?
- ZGC收集器: 基于Region内存布局,不设分代,使用了读屏障、染色指针和内存多重映射等技术实现的,可并发的,标记-整理算法的,以低延迟为首要目标的垃圾收集器。技术&运作过程?
- 垃圾收集器日志:
- 查看GC基本信息
- 查看GC详细信息:
- 查看GC过程中用户线程并发时间以及停顿时间: