JAVA 垃圾回收机制(一) --- 对象回收与算法初识

系列文章:

深入Java虚拟机之 – 总结面试篇
深入Java虚拟机之 — JVM的爱恨情仇
JAVA 垃圾回收机制(一) — 对象回收与算法初识
JAVA 垃圾回收机制(二) — GC回收具体实现
深入Java虚拟机之 – 类文件结构(字节码)
深入Java虚拟机之 – 类加载机制

一、概念

这里说的GC回收,指的是 Java 堆的地方, 深入Java虚拟机之 — JVM的爱恨情仇 文章 中,我们知道了程序计算器,虚拟机栈和本地方法栈都是随线程开启,随线程关闭的,因此这几块区域的内存分配和回收都具备确定性。而Java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,只有程序在运行时,才知道创建了哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的就是这部分内存。
而 GC 关注的也就3个点

  • 哪些对象需要回收
  • 什么时候回收
  • 如何回收

二、哪些对象需要回收

怎么判断对象是“存活” 的,还是已经"死亡"呢?主要有以下方法:

2.1 引用计算算法

给对象添加一个引用计算器,每当有一个地方引用它,则加1,当引用失效,则减1;任何时刻计算器为0的对象就是不可能再被使用的。但它很难解决对象之间相互循环引用的问题,所以主流的Java虚拟机都没有采用这种算法。

2.2 可达性分析算法

通过一系列的 “GC Roots” 的对象作为起始点,从这些起始点开始向下搜索,搜索走过的路径被称为引用链(Reference Chain),当一个对象到GC Roots 没有任何引用链项链,即GC Roots 不可达,则证明此对象是不可用的,如下图(java 虚拟机第二版)
在这里插入图片描述
在 Java 虚拟机中,可作为 GC Roots 的对象包含以下几种

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

关于这几个区的介绍,深入Java虚拟机之 — JVM的爱恨情仇

引用判断

无论是引用计数算法,还是可达性分析算法,对象是否存货都跟 “引用” (reference)有关,在JDK1.2 之后,引用可分为以下4个

  • 强引用:直接 new ,如 new Object(); 只要这类强引用还在,对象就不会回收
  • 软引用:(SoftReference类)用来描述一些还有用但非必需的对象;当将来发生内存溢出之前,系统会把这些有软引用的对象列入回收范围中进行二次回收,如果这次回收还没有足够的内存,则内存溢出报异常;
  • 弱引用:(WakeReference类) 被弱引用的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器开始工作时,无论内存是否足够,都会对这些对象进行回收。
  • 虚引用(幽灵引用/幻影引用):(PhantomReference类) 一个虚引用的对象,它的唯一目的就是能在这个对象被收集器回收时收到一个系统通知,即一个对象是否有虚引用,完全不会对其生存时间构成影响。

三、什么时候回收

在可达性分析算法不可达的对象,也不一定"非死不可’,它会经历两次标记,一是当不可达 GC Roots 时,标记一次并筛选,筛选的条件是该对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或者已经执行过 finalize 方法时,则认为此对象会被回收。如下图
在这里插入图片描述
这里的执行,是指虚拟机会触发这个 finalize 方法,但并不会等待它结束,这是因为 finalize 方法执行缓慢,可能会导致 F-Queue其他对象处于等待,甚至是崩溃。
如果在执行 finalize 时,对象重新和其他对象关联上了,则成功拯救了自己
注意:任何一个对象的 finalize() 只会被系统调用一次,下次不会再执行

3.2 回收方法区

上面都是对 Java 堆进行回收,虽说 Java 堆 可以回收70%~95%的空间,但方法区同样可以回收一些资源,方法区主要回收两个部分废弃常量无用的类
废弃常量:
当前系统没有任何一个 String 对象引用这个 “abc” 的常量池,也没有其他地方引用了这个字面量,这时可以判断这个常量是可以废弃回收的;其他常量池中的接口,字段的符号引用也以此类似
无用的类:
无用类的回收,需要满足三个条件

  1. 是该类所有的实例都已经被回收,也就是Java堆中不存在实例
  2. 加载该类的 ClassLoader 已经被回收
  3. 对应的 java.lang.class 对象没有再任何地方被引用,也无法通过反射拿到该类
    当然,这里跟Java堆一样,也只是 "可以回收"了,是否对类进行回收,可以对虚拟机的参数进行设置,这里就不细讲了。

四、如何回收

关于对象的回收,就涉及到 垃圾收集算法了。可以参考 JAVA 垃圾回收机制(二) — GC回收具体实现 第二节

4.1 标记-清除算法

