一文夯实垃圾网络的理论基础

打印 上一主题 下一主题

主题 883|帖子 883|积分 2649

如何判断一个引用是否存活

引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时间计数器为 0 的对象就是不可能再被使用的。
长处:可即刻采取垃圾,当对象计数为0时,会立刻采取;
弊端:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放。JVM不用这种算法

可达性分析算法

通过 GC Root 对象为起点,从这些节点向下搜索,搜索所走过的路径叫引用链,当一个对象到 GC Root没有任何的引用链相连时,说明这个对象是不可用的。


  • JVM中的垃圾采取器通过可达性分析来探索全部存活的对象
  • 扫描堆中的对象,看可否沿着GC Root对象为起点的引用链找到该对象,如果找不到,则表示可以采取
GC Root的对象有哪些?


  • 虚拟机栈(栈帧中的本地变量表)中引用的对象,例如各个线程被调用的方法栈用到的参数、局部变量或者临时变量等。 
  • 方法区中类静态属性引用的对象或者说Java类中的引用范例的静态变量。
  • 方法区中常量引用的对象或者运行时常量池中的引用范例变量。
  • 本地方法栈中JNI(即一样平常说的Native方法)引用的对象
  • JVM内部的内存数据结构的一些引用、同步的监控对象(被修饰同步锁)。
方法区的采取

因为方法区主要存放永久代对象,而永久代对象的采取率比新生代低很多,因此在方法区上举行采取性价比不高。
主要是对常量池的采取和对类的卸载。
在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGi 这类频繁自定义 ClassLoader 的场景都必要虚拟机具备类卸载功能,以保证不会出现内存溢出。
类的卸载条件很多,必要满意以下三个条件,并且满意了也不肯定会被卸载:

  • 该类全部的实例都已经被采取,也就是堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被采取。
  • 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
可以通过 -Xnoclassgc 参数来控制是否对类举行卸载。
finalize()

finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。(Java 9中已弃用)
当一个对象可被采取时,如果必要执行该对象的 finalize() 方法,那么就有可能通过在该方法中让对象重新被引用,从而实现自救。自救只能举行一次,如果采取的对象之前调用了 finalize() 方法自救,背面采取时不会调用 finalize() 方法。
引用范例

四个引用的特点:

  • 强引用:gc时不会采取
  • 软引用:只有在内存不够用时,gc才会采取
  • 弱引用:只要gc就会采取
  • 虚引用:是否采取都找不到引用的对象,仅用于管理直接内存
强引用

平常常见的
  1. Object object = new Object();
复制代码
只要一个对象有强引用,垃圾采取器就不会举行采取。即便内存不够了,抛出OutOfMemoryError异常也不会采取。因此强引用是造成java内存走漏的主要缘故原由之一。 对于一个平凡的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相 应(强)引用赋值为 null,就是可以被垃圾网络的了,详细采取时机还是要看垃圾网络策略。
  1. /**
  2. * 一个对象
  3. * 重写finalize方法,可以知道已经被回收的状态
  4. */
  5. public class OneObject {
  6.     @Override
  7.     protected void finalize() throws Throwable {
  8.         System.out.println("啊哦~OneObject被回收了");
  9.     }
  10. }
  11. /**
  12. * 强引用例子
  13. */
  14. public class ShowStrongReference {
  15.     public static void main(String[] args) {
  16.         // 直接new一个对象,就是强引用
  17.         OneObject oneObject = new OneObject();
  18.         System.out.println("输出对象地址:" + oneObject);
  19.         System.gc();
  20.         System.out.println("第一次gc后输出对象地址:" + oneObject);
  21.         oneObject = null;
  22.         System.gc();
  23.         System.out.println("置为null后gc输出对象地址:" + oneObject);
  24.     }
  25. }
  26. //输出:
  27. 输出对象地址:com.esparks.pandora.learning.references.OneObject@72ea2f77
  28. 第一次gc后输出对象地址:com.esparks.pandora.learning.references.OneObject@72ea2f77
  29. 置为null后gc输出对象地址:null
  30. 啊哦~OneObject被回收了
复制代码
软引用

特点:软引用通过java.lang.SoftReference类实现。只有在内存不够用时,gc才会采取
软引用的生命周期比强引用短一些。只有当 JVM 认为内存不敷时,才会去试图采取软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清算软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)团结使用,如果软引用所引用的对象被垃圾采取器采取,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被采取。如果队列为空,将返回一个null;否则该方法返回队列中前面的一个Reference对象。
  1. SoftReference<OneObject> oneObjectSr = new SoftReference<>(new OneObject());
复制代码

