1、Java引用的种类

Java是面向对象的编程语言,一个Java程序往往需要创建大量的Java类,然后对各Java类创建大量的Java对象,再调用这些Java对象的属性和方法来操作它们。

程序员需要通过关键字new创建Java对象,既可以视作为Java对象申请内存空间,JVM会在堆内存中为每个对象分配空间;当一个Java对象失去引用时,JVM的垃圾回收机制会自动清除它们,并回收它们所占用的空间。

Java内存管理包括内存分配(创建Java对象时)和内存回收两个方面(回收Java对象时)。这两方面的工作都是由JVM自动完成的,因此降低了Java程序员的学习难度,以至于使容易忽视内存分配的问题。但这两方面的工作也加重了JVM的工作,从而使Java程序运行较慢。

1、1 对象在内存中的状态

对于JVM的垃圾回收机制来说,是否回收一个对象的标准在于:是否还有引用变量引用该对象?只要有引用变量引用该变量,垃圾回收机制就不会回收它。也就是说,当Java对象被创建出来之后,垃圾回收机制会实时地监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等。当垃圾回收机制实时地监控到某个对象不再被引用变量所引用时,垃圾回收机制就会回收它所占用的空间。

基本上,可以把JVM内存中的对象引用理解成一种有向图,把引用变量、对象都当成有向图的顶点,将引用关系当成图的有向边,有向边总是从引用端指向被引用的Java对象。因为Java的所有对象都是由一条条线程创建出来的,因此可以把线程对象当成有向图的起始顶点。

对于单线程程序而言,整个程序只有一条main线程,那么该图就是以main进程为顶点的有向图。在这个有向图中,main顶点可达的对象都处于可达状态,垃圾回收机制不会回收它们;如果某个对象在这个有向图中处于不可达状态,那么就认为这个对象不再被引用,接下来垃圾回收机制就会主动回收它了。

class Node {

Node next;

String name;

public Node(String name) {

this.name = name;

}

}

public class NodeTest {

public static void main(String[] args) {

Node n1 = new Node("一号节点");

Node n2 = new Node("二号节点");

Node n3 = new Node("三号节点");

n1.next = n2;

n3 = n2;

n2 = null;

}

}

上面程序中定义了三个Node对象,并通过合适的引用关系把这三个Node对象组织在一起,下图为JVM中对应的有向图。

从main顶点开始,有一条路径到达“第一个Node对象”,因此该对象处于可达状态,垃圾回收机制不会回收它;从main顶点开始,有两条路径到达“第二个Node对象”,因此该对象也处于可达状态,垃圾回收机制不会回收它;从main顶点开始,没有路径可以到达“第三个Node对象”,因此这个Java对象就变成了垃圾,接下来垃圾回收机制就会回收它。

当一个对象在堆内存中运行时,根据它在对应有向图中的状态,可以把它所处的状态分成如下三种。

可达状态:当一个对象被创建后,有一个以上的引用变量引用它。在有向图可以从起始顶点导航到该对象,那么它就处于可达状态,程序可以通过引用变量来调用该对象的属性和方法。

可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它将先进入可恢复状态,此时从有向图的起始顶点不能导航到该对象。在这种状态下,系统的垃圾回收机制准备回收该对象所占用的内存。如果系统调用finalize方法重新让一个以上的引用变量引用该对象,则这个对象会再次变为可达状态;否则,该对象将进入不可达状态。

不可达状态:当对象的所有关联都被切断,且系统调用所有对象的finalize方法依然没有使该对象变成可达状态后,这个对象将永久性地失去引用,最后变成不可达状态。只有当一个对象处于不可达状态时,系统才会真正回收该对象所占有的资源。

public class StatusTranfer {

public static void test() {

String a = new String("Java对象1"); //①

a = new String("Java对象2"); //②

}

public static void main(String[] args) {

test();

}

}

当程序执行test方法的①行代码时,代码定义了a变量,并让该变量指向“Java对象1”字符串。该代码执行结束后,“Java对象1”字符串对象处于可达状态。

当程序执行test方法的②行代码后,代码再次定义了“Java对象2”字符串对象,并让a变量指向该对象。此时,“Java对象1”字符串对象处于可恢复状态,而“Java对象2”字符串对象处于可达状态。

一个对象可以被一个方法的局部变量引用,也可以被其他类的类变量引用,或者被其他对象的实例变量引用。当某个对象被其他类的类变量引用时,只有该类被销毁后,该对象才会进入可恢复状态;当某个对象被其他对象的实例变量引用时,只有当引用该对象的对象被销毁或变成不可达状态后,该对象才会进入不可达状态。