这里的标记指的是对象进过前面第三章我们介绍的那样,已经可以判定就是可以回收的意思;这个算法首先标记处所有需要回收的对象,在标记完成之后统一回收所有被标记的对象。
看似美好,实则不然,主要有以下两个缺点:

  1. 效率问题:标记和清除两个效率都不高
  2. 空间问题:标记清除后,会产生大量空间碎片,在大对象需要分配空间时,找不到内存,从而又触发 GC 操作。

标记和清除的执行过程如下图:
在这里插入图片描述
标记-清除算法是GC回收算法的基础,后面产生的算法都是基于它的缺点改进的

4.2 复制算法

复制算法可以分为等比例的和8:2两种

4.2.1 1:1 比例

基于 标记-清除算法的效率问题,复制算法出现的。
这种算法是把内存分为相等的两块,一块用来存储对象,当GC操作后,把还存留的对象移动到未存对象的那块内存区域,再把使用过的内存清掉。这样每次都对整个半区进行内存回收,就不用担心内存碎片的问题了。执行过程如下图:
在这里插入图片描述
从执行过程来看,要消耗到一半的内存,怎么想都是浪费的,在对象多时,会频繁触发GC

4.2.2 8:1 比例

在说明这些回收机制之前,先说说 新生代和老年代的问题。那么这个分代算法是怎么回事呢?
首先先要理解,新生代和老年代都是一个内存空间,由参数配置,只是可以根据算法,决定对象是在新生代还是在老年代的内存区域!!!
一块内存可以分为3个区域,一个 Eden 和两个 Survivor 区,当对象在 Eden 创建,并经理了第一次 GC 之后仍然存活,并且能被 survivor 区容纳的话,将移到 survivor 区;对象在Survivor 区中“熬过”一次,年龄增加1,当增加到 15 岁(默认,这个阈值可以通过 -XX:MaxTenuringThreshold 设置),就会晋升成老年代的对象。
从这里来看,可以得到两个结论

  • 新生代:对象少,垃圾多
  • 老年代:对象多,垃圾少 (毕竟经历了10几次GC的老油条)

由于上面的 1:1 的铺张浪费,基本主流的 Java 虚拟机都是采用 一个 Eden 和 两个 Survivor 空间。因为新生代的对象 98% 都是"朝生夕死"的,每次都是用 Eden 和 一块 Survivor 空间;
每次GC之后,还存活在 Eden 和 Survivor 空间的对象会被移动到另外一块Survivor上,并清掉 Eden 和刚才用过的 Survivor 空间。当Survivor 空间不够用时,还得依赖其他内存(这里指老年代),进行分配担保。即存活下来的内存不够放新生代上,只有移动到老年代的内存空间上。
举个简单例子:假如虚拟机中设置了新生代的内存大小为10M,老年代的也为10M,Eden 和 Survivor 为 8:1 的关系,那么Eden就只有8M,Survivor 为1M;下面创建4个对象

public static void test(){
	// 假设 _1MB 字符创为 1MB
	byte[] b1 = new byte[2*_1MB];
	byte[] b2 = new byte[2*_1MB];
	byte[] b3 = new byte[2*_1MB];
	byte[] b4 = new byte[4*_1MB];
}

执行 test() ,当要分配 b4 对象时,会执行一次 Minor GC,原因是 Eden 才 6M,被b1,b2,b3填充之后,已经没有数据去填充 b4了,就会触发 GC,而b4没办法,只有移动到老年代的内存区域了。

那我们可以得到,** 对象存活率较高的情况下,效率较低;如果不想浪费 50% 的控件,就需要额外的控件进行分配担保,也就是内存跑到老年代去了,那这个对象没进行GC就跑到老年代去了,那肯定是很占内存的**
所以就有了 标记-整理算法

4.3 标记-整理算法

标记整理算法,是针对老年代的。它与标记-清除算法一样,但不是对相对进行清除,而是移动到另一端,分两个步骤

  1. 标记那些被引用的对象
  2. 将被标记的对象移动按顺序移动到一端,然后清除掉可回收的对象

在这里插入图片描述

4.4、分代收集算法

在商业的虚拟机中,都采用 分代收集算法,根据对象存活周期将内存划分为几块,即新生代和老年代;
在新生代中,每次都有大量对象死去,只有少量活着,就选用复制算法;而老年代的对象存活率比较高,没有额外空间对它进行分配担保,就必须使用 “标记-整理”或者“标记-清理” 进行回收

至此,Java 回收机制(一) – 对象回收与算法初识就讲完啦。。。

参考:

  • 《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版》
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值