当内存充足的时间,垃圾采取器不会举行采取。当内存不够时,就会采取只存在软引用的对象释放内存。
常用于本地缓存处理。
  1. /** * 软引用 * 内存不够了就会采取 * 注意,运行时必要保证heap大小为35m,即小于实行中全部对象的大小,才能触发gc * -Xmx35m * */public class ShowSoftReference {    public static void main(String[] args) {        // 我们必要通过SoftReference来创建软引用        SoftReference<OneObject> oneObjectSr = new SoftReference<>(new OneObject());        // 我们这里创建一个大小为20m的数组        SoftReference arraySr = new SoftReference(new byte[1024 * 1024 * 20]);        System.out.println("软引用对象oneObjectSr的地点:" + oneObjectSr);        System.out.println("通过oneObjectSr关联的oneObject对象的地点:" + oneObjectSr.get());        System.out.println("数组的地点:" + arraySr);        System.out.println("通过arraySr关联的byte数组的地点:" + arraySr.get());        System.gc();        System.out.println("正常gc一次之后,oneObject对象并没有采取。地点" + oneObjectSr.get());        // 再创建另一个大小为20m的数组,这样heap就不够大了,从而系统自动gc。如果依旧不够,会把已有的软引用关联的对象都采取掉。        System.out.println("创建另一个大小为20m的数组otherArray");        byte[] otherArray = new byte[1024 * 1024 * 20];        System.out.println("otherArray的地点:" + otherArray);        // gc后,软引用对象还在,但是通过软引用对象创建的对象就被采取了        System.out.println("现在软引用对象oneObjectSr的地点:" + oneObjectSr);        System.out.println("通过oneObjectSr关联的oneObject对象的地点:" + oneObjectSr.get());        System.out.println("现在数组的地点:" + arraySr);        System.out.println("现在arraySr中关联的byte数组的地点:" + arraySr.get());    }}
复制代码
执行代码,可以看到以下输出:
  1. 软引用对象oneObjectSr的地址:java.lang.ref.SoftReference@4f8e5cde
  2. 通过oneObjectSr关联的oneObject对象的地址:test.niuke.Test1$OneObject@504bae78
  3. 数组的地址:java.lang.ref.SoftReference@3b764bce
  4. 通过arraySr关联的byte数组的地址:[B@759ebb3d
  5. 正常gc一次之后,oneObject对象并没有回收。地址test.niuke.Test1$OneObject@504bae78
  6. 创建另一个大小为20m的数组otherArray
  7. otherArray的地址:[B@484b61fc
  8. 现在软引用对象oneObjectSr的地址:java.lang.ref.SoftReference@4f8e5cde
  9. 通过oneObjectSr关联的oneObject对象的地址:null
  10. 现在数组的地址:java.lang.ref.SoftReference@3b764bce
  11. 现在arraySr中关联的byte数组的地址:null
复制代码
弱引用

特点:弱引用通过WeakReference类实现。只要gc就会采取
弱引用的生命周期比软引用短。在垃圾采取器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间充足与否,都会采取它的内存。由于垃圾采取器是一个优先级很低的线程,因此不肯定会很快采取弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)团结使用,如果弱引用所引用的对象被垃圾采取,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
  1. WeakReference<OneObject> oneObjectWr = new WeakReference<>(new OneObject());
复制代码

只要发生gc,就会采取只存在弱引用的对象。
常用于Threadlocal。
  1. /**
  2. * 弱引用
  3. * 只要gc就会回收
  4. */
  5. public class ShowWeakReference {
  6.     public static void main(String[] args) {
  7.         // 我们需要通过WeakReference来创建弱引用
  8.         WeakReference<OneObject> objectWr = new WeakReference<>(new OneObject());
  9.         System.out.println("弱引用objectWr的地址:" + objectWr);
  10.         System.out.println("弱引用objectWr关联的oneObject对象的地址:" + objectWr.get());
  11.         System.gc();
  12.         // gc后,弱引用对象还在,但是通过弱引用对象创建的对象就被回收了
  13.         System.out.println("gc后,弱引用objectWr的地址:" + objectWr);
  14.         System.out.println("gc后,弱引用objectWr关联的oneObject对象的地址:" + objectWr.get());
  15.     }
  16. }
复制代码
执行代码,可以看到以下输出:
  1. 弱引用objectWr的地址:java.lang.ref.WeakReference@72ea2f77
  2. 弱引用objectWr关联的oneObject对象的地址:com.esparks.pandora.learning.references.OneObject@33c7353a
  3. gc后,弱引用objectWr的地址:java.lang.ref.WeakReference@72ea2f77
  4. gc后,弱引用objectWr关联的oneObject对象的地址:null
  5. 啊哦~OneObject被回收了
复制代码
虚引用

特点:虚引用也叫幻象引用,通过PhantomReference类来实现。是否采取都找不到引用的对象,仅用于管理直接内存
无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时间都可能被垃圾采取器采取。虚引用必须和引用队列 (ReferenceQueue)团结使用。当垃圾采取器准备采取一个对象时,如果发现它还有虚引用,就会在采取对象的内存之前,把这个虚引用加入到与之关联的引用队列中。 程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾采取。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被采取之前采取一些程序行动。
  1. private ReferenceQueue<OneObject> queue = new ReferenceQueue<>();
  2. PhantomReference<OneObject> oneObjectPr = new PhantomReference<>(new OneObject(), queue);
复制代码
无论是否gc,其实都获取不到通过PhantomReference创建的对象。

其仅用于管理直接内存,起到通知的作用。
这里补充一下背景。因为垃圾采取器只能管理JVM内部的内存,无法直接管理系统内存的。对于一些存放在系统内存中的数据,JVM会创建一个引用(类似于指针)指向这部分内存。

当这个引用在采取的时间,就必要通过虚引用来管理指向的系统内存。这里还必要依赖一个队列来实现。当触发gc对一个虚引用对象采取时,会将虚引用放入创建时指定的ReferenceQueue中。之后单独对这个队列举行轮询,并做额外处理。

[code]/** * 虚引用 * 只用于管理直接内存,起到通知的作用 */public class ShowPhantomReference {    /**     * 虚引用必要的队列     */    private static final ReferenceQueue QUEUE = new ReferenceQueue();    public static void main(String[] args) {        // 我们必要通过 PhantomReference来创建虚引用        PhantomReference objectPr = new PhantomReference(new OneObject(), QUEUE);        System.out.println("虚引用objectPr的地点:" + objectPr);        System.out.println("虚引用objectPr关联的oneObject对象的地点:" + objectPr.get());        // 触发gc,然后检查队列中是否有虚引用        while (true) {            System.gc();            Reference

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

羊蹓狼

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表