JVM 垃圾回收(1)《根对象/四种引用》

本文介绍了JVM垃圾回收的两种主要算法:引用计数法和可达性分析算法,以及四种引用类型:强引用、软引用、弱引用和虚引用。引用计数法在处理循环引用时存在问题,而可达性分析算法通过确定根对象来判断对象是否可回收。在Java中,根对象包括虚拟机栈、方法区中的静态属性和常量、本地方法栈中的对象。文章详细解释了每种引用类型的特性和使用场景,并通过实例展示了如何利用MAT工具分析GC Roots。此外,文章还探讨了软引用在内存不足时的回收策略,以及弱引用和虚引用的回收特点,强调了终结器引用配合引用队列在释放资源中的作用。
摘要由CSDN通过智能技术生成

如何判断对象可以回收

有两个算法,一个是引用计数法,还有一个是可达性分析算法

引用计数法

引用计数指只要一个对象被其他变量所引用,那我就让这个对象他的计数加一,如果他被引用了两次,那就让他的计数变成2。如果某一个变量不在引用它,那么让他的计数减一。当这个对象他的引用计数变为0的时候,意味着没有人再引用它了,那么他就可以作为一个垃圾进行回收。

虽然引用计数法听起来很好,但他存在一个重要的弊端,就是循环引用的问题。比如,A对象现在引用了B对象,那么B对象的引用计数现在是1,反过来B对象同时也引用了A对象,那A对象的引用计数也是1。但没有第三者引用他们两,那是不是意味着他们两可以被垃圾回收呢,答案是不行。因为他们各自的引用计数都是1,这样就造成了内存上的泄露。Java虚拟机没有采用引用计数法,而是采用了可达性分析算法

可达性分析算法

可达性分析算法他首先要确定一系列根对象,根对象是可以暂时理解为肯定不能当成垃圾被回收的对象。

在垃圾回收之前,首先会对堆中的所有对象进行一遍扫描,然后看看每一个对象是不是被根对象直接或者间接的引用,如果是,那么这个对象就不能被回收,反之,如果一个对象没有被根对象直接或间接的引用,那么这个对象可以作为垃圾将来可以被回收。

通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时[从 GC Roots 到这个对象不可达]时,证明此对象不可用。

根对象

在Java语言中,可作为GC Roots的对象包含以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。就是在方法中new的对象
  • 方法区中静态属性引用的对象用static修饰的全局的静态的对象
  • 方法区中常量引用的对象static final关键字
  • 本地方法栈中(Native方法)引用的对象引用Native方法的所有对象