对垃圾回收机制来说,判断一个对象是否可回收的标准在于该对象是否被引用。因此引用也是JVM进行内存管理的一个重要概念。为了更好地管理对象的引用,从JDK1.2开始,Java在java.lang.ref包下提供了三个类:SoftReference、PhantomReference和WeakReference,分别代表了对对象的三种引用方法:软引用、虚引用和弱引用。其实,在Java中还有强引用,总共四种对对象的引用。

1、2 强引用

这是Java程序中最常见的引用方式,程序创建一个对象,并把这个对象赋给一个引用变量,这个引用变量就是强变量。当一个对象被一个或一个以上的强引用变量所引用时,它处于可达状态,它不可能被系统垃圾回收机制回收。强引用时Java编程中广泛使用的引用类型,被强引用所引用的Java对象绝不会被垃圾回收机制回收,即使内存非常紧张;即使有些Java对象以后永远也不会被用到,JVM也不会回收强引用所引用的Java对象。

1、3 软引用

软引用需要通过SoftReference来实现,当一个对象只具有软引用时,它可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象;当系统内存空间不足时,系统将会回收它。

软引用通常用于内存敏感的程序中,软引用是强引用很好的替代。当系统内存空间充足时,软引用和强引用没有太大的区别;当系统内存空间不足时,被软引用所引用的Java对象可以被垃圾回收机制回收,从而避免系统内存不足的异常。

1、4 弱引用

弱引用和软引用有点类似,区别在于弱引用所引用对象的生存期更短。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。但是并不是当一个对象只有弱引用时,它就会立即被回收,而是等到系统垃圾回收机制运行时才会被回收。

import java.lang.ref.WeakReference;

public class WeakReferenceTest {

public static void main(String[] args) {

String str = new String("Java对象");

WeakReference wr = new WeakReference(str);

str = null; //切断str引用和"Java对象"字符之间的引用

System.out.println(wr.get()); //①

System.gc(); //强制垃圾回收

System.runFinalization();

System.out.println(wr.get()); //②

}

}

输出结果为:

Java对象

null

当执行str = null;之后,切断了str和“Java对象”字符串对象之间的引用关系,此时只有一个弱引用对象引用字符串对象,程序中①行代码依然可以输出“Java对象”。如果系统垃圾回收机制启动,只有弱引用的对象就会被清理掉。程序中②行代码输出null,这就表明对象已经被清理了。

注意:上面创建“Java对象”字符串对象时,不要使用String str = “Java对象”;代码,因为系统不会缓存这个字符串常量(会使用强引用来引用它),系统则不会回收被缓存的字符串常量。

弱引用具有很大的不确定性,因此每次垃圾回收机制执行时都会回收弱引用所引用的对象,而垃圾回收机制的运行又不受程序员的控制,因此程序获取弱引用所引用的Java对象时必须小心空指针异常。

1、5 虚引用

软引用和弱引用可以单独使用,但虚引用不能单独使用,必须和引用队列联合使用。单独使用虚引用没有太大的意义。虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序可以通过检查与虚引用关联的引用队列中是否已经包含指定的虚引用,从而了解虚引用所引用的对象是否即将被回收。

引用队列由java.lang.ref.ReferenceQueue类表示,它用于保存被回收后对象的引用。当把软引用、弱引用和引用队列联合使用时,系统回收被引用的对象之后,将会把被回收对象对应的引用添加到关联的引用队列中。与软引用和弱引用不同的是,虚引用在对象被释放之前,将把它对应的虚引用添加到它关联的引用队列中,这使得可以在对象被回首之前采取行动。

import java.lang.ref.PhantomReference;

import java.lang.ref.ReferenceQueue;

public class PhantomReferenceTest {

public static void main(String[] args) throws Exception {

String str = new String("Java对象");

ReferenceQueue rq = new ReferenceQueue<>(); //创建引用队列

PhantomReference pr = new PhantomReference(str, rq); //创建虚引用

str = null;

System.out.println(pr.get()); //①

System.gc(); //强制垃圾回收

System.runFinalization();

System.out.println(rq.poll() == pr); //取出引用队列中最先进入队列中的引用于pr进行比较 ②

}

}

输出结果为:

null

true

因此系统无法通过虚引用来获得被引用的对象,所以执行①出输出null。当程序强制垃圾回收后,只有虚引用引用的字符串对象会被垃圾回收,当被引用的对象被回收后,对应的引用会被添加到关联的引用队列中,因此②执行后会输出true。

总结:使用这些引用类可以避免在程序执行期间将对象留在内存中。如果以软引用、弱引用或虚引用的方法引用对象,垃圾回收器就能够随意地释放对象。如果希望尽可能减小程序在其生命周期中所占用的内存大小,这些引用类就很有好处。最后需要指出的是,要使用这些特殊的引用类,就不能保留对对象的强引用。如果保留了对对象的强引用,就会浪费这些类所提供的任何好处。

