开始

GC的历史远比Java久远
Lisp是第一门真正使用内存动态分配和垃圾收集的语言
第三章 垃圾收集器和内存分配策略 - 图1
每一个栈帧分配多少内存,基本上在类结构确定时就已经知道了
线程私有的程序计数器,虚拟机栈,和本地方法栈,内存都是在方法结束或线程结束时,内存就自动回收了。
但是Java堆和方法区则不同,只有在程序运行期间,才知道会创建哪些对象。这部分内存的分配和垃圾回收,都是动态的
image.png

3.2对象已经死了吗?

可达性分析

1、引用计数算法 对象间相互引用的问题难以解决
2、可达性分析算法
通过一系列的gc root的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到gc roots没有任何引用链相连时,此对象是不可用的,可回收。

引用类型

引用类型:强引用,软引用,弱引用,虚引用。

  • 强引用:程序代码中普遍的存在Strong Reference
  • 软引用:描述一些还有用但非必须的对象。系统会在发生内存溢出异常之前把这些对象列进回收范围之中进行第二次回收Soft Reference
  • 弱引用:用来描述非必需对象,比软引用更弱一些,无论内存是否足够,都会回收Weak Reference;

被弱引用关联的对象只能生存到下一次垃圾收集发生之前。

  • 虚引用:最弱的一种引用关系,为一个对象

设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知 Phantom Reference

不可达对象并非非死不可

不可达对象,要被垃圾回收,至少要经过两次标记过程

  • 根据可达性分析法,将不可达的对象标记一下,并且进行一次筛选,
    • 筛选的条件是此对象是否有必要执行 finalize 方法。
    • 当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
  • 被判定为需要执行的对象将会被放在一个队列中进行第二次标记,如果仍然被标记,基本上这个对象就要被回收了

  • finalize()方法,是对象拯救自己的最后一次机会。

  • 只要finalize方法中,这个对象与引用链上的任何一个对象建立关联,就可以避免后续被标记
  • 一个对象的finalize()方法,最多被系统自动调用执行一次
  • 所以一个对象最多,可以自救成功一次
  • 最好忘了finalize()方法的存在

回收方法区

永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类
判定一个无用的类,需要同时满足3个条件:
1:该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
2:加载该类的Class Loader已经被回收。
3:该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

3.3垃圾回收算法

标记-清除算法

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

缺点:标记和清除两个过程的效率都不高;标记清除后会留下大量的不连续内存碎片,导致程序需要分配较大对象时无法找到足够的连续内存而提前触发另一次垃圾回收

复制算法

将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块内存用完了,还存活的对象复制到另外一块上面,然后把已使用过的内存空间一次清除掉。新生代都是用该收集算法回收的。
Eden:Survivor = 8:1
分配担保

标记-整理算法

标记过程和标记清除算法一样,之后让所有存活的对象都向一端移动,然后清理掉端边界以外的内存。
老生代都是用这种方法

分代收集算法

新生代:存活率低,使用复制算法
老生代:存活率高,使用标记整理算法

3.4HotSpot的算法实现

枚举根节点,OopMap的数据结构,快速完成GC Roots的枚举
Stop the World
主流虚拟机,都是准确式GC,并不需要一个不漏地检查完所有的执行上下文和全局的引用位置,通过OopMap数据结构来直接知道哪些地方有对象引用
安全点
安全区域

3.5垃圾回收器

第三章 垃圾收集器和内存分配策略 - 图3
JDK7 将G1收集器正式商用化
枚举根节点,OopMap的数据结构,快速完成GC Roots的枚举

  • Serial收集器 单线程 Client模式下的新生代收集器
  • ParNew收集器 Serial的多线程版本 Server模式下的首选新生代收集器
    • 可以和CMS收集器合作

      并行:多个垃圾收集线程一起工作,但用户线程仍然等待 并发:用户线程和垃圾回收线程一起工作

  • Parallel Scavenge收集器

是JDK8 默认收集器, 吞吐量优先的收集器
拥有自适应调节策略
无法和CMS收集器合作
CMS 等收集器的关注点尽可能地缩短垃圾收集时用户线程的停顿时间
而Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量 (Throughput ) 。
吞吐量:CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。
1、 停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户的体验。
2、 高吞吐量则可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

  • Serial Old 收集器

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。在Client模式下的虚拟机使用

  • Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
可以和Parallel Scavenge一起组建应用组合,提升对吞吐量或CPU资源使用率
在 JDK 1. 6 中才开始提供的。

  • CMS收集器

