JVM - 垃圾回收(垃圾标记阶段算法,内存泄漏与溢出)(2)

12 篇文章 0 订阅

2.垃圾回收相关算法:

2.1.垃圾标记阶段算法:

2.1.1 标记阶段的目的

垃圾标记阶段:主要是为了判断对象是否存活

1.在堆里存放着几乎所有的 Java 对象实例,在 GC 执行垃圾回收之前,首先需 要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为己经死亡的对象,GC 才会在执行垃圾回收时,释放掉其所占用的内存空间,因此这个 过程我们可以称为垃圾标记阶段

2.那么在 JVM 中究竟是如何标记一个死亡对象呢?简单来说,当一个对象已经 不再被任何的存活对象继续引用时,就可以宣判为已经死亡。

3.判断对象存活一般有两种方式:引用计数算法和可达性分析算法

(总结:
主要是来判断哪些对象不再被使用,标记为垃圾对象
判定对象为垃圾的标准:不被任何引用所指向的对象. Object obj = new Objec();
垃圾回收阶段的算法: 引用计数算法(在JVM不被使用)和可达性分析算法.
)

2.1.2引用计数算法

1.引用计数算法(Reference Counting)比较简单,对每个对象保存一个整型 的引用计数器属性。用于记录对象被引用的情况。

2.对于一个对象 A,只要有任何一个对象引用了 A,则 A 的引用计数器就加 1; 当引用失效时,引用计数器就减 1。只要对象 A 的引用计数器的值为 0,即表示 对象 A 不可能再被使用,可进行回收。

3.优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。

4.缺点:

1).它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。

2).每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。

3).引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷,导致在.Java 的垃圾回收器中没有使用这类算法。

在这里插入图片描述

总结:(如果有一个引用指向此对象,那么计数器加1,如果没有引用指向,计数器为0,此时就判定为垃圾.
优点:方便使用,设计简洁
缺点:增加了计数器的存储空间,计数需要消耗时间
导致循环引用问题:好几个对象之间相互引用,但是没有其他引用指向它们,此时垃圾回收器不能回收它们,但是也没有引用指向,这就造成了内存泄漏)

2.1.2可达性分析算法(这是java目前所使用的垃圾标记算法)

可达性分析算法:也可以称为根搜索算法、追踪性垃圾收集

1.相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题, 防止内存泄漏的发生。

2.相较于引用计数算法,这里的可达性分析就是 Java、C#选择的。这种类型的垃圾收集通常也叫作追踪性垃圾收集(Tracing Garbage Collection).

可达性分析实现思路

所谓"GCRoots”根集合就是一组必须活跃的引用,其基本思路如下:

1.可达性分析算法是以根对象(GCRoots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。

2.使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)

3.如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。

4.在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。

在这里插入图片描述

(总结:
循环引用问题,设计简单,运行高效,防止内存泄漏
思路:
从一些活跃引用(GC Roots 根)开始,如果对象被根直接或间接引用,那么此对象不是垃圾,否则标记为垃圾对象.
)

GC Roots 可以是哪些元素?

1.虚拟机栈中引用的对象 比如:各个线程被调用的方法中使用到的参数、局部变量等。

2.本地方法栈内 JNI(通常说的本地方法)引用的对象

3.方法区中类静态属性引用的对象,比如:Java 类的引用类型静态变量

4.方法区中常量引用的对象,比如:字符串常量池(StringTable)里的引用

5.所有被同步锁 synchronized 持有的对象

6.Java 虚拟机内部的引用。

基 本 数 据 类 型 对 应 的 Class 对 象 ,一 些 常 驻 的 异 常 对 象 ( 如 : NullPointerException、OutofMemoryError),系统类加载器。

总结

简单一句话就是,除了堆空间的周边,比如:虚拟机栈、本地方法栈、方法区、 字符串常量池等地方对堆空间进行引用的,都可以作为 GC Roots 进行可达性分析。

概述::(那些引用被用来当做根:
虚拟机栈中引用的对象(方法中引用的对象);
本地方法栈引用的对象;
静态变量所引用的对象;
方法区中常量引用的对象;
被synchronized 当做锁的对象;
虚拟机内部引用的对象.
总结: 栈中引用的(正在使用的)方法区,常量池中(生命周期较长的),被synchronized 当做锁的对象.
)

2.2 对象的 finalization 机制

finalize() 方法机制

对象销毁前的回调函数:finalize();

Java 语言提供了对象终止(finalization)机制来允许开发人员提供对象被销毁 之前的自定义处理逻辑。

当垃圾回收器发现没有引用指向一个对象,即:垃圾回收此对象之前,总会先调 用这个对象的 finalize()方法,一个对象的 finalize()方法只被调用一次。

finalize() 方法允许在子类中被重写,用于在对象被回收时进行资源释放。通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件、套接字和数据库连接等

Object 类中 finalize() 源码
protected void finalize() throws Throwable { }
永远不要主动调用某个对象的 finalize()方法,应该交给垃圾回收机制调用。理由包括下面三点:

1.在 finalize()时可能会导致对象复活。

2.finalize()方法的执行时间是没有保障的,它完全由 GC 线程决定,极端情况下, 若不发生 GC,则 finalize()方法将没有执行机会。

3.一个糟糕的 finalize()会严重影响 GC 的性能。比如 finalize 是个死循环。

概述:final(关键字),finally(代码块),finalize()(方法).
是Object类中的一个方法,在对象被回收之前调用,只调用一次.
finalize()方法机制:
java允许对象在销毁前去调用finalize(),去处理一些逻辑,一般不使用(不建议使用).
不要显示的去调用finalize()方法,在里面写代码一定要慎重:
在 finalize()时可能会导致对象复活。
finalize()是由垃圾回收器调用的,没有固定的时间.
一个糟糕的 finalize()会严重影响 GC 的性能。比如 finalize 是个死循环。