java回收类_Java的内存回收——Java引用的种类相关推荐

  1. java 内存回收参数_JAVA虚拟机内存回收算法与调优参数

    一.相关概念 基本回收算法 引用计数(Reference Counting)比较古老的回收算法.原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回收时,只用收集计数为0的对象. ...

  2. Android 操作系统的内存回收机制之默认内存回收、OOM以及lowmemorykiller

    Android 操作系统中的内存回收可分为两个层次,即默认内存回收与内核级内存回收,本章重点对默认内存回收机制进行研究,Linux 内核层次的内存回收机制本文不涉及. 本章所有代码可参见 Activi ...

  3. java 垃圾回收机制_Java的垃圾回收机制

    前言 在C++语言中, 程序员必须小心谨慎的处理每一项内存分配, 且内存使用完后必须手动释放曾经占用的内存空间.当内存释放不够完全时, 即存在分配但永不释放的内存块, 就会引起"内存泄漏&q ...

  4. Java零拷贝续——DirectByteBuffer内存回收

    DirectByteBuffer中的address指向的地址空间属于堆外内存,它是不受JVM的管控,所以需要我们自己去管理: 前面的文章我们分析过address的地址由mmap或malloc的分配,那 ...

  5. java中垃圾收集_Java中垃圾回收机制

    "猪能吃的是湿垃圾,猪不吃的是干垃圾,猪吃了会死的是有害垃圾,卖了能买猪的是干垃圾 ......"最近,上海人民都快被垃圾分类弄疯了.那作为程序员的你,知道在Java中是怎么垃圾回 ...

  6. java 系统 类_Java常用实体类--System类

    字符串.日期.数字是Java程序中最常使用的数据对象,对这些数据的创建.修改.格式化和转换等操作融入在Java程序的每个角落,必须熟练掌握.本节将通过实例演示以下常用实体类Java系统级类:系统类Sy ...

  7. java动态扩展_java栈内存动态扩展要怎么理解?要如何实现?

    小伙伴们知道如何在java栈中内存动态扩展吗?这是虚拟机中的一个概念,下面让我们一起来看看该如何实现吧. 一.内存概念 在java中,我们一般会简单把java内存区域划为两种:堆内存与栈内存.其实这种 ...

  8. java 异常类_Java异常处理

    Java 异常处理 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的. 比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error:如果你 ...

  9. java高级类_Java高级类特性(一)

    权限类内同包不同包子类不同包非子类 private √ × × × default √ √ × × protected √ √ √ × public √ √ √ √ 四.super关键字的使用 pac ...

最新文章

  1. Linux下如何实现用户的集中管理(NIS服务器的高级配置)
  2. linux svn 看不到文件,SVN更新时不能打开新文件svn-base系统找不到指定的文件
  3. python多线程同步
  4. 失去循环标记的Python,我这样实现跳出外层循环
  5. iOS核心动画高级技术(九) 图层时间
  6. attention :为什么要用attention机制
  7. 【链表】单链表的排序(归并排序)
  8. think in java bruce eckel_java大师 Bruce Eckel 批评java
  9. 在谷歌云盘训练YOLOV5模型
  10. pycharm笔记-动手学深度学习(李沐)线性代数课后习题
  11. matlab 矩阵转置
  12. 蜂鸣器c语言相关程序,蜂鸣器c语言程序.doc
  13. 【用html做个人简历的网页(初级)】
  14. html当前页面的脚本发生错误,如何解决“当前页面脚本发生错误”的问题
  15. 操作系统复习之OS的运行环境
  16. LabVIEW串口仪器控制
  17. 什么是绿色工厂,怎么进行绿色工厂认证
  18. 实验十八 IEEE 802.15.4和ZBR协议仿真
  19. 五行塔怎么吃第五个_卸甲枭雄最新章节_章节目录 第1541章 真正的五行塔_小说下载/手机阅读_领域文学...
  20. EventBus源码分析 1

热门文章

  1. 数据库面试题【十六、优化长难的查询语句】
  2. rhel6系统中,mysql 5.6复制新特性下主从复制配置[基于GTID]
  3. Oracle分区表 (二)
  4. 3. CMake 系列 - 分模块编译安装项目
  5. Unity 安卓连调profile失败
  6. 面向对象编程,链式调用,先输出‘hello’,10秒之后,输出‘world’
  7. stm32 之引脚和各功能模块间关系
  8. javaBean List Map json(转)
  9. 从字符串 到类型 Boolean 的转换无效。
  10. [转]在Winform(C#)中使用Flash控件