jvm学习笔记1:垃圾收集算法

写在前面

本文主要介绍以下几个内容:

  • 怎么确定那些内存是垃圾: 引用计数法、可达性分析
  • 什么时候回收垃圾:
  • 怎么回收垃圾: 复制算法、标记-标记清除算法、标记整理算法、分代收集算法、分区收集算法、增量收集算法

1.怎么确定垃圾

1.1 垃圾标记的两种算法

算法定义优点缺点
引用计数算法给对象添加一个计数器,每当有地方引用这个对象,计数器加1;引用失效时,计数器减1;计数器为0的对象就是不可用的。判定简单,效率较高无法解决对象之间的相互循环引用问题
可达性分析算法通过一系列的“GC Roots” 的对象作为起始点,从这些节点向下搜索,搜索所走过的路径成为引用连(Reference Chain),当一个对象到GC Roots没有任何引用链,则证明这个对象是不可用的。

1.2 GC Roots的对象:

  • 虚拟机栈(栈帧中局部变量表)引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(也就是Native方法)引用的对象

1.3 生存还是死亡,finalize()方法

即使在可达性分析算法中不可达的对象,也并不是非死不可的,要宣告一个对死亡,至少经过两次标记过程,如下图。

标记过程图示

2.怎么回收垃圾

2.1回收垃圾的几种算法

算法介绍

算法定义优点缺点备注
标记-清除算法 (Mark-Sweep)分为标记(前文介绍的引用计数算法和可达性分析算法)和清除(收集器遍历堆内存的对象,如果对象的对象头没有标记为可达对象,则将其回收)两个阶段简单,最基础的算法1.效率不算高(标记需要递归根节点 ,清除需要遍历堆中的对象);
2.并且清除后会产生大量不连续的内存碎片,需要维护一个空闲列表
为什么不在标记阶段直接进行清除:因为标记阶段标记的是存活对象,而清除的是未存活对象,在标记阶段无法知道。
复制算法(Copying)将内存按容量大小分为大小相等的两块,每次只使用其中一块。当这块的内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。再交换两块空间的角色。1.没有标记和清除过程,实现简单,运行高效;
2.复制的对象可以保证空间连续,没有内存碎片化的问题
1.如果对象存活率高,效率将变低;
2.每次只能使用一半空间,空间利用率低 ;
3.对于G1这种拆分为多个Region的GC?
为什么没有标记过程:因为在可达性分析阶段就能确定那个对象是存活对象
标记-压缩算法 (Mark-Compact)标记同上,整理:让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。1.解决了复制算法的内存利用率问题;
2.解决了标记-清除算法的内存碎片化问题
效率最低
分代收集算法将Java堆分为新生代和老年代,根据每个区域的特点选择不同的收集算法。在新生代,90%的对象朝生夕死,只有少数对象存活,选用复制算法。老年代对象存活率高。没有额外的空间进行分配担保,采用标记-清除或者标记-压缩算法。
增量收集算法如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次,垃圾收集线程值收集一小片区域的内存,接着切换到应用程序线程,依次反复,直到垃圾收集完成。减少了单次STW的时间,提高用户体验线程切换和上下文转换的消耗,会使得垃圾回收的成本上升,造成系统的吞吐量下降将总的收集量一部分一部分的去执行
分区收集算法将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收可以控制每次GC的停顿时间将总的内存空间分为小分区,一次可控的去收集多少个小区间。

算法对比:

算法/比较项标记-清除算法复制算法标记整理算法
速度次之最快最慢
空间开销小(内存碎片)小(无内存碎片)
对象移动
  • 标记-清除算法
    注:清除并不是将对象的内存空间清空,只是将这个可回收对象的地址加入空闲列表。(类似于电脑的格式化:格式化后,数据还在,只是将内存地址加入空闲列表,这时候可以让别的数据写进来,如果没有数据来进行覆盖,是可以恢复之前的数据的)
    在这里插入图片描述
  • 复制算法

复制算法图示

  • 标记-整理算法
    标记-整理算法图示

3.什么时候回收(以Hotspot为例)

-内存空间满了则回收(感觉这里漏了点什么,留待以后补充吧)

4.补充几个知识点

4.1 关于引用

Java1.2以前,引用的定义很传统:如果Reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。但这过于 狭隘,一个对象只有引用和没有引用两种状态。比如我想描述这样一类对象:当内存空间还足够时,则能保留在内存中;如果内存不够,则可以抛弃这些对象。

  • 强引用 Strong Reference
    只要引用还在,永不回收

  • 软引用 Soft Reference 内存不足就回收
    内存溢出之前,进行回收。如果回收后还没有足够的内存,才会OOM。

  • 弱引用 Weak Reference 发现即回收
    只能生存到下一次垃圾收集之前。

  • 虚引用 Phantom Reference
    一个对象是否有虚引用,完全不会对其生命周期构成影响,也无法通过取得虚引用来取得一个对象的应用。

4.2 Stop The Word(STW)

定义
在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起。
为什么要STW
可达性分析要求根节点和对象的引用关系不能发生变化,这就要求分析工作必须在同一个快照进行。为了保证一致性,需要STW。(本人自我见解:按上文,那么STW只需要发生在可达性分析的标记阶段就行,那么清除/压缩阶段需要STW么,个人感觉也是需要的。因为清除/压缩阶段需要遍历堆中的对象,进行清除或者移动。这也要求堆的一致性)

4.3 安全点Safe Point 和安全区域Safe Region

准确式GC
当系统停顿下来,不需要一个不漏 的检查完所有执行上下文和全局的引用位置,虚拟机有办法(OopMap数据结构)直接得知那些地方存着对象引用。 --可以快速且准确的完成GC Roots枚举
思考
维护 OopMap是需要耗费内存和Cpu的,不可能为了每条指令都去维护OopMap。那么问题来了,在什么时候维护OopMap,并且这需要考虑什么因素?
安全点Safe Point
只有 在特定的位置(也就是上文说的维护OopMap的指令位置),才能准确的枚举根节点,这些位置称为安全点(Safe Point)。只有在这些位置程序才能暂停。安全点的数目太多会影响性能;太少会让让GC等待是时间太长。
怎么选取安全点
以"是否 具有能让程序长时间执行的特征"为标准进行选取,比如方法调用、循环跳转、异常跳转等。
安全点的停顿方式

  • 抢占式中断:发生GC时,所有线程中断。如果某个线程不在安全点,就让它跑到安全点。
  • 主动式中断:发生GC时,设置一个标志。线程执行到安全点,回去轮询这个标志,为真时就自己中断挂起。

安全区域
指在一段代码片段里,引用关系不会发生变化。在这个区域中的任意位置进行GC都是安全的。(这是为了解决程序不执行时的GC问题,安全点无法满足要求,难道要等程序执行到安全点么,万一代码sleep(10000),难道要等10秒后再进行GC)。
安全区域的进入和退出
在线程执行到安全区域中的代码时,首先标识自己已经进入了安全区域。那样,当GC时,会忽略掉这部分进程,不用中断它。在线程要离开安全区域时,会检查枚举根节点或者整个GC是否已经完成。如果已完成,则继续执行;否者就暂停,知道收到可以离开的信号。

4.4 垃圾回收的并行Parallel与并发Concurrent

并行
多个垃圾收集线程并行工作,但此时用户线程处于等待状态
并发
用户线程和垃圾收集线程同时执行(但不一定是并行,也可能是交替执行),用户程序再继续运行,而垃圾收集程序运行于另一个CPU上。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值