jvm内存模型与垃圾回收(下)

上篇地址
jvm内存模型与垃圾回收(上)

1. 垃圾回收相关算法

标记清除-标记整理-复制 这三个看上面的文章

1.1 分代收集算法

将不同生命周期的对象采用不同的收集方式,以便提高回收效率,一般是将Java堆分为新生代和老年代,这样可以根据各个年代的特点使用不同的回收算法,提高垃圾回收效率

1.1.1 增量收集算法

标记清除-标记整理-复制 这三种算法在垃圾回收过程中,用户线程将处于 STW 状态,如果垃圾回收时间过长将严重影响用户体验
增量收集算法基本思想
垃圾收集线程只收集一小片区域的内存空间,接着切换到用户线程执行,依次反复,直到垃圾收集完成
总的来说,增量收集算法的基础仍是传统的标记清除,复制算法,增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作

1.1.2 分区算法

堆空间越大,一次GC时间就越长,为了更好的控制GC产生的停顿时间,将一块打的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿
分区算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间,每一个区间独立使用,独立回收。这种算法的好处是可以空间一次回收多个小区间

2 垃圾回收相关概述

2.1 System.gc()的理解

  • 通过System.gc() 或 Runtime.getRuntime().gc()的调用,会显示触发Full GC,同时对老年代和新生代进行回收
  • 然而 System.gc() 调用附带一个免责声明,无法保证对垃圾收集器的调用

2.2 内存溢出与内存泄漏

  1. 内存溢出: OUtOfMemoryError,没有空闲内存,并且垃圾收集器也无法提供更多内存
    1. Java虚拟机的堆内存设置不够
    2. 代码中大量创建了大对象,并且长时间不能被垃圾收集器收集(存在被引用)
  2. 内存泄漏:只有对象不被程序用到了,但GC又不能回收他们的情况,才叫内存泄漏
    一旦发生内存泄漏,直到耗尽所有的内存,最终出现OOM异常,导致程序崩溃,
    举例:
    1. 单例模式
      单例的生命周期和应用程序是一样长的,所以单例程序中,如果持有堆外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内部泄漏的产生
    2. 一些提供close的资源未关闭导致内存泄漏
      数据库链接,网络链接和io连接必须手动close,否则是不能被回收的

2.3 垃圾回收的并行与并发

  • 并发:指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾回收线程在执行时不会停顿用户程序的运行
  • 并行:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态

2.4 安全点与安全区域

2.4.1 安全点概述

程序执行时并非在所有地方都能停顿下来开始GC,只有在特定的位置才能停顿下来开始GC,这些位置称为 "安全点"
Safe Point的选择很重要,如果太少可能导致GC等待的时间太长,如果太频繁可能导致运行时的性能问题。大部分指令的执行时间都非常短暂,通常会根据是否具有让程序长时间执行的特征为标准。比如:选择一些执行时间较长的指令作为Safe Point,如:方法调用、循环跳转和异常跳转

2.4.2 如何在GC发生时,检查所有线程都跑到最近的安全点停顿下来

  1. 抢先式中断:(目前没有虚拟机采用了)
    首先中断所有线程。如果还有线程不在安全点,就恢复线程,让线程跑到安全点
  2. 主动式中断:
    设置一个中断标志,各个线程运行到 Safe Point的时候主动轮询这个标志,如果中断标志为真,则将自己进行中断挂起

2.4.3 安全区域概述

安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始GC都是安全的

2.5 五种引用

jvm内存模型与垃圾回收(下)_第1张图片

2.5.1 强引用

对于一个普通对象,如果存在的引用关系,就会被回收,反之不回收,如Object obj = new Object();

2.5.2 软引用 Soft Reference(内存不足即回收)

软引用用来描述一些还有用,但非必需的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常

2.5.3 弱引用Weak Reference(一发现即回收)

软引用对象与软引用对象的最大不同就在于,当GC在进行回收时,需要通过算法检查是否回收软引用对象,而对于弱引用对象,GC总是进行回收。弱引用对象更容易、更快被GC回收

2.5.4 虚引用Phantom Reference(对象回收跟踪)

虚引用是所有引用类型中最弱的一个。
持有虚引用的对象和没有引用几乎是一样的,随时都可能被垃圾回收器回收。
它不能单独使用,也无法通过虚引用来获取被引用的对象,当视图通过虚引用的get() 方法获取对象时,总是 null
为一个对象设置虚引用关联的唯一目的在于跟踪垃圾回收过程。比如:能在这个对象被收集器回收时收到一个系统通知

