Fork me on GitHub

JVM系列(四)

GC一探究竟(一)

1. 前言

GC,也即是垃圾收集,对几乎所有的JAVA程序员来说,绝对是不陌生的。Java与C语言不同,Java程序员不需要去管内存的释放,而C语言开发则需要程序员去手动释放内存。而正是因为这种自动化的机制,让程序员无法人为的去控制内存的释放,因此可能会出现各种内存溢出,内存泄漏的问题。而这也正是我们需要去了解Java的内存回收机制的必要原因。

在之前的文章中提到了Java运行时数据区域的划分,对于程序计数器,Java栈,本地方法栈这三个区域属于线程私有区域,随线程而生,随线程而灭。因此GC不会去关注这部分内存。而堆和方法区是属于线程共享的区域,这部分的内存和回收都是动态的,只有在运行期才能确定所需的内存。因此GC回收的内存和后续的“内存“分配指的便是这部分内存。

2. 如何成为该被回收的对象?

前面也提到了,Java堆中的对象的回收,我们Java程序员是无法进行操纵的,而我们需要了解的是那对象是什么时候,什么情况下该被回收了。因此,介绍两种判断对象是否该成为被回收对象的算法,分别是引用计数算法和可达性分析算法。

2.1 引用计数算法

2.1.1 原理

给对象添加一个引用计数器,每当对象增加一个引用的时候,计数器的值+1,一个引用失效,则-1,任何时刻,计数器为0的对象就是不可能会再被使用的了。

2.1.2 优点

引用计数算法的实现简单,而且判定的效率很高,只需要给对象增加一个引用计数器,判断引用计数器的值便能确定对象是否该被回收了

2.1.3 缺点

有利就有弊,引用计数法的简单实现无法解决一个问题,那就是对象之间循环引用的问题。举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class A{
A a;

public static void main(String [] args){
A obj1 = new A();
A obj2 = new B();

obj1.a=obj2;
obj2,a=obj1;

obj1=null;
obj2=null;
}
}

如上栗子,我们可以知道,对象1和对象2的引用计数器的值最后是为1的,而根据我们以往的想法,obj1和obj2的引用置空之后,他们指向的对象就无法使用,应该被回收了,因此使用引用计数算法是无法满足的,所以Ho0Spot虚拟机使用的不是这种算法,而是接下来讲的。

2.2 可达性分析算法

2.2.1 原理

该算法是以一系列称为“GC Roots“的对象为起点,然后从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链的时候,也就是GC Roots到这个对象是不可达的时候,这个对象便是不可用的了,因此可以作为回收的对象。

2.2.2 GC Roots

在java语言中,可以作为GC Roots的对象有以下几种

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

2.3 对象由标记到死亡

前面介绍了如何确定一个对象可以被标记为回收对象,那现在介绍下从标记到回收的过程。

要宣告一个对象真正死亡,至少经过两次标记过程,当使用可达性算法分析不可达的对象的时候,对象被第一次标记为不可达对象时候,会进行一次筛选操作,当对象没有覆盖或者调用过一次finalize方法时候,此时定义为对象没必要执行finalize方法。否则会判定为有必要执行finalize方法,而该方法是对象逃离死亡的最后一次机会,只要在该方法中可以使得对象和引用链的任何一个对象关联即可。(如,可将this赋值给某个类变量,或者对象的成员变量)只有这样,在第二次标记的时候才可以被移除“即将被回收的集合” 。否则,第二次标记的时候,基本上没有逃脱的对象真的被回收了。

2.4 方法区的回收

前面讲的GC内容是关于堆中的对象的,而在前言中也提到了,方法区也是GC涉及的一块内存区域。

很多人都认为方法区(HotSpot虚拟机中的永久代)是没有垃圾回收的,但其实还是存在的,只不过在方法区的主要是废弃常量和无用的类。

2.4.1 废弃常量

废弃常量和不可用对象的定义是差不多的,对于方法区的常量,假如外界没有了引用这个常量的引用,那么这个常量便是一个废弃常量,如“字面常量“abc“,假如程序中没有String对象是”abc”,那说明没有任何String对象引用到这个常量,因此这个常量就会被回收。

2.4.1 无用的类

类需要同时满足下面三个条件,才能算是无用的类

  • 该类的所有实例都被回收了,即Java堆中不存在该类的任何实例
  • 加载该类的ClassLoader已经被回收了
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

当满足上述三个条件的类才可以被回收,但是并不是一定会被回收,需要参数进行控制,例如HotSpot虚拟机提供了-Xnoclassgc参数进行控制是否回收。

-------------本文结束感谢您的阅读-------------

本文标题:JVM系列(四)

文章作者:AllenYu

发布时间:2019年01月15日 - 20:01

最后更新:2019年01月15日 - 20:01

原始链接:http://yuzeduan.github.io/2019/01/15/JVM系列-四/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。