可以用eclipse提供的Memory Analyzer(MAT)工具查找可以作为GC Root的对象。(网友1:idea可以使用JProfiler

// 演示GC Roots
public class Demo {
   
	public static void main(String[] args) throws InterruptedException, IOException {
   
		List<Object> list1 = new ArrayList<>();
		list1.add("a");
		list1.add("b");
		print(1);
		System.in.read();// 运行后此处暂停

		list1 = null;
		print(2);
		System.in.read();
		print("end...");
	}
}

对list1对象的有值和没值的前后进行对比,看看到底哪些对象能成为根对象。

运行之后,用jmap工具去查看堆内存的占用情况(在终端的Java该文件目录下执行jmap命令)。MAT也不能独立玩,得先用jmap把内存的快照给抓下来,抓下来以后,才能用MAT工具对他进行进一步的分析。jmap如果想把当前的内存状态抓去成一个文件,他需要配合几个参数来用。先终端执行jps命令,先查一下该Java程序的进程ID(比如这里是21384),然后再运行jmap -dump:format=b,live,file=1.bin 21384 ,dump参数不是说要查看堆内存的那些占用情况了,而是要把堆内存的当前运行时的那些状态抓取成转储一个文件,format的意思是要转储成的文件格式,b表示他是二进制格式,live参数的意思是,快照的时候我只关心那些存活对象,即已经被垃圾回收掉的把他都过滤掉,而且live这个参数一加他就会抓快照之前进行一次垃圾回收,因为你进行垃圾回收之后,才会确定哪些是存活对象,所以这个live会主动触发一次垃圾回收,会把整个堆全部一次进行垃圾回收,最后一个参数file是决定将来要把这个抓取的内存快照存为哪个文件,比如存到当前目录下的1.bin文件,最后再跟上进程ID。

执行该命令后,就会提示Heap dump file created。当然,上面这是在list1引用被null之前抓取的,因为运行程序后在System.in.read()这里停顿了。

接着在控制台回车一下,就会接着执行下面的代码了,即list1被null,然后输出2之后再暂停。再次执行jmap -dump:format=b,live,file=2.bin 21384 (转储文件改为2.bin)这个第二个状态后,进行对比。这样的话,前后两个状态各存储在1.bin和2.bin了。

然后回到MAT工具,File->Open Heap Dump->1.bin,再把2.bin也打开。在1.bin界面里打开最右侧(长得有点像数据库)的图标,并选择Java Basics->GC Roots,选择这个就可以查看到当前抓去的快照里GC Root都有哪些了。之后,可以看到他把根对象分成了四个部分,System Class, Native Stack, Thread, Busy Monitor

比如,System Class都是系统类(里面很多类),也就是启动类加载器加载的类,就是那些核心的类,运行期间肯定会用到的,他们都能够作为GC Root对象,由于他们是核心类,所以不会被垃圾回收。

这里的Native Stack是操作系统的方法执行时,他所引用的一些Java对象,也是可以作为根对象,也是不能被垃圾回收掉的。

接着先看Busy Monitor,Java中有同步锁机制,就是那个syn什么什么的关键字,syn什么什么的关键字如果对一个对象加了锁,被加锁的对象就不会被当成垃圾,如果被回收了,将来就不知道谁来解锁了,所以这种正在加锁的对象(Monitor),他们也是可以作为根对象,他们所在引用的其他对象也是需要被保留的。

然后第三类是Thread,就是一些活动线程,比如活动线程中使用的对象是不能当做垃圾的,线程正在运行,那我把他启动的一些对象,或他需要的对象当成垃圾回收了,那没法继续往下执行了。我们知道线程运行时他都有一次次的方法调用,每次方法调用都会产生一个栈帧,也就是栈帧内所使用的一些东西,他们可以作为根对象。比如,展开Thread部分后,里面就能看到主线程(main thread),把主线程部分点击展开,展开之后可以看到一堆主线程栈帧内用到的一些变量的情况,那他们用到的一些局部变量所引用的对象,都可以作为根对象。

(上面代码中,list1就是栈帧里的局部变量,而它引用的对象[new ArrayList<>()]是在堆里的。)即根对象指的是堆中的new ArrayList<>(),而不是前面的这种list1。在当前活动线程(main)执行过程中,局部变量所引用的对象是可以作为根对象的。方法参数也是一样,比如String[] args,他(args)引用的String[]对象也是根对象。

然后切换到2.bin界面,看看第二个状态下的情况。也是一样的,打开最右侧(长得有点像数据库)的图标,并选择Java Basics->GC Roots,同样,我只关心第三块(Thread)部分,展开后找到主线程后展开,这下就看不到ArrayList对象了。因为list1这个局部变量被置为null了,即他不再引用上面的ArrayList对象了,由于刚才执行命令时加了live参数,而这个参数会主动进行一次垃圾回收,所以把刚刚这个不再被引用的ArrayList对象给回收掉了。所以在根对象列表中找不到他了。所以以后要想查看根对象的话,可以用MAT可视化工具去查看哪些对象是根对象

四种引用(实际上常用的有五种

强引用

其实我们平时在用的所有的引用都属于强引用。比如,new了一个对象,同等号赋值给了一个变量,那么这个变量强引用了刚刚的对象。

强引用的特点是,只要沿着GC Root的引用,能够找到他,那么他就不会被垃圾回收。

  • 只有所有GC Roots对象都不通过【强引用】引用该对象,该对象才能被垃圾回收。
软引用

软弱引用跟强应用的一个区别是只要A2、A3这两个对象没有被直接的强引用所引用,那么他们当垃圾回收发生时都有可能被回收掉。

当发生垃圾回收时,回收完了还发现内存不够时,就会把软引用所引用的对象给释放掉,当然前提是没有其他强应用在引用它。

  • 仅有软引用引用该对象时,在垃圾回收后,内存扔不足时会再次触发垃圾回收,回收软引用对象。
  • 可以配合引用队列来释放软引用自身。
/* 
演示软引用
-Xmx20m -XX:+PrintGCDetails -verbose:gc

这个例子中,实现设置了虚拟机参数,即堆内存大小是20Mb(-Xmx20m)。
*/
public class Demo {
   
	private static final 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值