2.5.5 终结器引用 Final Reference

  • 它用以实现对象的 finalize() 方法,也可以称为终结器引用
  • 无需手动编码,其内部配合引用队列使用
  • 在 GC时,终结器引用入队,由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize() 方法,第二次 GC 时才能回收被引用的对象

3. 垃圾回收器

3.1 GC性能指标

  1. 吞吐量:运行用户代码的时间占总运行时间(程序运行时间+内存回收的时间)的比例
  2. 垃圾收集开销:吞吐量的补数,垃圾收集所用时间与总运行时间比例
  3. 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
  4. 收集频率:相对于应用程序的执行,收集操作发生的频率
  5. 内存占用:Java 堆区所占的内存大小
  6. 快速:一个对象从诞生到被回收所经历的时间

3.2 垃圾回收器

3.2.0 七款经典的垃圾收集器

  • 串行收集器:Serial、Serial Old
  • 并行收集器:ParNew、Parallel Scavenge、Parallel Old
  • 并发收集器:CMS、G1

7款收集器与垃圾分代之间的关系

jvm内存模型与垃圾回收(下)_第2张图片
垃圾回收器的组合关系
jvm内存模型与垃圾回收(下)_第3张图片

-XX:+PrintCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)
使用命令行指令:jinfo -flag 相关垃圾回收器参数 进程ID

3.2.1 Serial回收器:串行回收

3.2.1.1 Serial收集器

  • Serial 收集器是 JDK1.3 之前回收新生代唯一的选择
  • Serial 收集器默认新生代垃圾收集器,采用串行回收、复制算法 和 STW 机制对内存进行回收

3.2.1.2 Serial Old 收集器

  • Serial Old 收集器是老年代垃圾收集器,采用串行回收、标记整理算法和STW机制对内存进行回收
  • Serial Old 收集器可以与新生代的Parallel Scavenge收集器配合使用,也可以作为老年代CMS收集器的后背垃圾回收方案
    jvm内存模型与垃圾回收(下)_第4张图片
    优势:与其它收集器的单线程相比简单高效,因为没有线程交互的开销
    使用-XX:+UseSerialGC 参数可以指定年轻代和老年代都使用串行垃圾收集器

3.2.2 ParNew回收器:并行回收

ParNew 可以理解为是 Serial 收集器的多线程版本,除了采用并行回收的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别。ParNew收集器在年轻代中同样采用复制算法、STW机制

jvm内存模型与垃圾回收(下)_第5张图片

  • 对于新生代,回收次数频繁,使用并行方式效率高
  • 对于老年代,回收次数少,使用串行方式节省资源
    除 Serial 外,只有 ParNew GC 能与 CMS 收集器配合工作

-XX:+UseParNewGC 指定年轻代使用并行收集器
-XX:ParallelGCThreads 限制线程数量,默认开启和CPU数据相同的线程数

3.2.3 Paralle Scavenge 回收器:吞吐量优先(JDK8默认)

Parallel Scavenge 收集器同样采用了复制算法、并行回收和 STW机制

  1. Parallel 收集器的出现是否多此一举?
    • 和 ParNew 收集器不同,Parallel Scavenge 收集器的目标是达到一个可控制的吞吐量
    • 自适应调节策略也是Parallel Scavenge和ParNew的一个重要区别
  • 高吞吐量可以高效的利用CPU时间,适合在后台运算而不需要太多交互的任务,比如:批量处理,科学计算等应用程序
  • Parallel 收集器在JDK1.6时提供了用于执行老年代垃圾收集的 Parallel Old收集器,用来代替老年代的Serial Old收集器
  • Parallel Old 收集器采用了标记压缩算法,同样也是基于并行回收和 STW机制

相关参数:

-XX:MaxGCPauseMillis 设置垃圾收集器最大停顿时间(即STW的时间),单位是毫秒
-XX:GCTimeRatio 垃圾收集时间占总时间的比例(=1/(N+1))
       取值范围(0,99).默认 99