HotSpot虚拟机第一款真正意义上的并发收集器
CMS (Concurrent Mark Sweep ) 收集器是一种以获取最短回收停顿时间为目标的收集器。符合在互联网站或 B/S或系统的服务端上,停顿时间最短,以给用户带来较好的响应速度和体验。
基于标记-清除算法
第三章 垃圾收集器和内存分配策略 - 图4
运作过程4个包括:
1.初始标记 (CMS initial mark )
2.并发标记(CMS concurrent mark )
3.重新标记 (CMS remark )
4.并发清除(CMS concurrent sweep )
由于整个过程中耗时最长的并发标记和并发消除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
缺点:
1.CMS收集器对CPU资源非常敏感。
2.CMS收集器无法处理浮动垃圾 ( Floating Garbage) ,可能出现 “Concurrent Mode Failure”失败而导致另一次Full GC 的产生。
3.CMS 是一般基于”标记-清除”算法实现的收集器,有空间碎片问题

  • G1收集器

特点:
a) 并行与并发
b) 分代收集
c) 空间整合
d) 可预测的停顿
整体上看基于标记-整理算法实现
G1收集器之所以可以建立可预测的停顿时间模型,是因为他可以有计划的避免在整个java堆中进行全区域的垃圾回收。G1根据各个区域里面的垃圾堆积价值大小维护一个优先列表,每次根据收集时间优先回收价值最大的区域。
步骤:
a) 初始标记(stop)
b) 并发标记
c) 最终标记(stop)
d) 筛选回收(stop)
G1收集器可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收,这是由它能够极力地避免全区域的垃圾收集。G1将整个Java堆 ( 包括新生代、老年代) 划分为多个大小固定的独立区域(Region)时,并且跟踪过些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃极最多的区域。
区域划分及有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率。
RemberedSet 避免全堆扫描

  • ZGC收集器

理解GC日志
DefNew
ParNew
PSyoungGen

3.6 内存分类与回收策略

1, 对象优先在eden区分配
2, 大对象直接进入老年代
虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代中分配。
3, 长期存活的对象将进入老年代
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。
4, 动态对象年龄判定(如果在survivor空间中相同年龄所有对象大小的总和大于survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代)
5, 空间分配担保
担保失败 HandlePromotionFailure
老年代剩余连续空间是否大于历次晋升的平均值—>Minor GC
否则Major GC(Full GC);
新生代有Eden和Survivor区之分。参数SurvivorRatio

Minor GC 和 Full GC 的区别:

  • 新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
  • 老年代 GC(Major GC / Full GC):指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在 ParallelScavenge收集器的收集策略里就有直接进行 Major GC 的策略选择过程)。MajorGC 的速度一般会比Minor GC慢10倍以上。

Young generation 新生代—又分为Eden Space(伊甸园)、Survivor Space(幸存者区)
Tenured Generation 老年代

摘录

https://my.oschina.net/u/175660/blog/351702
JVM内存区域划分 EDEN SPACE、SURVIVOR SPACE、TENURED GEN
HotSpot虚拟机GC算法采用分代收集算法:
1、一个人(对象)出来(new 出来)后会在Eden Space(伊甸园)无忧无虑的生活,直到GC到来打破了他们平静的生活。GC会逐一问清楚每个对象的情况,有没有钱(此对象的引用)啊,因为GC想赚钱呀,有钱的才可以敲诈嘛。然后富人就会进入Survivor Space(幸存者区),穷人的就直接kill掉。
2、并不是进入Survivor Space(幸存者区)后就保证人身是安全的,但至少可以活段时间。GC会定期(可以自定义)会对这些人进行敲诈,亿万富翁每次都给钱,GC很满意,就让其进入了Genured Gen(养老区)。万元户经不住几次敲诈就没钱了,GC看没有啥价值啦,就直接kill掉了。
3、进入到养老区的人基本就可以保证人身安全啦,但是亿万富豪有的也会挥霍成穷光蛋,只要钱没了,GC还是kill掉。
分区的目的:新生区由于对象产生的比较多并且大都是朝生夕灭的,所以直接采用标记-清理算法。而养老区生命力很强,则采用复制算法,针对不同情况使用不同算法。
非heap区域中Perm Gen中放着类、方法的定义,jvm Stack区域放着方法参数、局域变量等的引用,方法执行顺序按照栈的先入后出方式。