java垃圾回收是个失败的设计,终于分清楚了干垃圾、湿垃圾,Java 垃圾回收机制我却还不会...

文章目录

1. 对象可回收判定

1.1 引用计数算法

1.2 可达性分析算法

2. 垃圾回收算法

2.1 标记-清除算法

2.2 标记-整理算法

2.3 复制算法

3. 分代收集设计

3.1 发生 Minor GC 的情况

3.2 发生 Full GC 的情况

既然提到了城市垃圾分类,来来来,一起做文明环保外来务工小市民。

a98dccb4fb4db5deaa9de0766cdfc3cb.png

1. 对象可回收判定

垃圾回收,判断对象是否存活,非存活的对象可以进行垃圾回收;

引用

如果 reference 类型的对象,存储的值是另外一块内存的起始地址;狭义上称之为引用。

对引用扩充如下概念,强引用、软引用、弱引用、虚引用,引用强度依次减弱;

概念

设计

实现

强引用

垃圾收集器工作时,只要强引用还在,则不会回收强引用关联的对象

Object object = new Object();

软引用

垃圾收集器工作时,会标记软引用关联的对象,如果此次垃圾回收后,会抛内存溢出异常;为避免抛出异常,则二次回收刚才标记的对象。如果内存还是不够,那就直接抛出内存溢出异常。

使用 SoftReference 实现

弱引用

垃圾收集器工作时,无论当前内存是否足够,都会回收掉弱引用关联的对象

使用 WeakReference 实现

虚引用

垃圾收集器工作时,虚引用关联的对象被收集器回收时,可以收到一条系统通知

使用 PhantomReference 实现

可回收判定

引用计数算法

可达性分析算法

设计

对象引用时计数器值为0,认为不存在引用则可回收

不属于GC Roots对象的引用链上的对象,认为不可达则可回收

优势

算法简单,判定效率高

缺陷

不能处理循环依赖

1.1 引用计数算法

设计

给对象加引用计数器,被引用则计数器自增一,引用失效时,计数器自减一;因此对于计数器值为零的对象,可以认为时非存活对象,能够被回收处理;

优点

实现简单,判定效率高

缺陷

不能处理循环依赖,循环依赖,则计数永不为 0

1.2 可达性分析算法

设计

从 GC Roots 的对象节点往下搜索,凡是在引用链上的对象认为时可达到的对象;不可达到的对象则认为不可用,允许垃圾回收处理

GC Roots 对象:包括(1)虚拟机栈中引用的对象(2)本地方法中引用的对象(3)非堆中类静态属性引用的对象(4)非堆中常量引用的对象,

8fba03e2d096cef52b2b7d4288327559.png

图1-1 GC Roots 引用链可达性分析图

2. 垃圾回收算法

算法

设计

实现

缺陷

标记-清除算法

标记可回收对象,清除标记对象

引用计数器来标记对象

存在效率问题和空间问题

标记-整理算法

标记可回收对象,整理存活对象往一端移动,清除边界之外的内存

引用计数器来标记对象

存在效率问题

复制算法

内存空间分为可用内存空间和保留内存空间,仅可用内存空间用来存放存活对象

复制存活对象到保留内存空间,清除已使用内存空间

解决效率问题,存在空间问题

2.1 标记-清除算法

设计

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

缺陷

效率问题:算法效率低;

空间问题:标记清除后会产生大量不连续的内存碎片,对占用较大内存对象进行内存分配时,找不到足够的连续内存会触发再一次的 GC

a42195e31263134269771bb6308eb644.png

图2-1 标记-清除算法示意图

2.2 标记-整理算法

设计

首先标记需要回收的对象,所有存活的对象向一端移动,整理完成后,清理边界之外的内存

e3e04b8618472eb7d08cea405e68992e.png

图2-2 标记-整理算法示意图

2.3 复制算法

设计

将可用内存空间划分相等的两块,每次仅使用一块,可用内存空间,另一块作为保留内存空间。

GC 时将存活的对象复制到保留内存空间,再清除已使用的内存空间。