-XX:+UseConcMarkSweepGC 设置使用CMS收集器(会自动将-XX:+UseParNewGC打开,即:ParNew+CMS+SerialOld的组合)
-XX:CMSInitiatingOccupanyFraction 设置堆内存使用率的阈值,一旦到达该阈值,便开始进行回收 JDK5默认68,JDK6及以上默认92

3.2.4 CMS回收器:低延迟(JDK9标记未来弃用,JDK14弃用)

JDK1.5时期推出,CMS(Concurrent-Mark-Sweep)收集器
采用标记清除算法,也会STW
CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间
jvm内存模型与垃圾回收(下)_第6张图片

CMS 收集器收集过程分为 4 个主要阶段

  1. 初始标记阶段:该阶段的主要任务仅仅只是标记出 GC Root能**直接关联**到的对象
  2. 并发标记阶段:从GC Root的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长不需要停顿用户线程,可以与垃圾收集线程一起并发运行
  3. 重新标记阶段:并发标记过程中,程序的工作线程会和垃圾收集线程同时运行或交叉运行,为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短
  4. 并发清理阶段:清理删除掉标记阶段判断的已经死亡的对象,释放内存空间

CMS特点与弊端

  1. 优点
    • 并发收集
    • 低延迟
  2. 缺点
    • 会产生内存碎片
    • CMS收集器对CPU资源非常敏感,在并发阶段,它虽不会导致用户线程停顿,但会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低
    • CMS收集器无法处理浮动垃圾。可能出现失败而导致另一次 Full GC的产生。并发标记阶段由于程序的工作线程和垃圾收集线程是同事运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法将这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有及时被回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间
    • CMS回收过程中,还要确保应用程序线程有足够的内存可用,CMS当内存使用率到达某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行要是CMS运行期间预留的内存无法满足程序需要,就会出现一次’Concurrent Mode Failure’失败,这是虚拟机将启动后备预案:启用 SerialOld收集器进行垃圾收集,这样停顿的时间就很长了

CMS收集算法为什么不采用标记整理,而是标记清除

标记整理会变动对象在内存中的地址,而并发清除阶段并发清除线程会与用户线程并发执行,会影响到用户线程使用的资源,所以并发清除更适合CMS

Serial GC、Parallel GC、CMS GC这三个GC有什么不同?

如果想最小化地使用内存和并行开销,请选择 Serial GC
如果想最大化应用程序的吞吐量,请选择 Parallel GC
如果想最小化GC的中断或停顿时间,请选择 CMS GC

3.2.5 G1回收器:区域化分代式(JDK8以后默认)

G1主要针对配备多核CPU及大容量内存的机器,极高概率满足GC停顿时间的同时兼顾高吞吐量的性能特性

已经有前面几个强大的GC了,为什么还要发布 G1?

为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间,同时兼顾良好的吞吐量

为什么名字叫 Garbage First(G1)呢?

  • G1是一个并行回收器,它把堆内存分割为很多不相关的区域(物理上不连续)。使用不同的区域来表示 Eden、幸存区 from、幸存区 to、老年代等
  • G1 有计划避免在整个 Java堆中进行全区域的垃圾收集。G1跟踪各个区域里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的区域
  • 由于这种方法侧重点在于回收垃圾最大量的区间,所以起名 G1(Garbage First)

G1回收器的特点(优势)

  1. 并行与并发
    • 并发性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算机能力。此时用户线程STW
    • 并行性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行
  2. 分代收集
    • G1依然属于分代型垃圾收集器,他会区分年轻代和老年代,年轻代仍然有Eden和幸存区,但从堆的结构上看,它不要求整个Eden区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量
    • 堆空间分为若干个区域,这些区域中包含了逻辑上的年轻代和老年代
    • 和之前的各类回收器不同,它同时兼顾年轻代和老年代
  3. 空间整合
    • CMS:标记清除 算法、内存碎片、若干次GC后进行一次碎片整理
    • G1将内存划分为一个个的区域,内存的回收是以区域作为基本单位Region(区域)之间是复制算法,但整体上实际可看作是标记整理,两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC
    • 可预测的停顿时间模型:也就是能让使用者明确指定一个长度为 M 毫秒的时间片段内,消耗在垃圾回收上的时间不得超过 N 毫秒
      • 由于分区的原因,G1 可选取部分区域进行内存回收,缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制
      • G1跟踪各个区域里面垃圾堆积的价值大小,在后台维护了一个优先队列,每次根据允许的收集时间,优先回收价值最大的区域,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率