生存还是死亡?

由于 finalize()方法的存在,虚拟机中的对象一般处于三种可能的状态。

如果从所有的根节点都无法访问到某个对象,说明对象己经不再使用了。一般来说,此对象需要被回收。但事实上,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段。一个无法触及的对象有可能在某一个条件下“复活”自己, 如果这样,那么对它立即进行回收就是不合理的。为此,定义虚拟机中的对象可能的三种状态。如下:

可触及的:从根节点开始,可以到达这个对象。

可复活的:对象的所有引用都被释放,但是对象有可能在 finalize()中复活。

不可触及的:对象的 finalize()被调用,并且没有复活,那么就会进入不可触及 状态。不可触及的对象不可能被复活,因为 finalize()只会被调用一次。

以上 3 种状态中,是由于 finalize()方法的存在,进行的区分。只有在对象不可触及时才可以被回收.

具体过程

判定一个对象 objA 是否可回收,至少要经历两次标记过程:

1.如果对象 objA 到 GC Roots 没有引用链,则进行第一次标记。

2.进行筛选,判断此对象是否有必要执行 finalize()方法

如果对象 objA 没有重写 finalize()方法,或者 finalize()方法已经被虚拟机调用过,则虚拟机视为“没有必要执行”,objA 被判定为不可触及的。

如果对象 objA 重写了 finalize()方法,且还未执行过,那么 objA 会被插 入到 F-Queue 队列中,由一个虚拟机自动创建的、低优先级的 Finalizer 线程触 发其 finalize()方法执行。

finalize()方法是对象逃脱死亡的最后机会,稍后 GC 会对 F-Queue 队列 中的对象进行第二次标记。如果 objA 在 finalize()方法中与引用链上的任何一个 对象建立了联系,那么在第二次标记时,objA 会被移出“即将回收”集合。之 后,对象会再次出现没有引用存在的情况。在这个情况下,finalize()方法不会被 再次调用,对象会直接变成不可触及的状态,也就是说,一个对象的 finalize() 方法只会被调用一次。

代码演示 finalize() 方法可复活对象

public class CanReliveObj {

	public static CanReliveObj obj;//类变量,属于 GC Root
	//此方法只能被调用一次
	 @Override
	protected void finalize() throws Throwable {
		//super.finalize();
		System.out.println("调用当前类重写的finalize()方法");
		obj = this;//当前待回收的对象在finalize()方法中与引用链上的一个对象obj建立了联系
	}
	public static void main(String[] args) {
		try {
			obj = new CanReliveObj();
			// 对象第一次成功拯救自己
			obj = null;
			System.gc();//调用垃圾回收器,触发FULL GC  也不是调用后立刻就回收的,因为线程的执行权在操作系统
			System.out.println("第1次 gc");
			// 因为Finalizer线程优先级很低,暂停2秒,以等待它
			Thread.sleep(2000);
			if (obj == null) {
				System.out.println("obj is dead");
			} else {
				System.out.println("obj is still alive");
			}

			System.out.println("第2次 gc");
			// 下面这段代码与上面的完全相同,但是这次自救却失败了
			obj = null;
			System.gc();
			// 因为Finalizer线程优先级很低,暂停2秒,以等待它
			Thread.sleep(2000);
			if (obj == null) {
				System.out.println("obj is dead");
			} else {
				System.out.println("obj is still alive");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}


}

运行结果:
在这里插入图片描述

第一次自救成功,但由于 finalize() 方法只会执行一次,所以第二次自救失败。

2.3.内存溢出与内存泄漏:

内存溢出

内存溢出相对于内存泄漏来说,尽管更容易被理解,但是同样的,内存溢出也是引发程序崩溃的罪魁祸首之一。
由于 GC 一直在发展,所有一般情况下,除非应用程序占用的内存增长速度非 常快,造成垃圾回收已经跟不上内存消耗的速度,否则不太容易出现 OOM 的情况。
大多数情况下,GC 会进行各种年龄段的垃圾回收,实在不行了就放大招,来一次独占式的 Full GC 操作,这时候会回收大量的内存,供应用程序继续使用。
Javadoc 中对 OutofMemoryError 的解释是,没有空闲内存,并且垃圾收集器也无法提供更多内存。

(内存不够用了)

内存泄漏

内存泄漏也称作“存储渗漏”。严格来说,只有对象不会再被程序用到了,但 是 GC 又不能回收他们的情况,才叫内存泄漏。
但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得 很长甚至导致 OOM,也可以叫做宽泛意义上的“内存泄漏”。
尽管内存泄漏并不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中的可用内存就会被逐步蚕食,直至耗尽所有内存,最终出现 OutofMemory 异常, 导致程序崩溃。

注意,这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内 存大小取决于磁盘交换区设定的大小。

常见例子:

单例模式

单例的生命周期和应用程序是一样长的,所以在单例程序中,如果持有对外部 对象的引用的话,那么这个外部对象是不能被回收的,则会导致内存泄漏的产生.

一些提供 close()的资源未关闭导致内存泄漏

数据库连接 dataSourse.getConnection(),网络连接 socket 和 io 连接必须 手动 close,否则是不能被回收的。

(有些对象已经不被使用了,但是垃圾回收机制并不能判定其为垃圾对象,不能将其回收掉,这样的对象越积越多,长久也会导致内存不够用,例如:
与数据库连接完之后,需要关闭连接通道,但是没有关闭;
IO读写完成后,没有关闭)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值