缺陷

效率高:内存分配时,只移动堆顶指针,顺序分配。

内存成本高:可用内存空间仅使用总内存的一半;(实际上,商业虚拟机并不会给到,可用内存空间 : 保留内存空间 = 1:1)

回收缺陷:用于回收新生代,不适用回收老年代

b197656efd189957e5e3734921fd84f8.png

图2-3 复制算法示意图

扩展

实际上,这种算法被沿用到商业虚拟机上,但是保留内存空间占比不会给到 1/2,成本太高;

比如 HotSpot 设计上 Eden : From Survivor : To Survivor = 8 : 1 : 1;Java 堆细分为Eden 区域,From Survivor 区域,To Survivor 区域。内存使用可达到 90%,每次使用 Eden 和 From Survivor空间;将存活的对象复制到 To Survivor,再清除 Eden 和 From Survivor 已使用的内存空间。

那这里就有问题了,To Survivor 空间不足以存放全部存活对象,怎么办?

Java 堆可分为新生代和老年代,使用复制算法回收新生代,当 To Survivor 空间不足以存放全部存活对象时,多出的新生代会进入老年代,这一条划重点 Full GC 用得到。

欸,此处又有问题了,复制算法回收新生代,那么老年代何时回收?

根据老年代的特点,一般通过标记-整理算法来回收

3. 分代收集设计

设计

按照对象存活周期划分,新生代和老年代;

新生代,垃圾回收时,大量对象死去,少量对象存活,选择复制算法;

老年代,对象存活率高,没有额外的空间堆它进行分配担保,使用标记-整理算法或者标记-清除算法来处理。

永久代也会发生垃圾回收;永久代存储常量、静态变量、已被JVM加载的类;永久代可回收废弃常量和废弃类;常量池中某个常量不存在引用时认为时废弃常量;Java 堆不存在类的实例,加载类的 ClassLoader 已被回收,无法通过反射访问该类时,认为时废弃类

Java 堆存放对象实例,堆也是 GC 发生的内存区域;

Minor GC(新生代 GC),Minor GC 发生在新生代的 GC,新生代 GC 比较频繁,回收速度相对也快

Full GC(老年代 GC),又称 Major JC,Full GC 发生在老年代的 GC,Full GC 速度比 Minor GC 慢 10 倍左右

需要大量连续内存空间的对象一般会直接进去老年代,比如 byte[] 类型的数据。新生代采用复制算法收集内存,这样避免了 Eden 与两个 Survivor 之间发生大量的内存复制。

长期存活的对象会进入老年代;对象在 Survivor 每经历一次 Minor GC,对象的"年龄"就增加 1,默认达到 15 会进入老年代。

3.1 发生 Minor GC 的情况

一般对象在新生代 Eden 区分配内存,Eden 内存空间不够时,JVM 发起一次 Minor GC;

3.2 发生 Full GC 的情况

(1)发生 Minor GC 前,JVM 会校验老年代最大可用连续内存空间是否足够存放新生代所有对象,满足条件,则此次 Minor GC 安全,可进行;如果不满足条件,校验是否允许内存分配担保失败(复制算法 To Survivor 不足以存放全部存活对象时,老年代做分配担保,就可以继续完成垃圾回收),允许就执行 Minor GC;不允许内存分配担保,则执行一次 Full GC,清理下老年代内存空间,Full GC 都解决不了的问题那就内存溢出了。

(2)若某次 Minor GC 存活的对象比较多,远远高于之前每一次 GC 晋升到老年代对象容量的平均大小值时,也会导致担保失败,失败后会重新触发 Full GC。为了避免频繁的 Full GC,一般还是会打开内存分配担保开关,即允许内存分配担保。

JDK 6之后的规则如下伪代码所示

if(老年代连续内存空间 > 新生代对象总大小

|| 老年代连续内存空间 > 历次 GC 晋升老年代的对象容量平均值) {

return Minor GC;

}

return Full GC;

5e47f9e5272605e6b33500e59b9c32df.png

图3-1 买一赠一图

Power By niaonao

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值