G1回收器的缺点

相较于CMS,在用户程序运行过程中,G1垃圾收集产生的内存占用要比CMS高,
小内存应用上CMS的表现大概率优于G1,大内存应用上G1可以发挥优势。

G1回收器的参数

jvm内存模型与垃圾回收(下)_第7张图片

G1回收器的适应场景

jvm内存模型与垃圾回收(下)_第8张图片

分区Region,设置H的原因

对于堆中的大对象,默认直接会分配到老年代,如果它是一个短期存在的大对象,就会对垃圾收集器造成负面影响。于是G1划分了一个Humongous区,用来专门存放大对象,如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储,为了能找到连续的H区,有时候不得不启动Full GC。G1的大多数行为都把H区作为老年代的一部分来看待

G1回收器垃圾回收过程

  1. 年轻代GC(young GC)
  2. 老年代并发标记过程(ConCurrent Marking)
  3. 混合回收(Mixed GC)
  4. (如果需要,单线程、独占式、高强度的Full GC还是继续存在的,针对GC的评估失败提供了一种失败保护机制,即强力回收)
    jvm内存模型与垃圾回收(下)_第9张图片
    jvm内存模型与垃圾回收(下)_第10张图片
G1回收过程一:年轻代GC

程序运行过程中不断创建对象到Eden区,当Eden区内存耗尽时,G1会进行一次年轻代的垃圾回收
YGC时,首先G1停止应用程序的执行(STW),G1创建回收集(Collection Set),回收集指需要被回收的内存分段的集合,年轻代回收过程的回收集包含Eden区和Survivor区的所有内存分段

G1回收过程二:并发标记
  1. 初始化标记
  2. 根区域扫描
  3. 并发标记
  4. 再次标记 STW
  5. 独占清理 STW
    计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。STW
  6. 并发清理
G1回收过程三:混合回收

jvm内存模型与垃圾回收(下)_第11张图片

G1回收过程四:Full GC

导致G1 Full GC的原因:

  1. 回收的时候没有足够的空间来存放晋升的对象
  2. 并发处理过程完成之前空间耗尽

G1回收器优化建议

  • 避免使用 -Xmn 或 -XX:NewRatio等相关参数显示的设置新生代大小固定年轻代的大小会覆盖暂停时间目标
    (YGC会STW,当新生代大小不合理时,就无法达到暂停时间的目标)
  • 暂停时间目标不要太过苛刻
    • G1 的吞吐量目标是90%的应用程序时间和10%的垃圾回收时间
    • 评估G1的吞吐量时,暂停时间目标不要太严苛。目标太过严苛可理解为愿意承受更多的垃圾回收开销,而这些会直接影响到吞吐量

3.3 垃圾回收器总结

垃圾回收器对比图

jvm内存模型与垃圾回收(下)_第12张图片

GC发展阶段

Serial => Parallel => CMS => G1=> ZGC

如何选择垃圾回收器

jvm内存模型与垃圾回收(下)_第13张图片

Class文件内部结构

  1. 魔数
    每个Class文件开头的4个字节的无符号整数称为魔数。魔数是Class文件的标识,用于确定这个文件是否是能被虚拟机接收的合法的Class文件。
    固定值 CAFEBABE 咖啡宝贝。如果Class文件不以CAFEBABE开头,JVM在进行文件校验时会抛出异常
  2. CLass文件版本
    • 紧接着魔数的 4 个字节存储的是 Class 文件的版本号。第1、2个字节的含义是编译的副版本号minor_version,第3、4个字节是编译的主版本号major_version
    • 它们共同构成了 class 文件的格式版本号
  3. 常量池
    • 常量池计数器:常量池中常量的数量是不固定的 ,所以在常量池的入口需要放置一项u2类型的无符号数,代表常量池容量计数值。
    • 常量池表:主要存放字面量和符号引用
  4. 访问标志
    • 用于识别一些类或者接口层次的访问信息,如:是否为 public、是否声明 final等
  5. 类索引,父类索引,接口索引集合
  6. 字段表集合
    • 字段计数器
    • 字段表
  7. 方法表集合
    • 方法计数器
    • 方法表
  8. 属性表集合:指 class 文件所携带的辅助信息,如:class源文件名称
    • 属性计数器
    • 属性表

你可能感兴趣的:(jvm,java,算法)