垃圾收集器与内存分配策略(读后感三:回收内存)

在前两篇博客中,我们有学习到在Java虚拟机中,内存的分配以及创建对象的过程

我们已经知道,程序计数器,虚拟机栈,本地方法栈都是线程私有的,随着线程的生而生,随着线程的死而消亡,当一个方法或者线程结束时,内存就自然的被回收了。但是在Java堆和方法区中,只有在程序运行时我们才知道要创建哪些对象,所以这部分的内存的分配和回收都是动态的,所以,垃圾收集器所关注的就是部分内存,回收的就是这部分内存。

所以现在存在我们脑海里的疑惑就是:垃圾收集器(GC)如何回收内存?什么时候回收?当然,我们还需要知道哪些内存需要被回收。

一:哪些内存需要被回收?(即如何判断对象已死)

1. 引用计数算法

2 .可达性分析算法

3. 关于引用

4. 最终判断对象是生存还是死亡

引用计数算法: 即 给对象一个引用计数器,每当一个地方引用他,计数器就+1,当引用失效时,计数器值就减1;任何时刻当计数器为0的对象就是不可能再被使用的,就被回收掉。

但是这种算法很难解决对象之间相互循环引用的问题,这样对象的计数器不为一,但也有永不再被使用的可能。

可达性分析算法:基本思路就是 通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何 引用链相连(不可达)时,则证明此对象是不可用的。

如下图所示:

1234

其中,object5分支下面的object6,object7以及object5虽然互相有关联,但是他们到GC Roots不可达,所以也会将他们被判定为是可回收的对象,但是如果用引用计数算法,则object5就不会被判定为可回收的对象。

那么,可以被用来当做GC Roots的对象有哪些?

A. 虚拟机栈(栈帧中的本地变量表)中引用的对象;

B. 方法区中类静态属性引用的对象

C. 方法区中常量引用对象;

D. 本地方法栈中JNI(即一般说的Native方法)引用的对象

 

关于引用:无论是通过引用计数算法还是可达性分析算法,判断对象的死活都与“引用有关”。

我们需要达到这样一种需求:当内存够用时,一些对象可不被回收,当我们的内存空间不够的时候,则自动回收掉一些对象。

所以我们把引用分为以下几种类型:

A. 强引用:不管内存够不够,只要强引用还存在,就永远不会被回收:类似于'Object obj = new Object();'

B. 软引用:当内存空间足够时,不会被回收,但是不够了,在内存溢出发生之前,会对这部分对象 二次回收,如果二次回收内存还不够,才会抛出内存溢出异常

C. 弱引用:无论当前内存空间是否足够,都会回收掉只被弱引用关联的对象

D. 虚引用:虚引用的作用是:当一个对象被垃圾收集器回收的时候,我们可收到一个系统通知。

对象究竟生存还是死亡:即使在可达性分析算法中不可达的对象,也并非是“非死不可”,因为每个对象都有一次“自我拯救”的机会,因为每个对象要被回收,至少要经历两次标记。第一次,如果在可达性分析算法中不可达,那么他被标记一次,进行“牢笼”中,并且进行“审问”,审问的问题是是否其finalize()方法被虚拟机调用过或者已被覆盖,如果是,则视为没必要执行,就进行了第二次标记,如果没有,那么这个“嫌犯”就被放入一个F-Queue队列中,由虚拟机自动创建的一个优先级较低的Finalizer线程执行它,如果这个对象在finalize()方法中执行缓慢,或者发生了死循环,则会导致F-Queue队列中其他对象永远处于等待,甚至整个内存回收系统崩溃。finallize()方法是对象逃脱死亡 的最后一次机会,在finalize方法中,如果对象把this(自己)指向了一个有效值(一个类变量或者对象的成员变量),则自我拯救成功,被移除“即将回收”的集合,如果没有自救成功,则基本就被回收掉了。

package gc;
/**
 * 
 * @author hxl
 * 1.对象可以在被GC时自救(执行finalize方法)
 * 2.自救机会只有一次,因为虚拟机只执行一次一个对象的finalize方法
 */
public class FinalizeEscapeGc{
	public static FinalizeEscapeGc SAVE_HOOK = null;
	
	public void isAlive(){
		System.out.println("我还活着");
	}

	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("finalize方法被执行");
		FinalizeEscapeGc.SAVE_HOOK = this;
	}
	
	public static void main(String[] args) throws InterruptedException {
		SAVE_HOOK = new FinalizeEscapeGc();
		
		//对象第一次成功自救
		SAVE_HOOK = null;
		System.gc();
		//因为finalize()优先级很低,所以先暂停1秒等待他被执行
		Thread.sleep(1000);
		if (SAVE_HOOK != null) {
			SAVE_HOOK.isAlive();
		}else{
			System.out.println("是的,我已经确定死亡");
		}
		
		//对象第二次自救失败
		SAVE_HOOK = null;
		System.gc();
		//因为finalize()优先级很低,所以先暂停1秒等待他被执行
		Thread.sleep(1000);
		if (SAVE_HOOK != null) {
			SAVE_HOOK.isAlive();
		}else{
			System.out.println("是的,我已经确定死亡");
		}
	}
}

添:

我们通常会认为方法区中的内存是没有垃圾收集的,但是对于废弃的常量和无用的类,也是可以(不是必须)被回收的。

判断是否是无用的类有三个条件(满足了,就判定为无用的类):

A. 加载改类的类加载器ClassLoader已经被回收

B. 该类的所有实例(Java堆中)全部被回收

C. 该类所对应的Class对象(java.lang.Class)没有在任何地方被引用(反射)

 

在下一篇文章中,我们将要学习的是垃圾收集算法,也就是 垃圾收集如何进行回收的。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值