• JAVA引用的种类


      最近在进行Java项目开发的时候,由于业务的原因,有时候new的对象会比较多,这个时候我总是有一个疑惑?那就是JVM在何时决定回收一个Java对象所占据的内存?这个问题其实对整个web系统来说是一个比较核心的性能问题了,因为众所周知,Java也是会发生内存泄漏的。经过几天的学习和查询资料,现在先来分析一下Java的引用的种类,Java的引用就是指向Java堆内存中对象的箭头的另一端的元素。

      可以先来分析一下对象在内存中的状态,对于JVM的垃圾回收机制来说,是否回收一个对象的标准在于:是否还有引用变量引用该对象?只要有引用变量引用该对象,垃圾回收机制就不会回收它。

      也就是说,当Java对象被创建出来之后,垃圾回收机制会实时监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等。当垃圾回收机制实时地监控到某个对象不再被引用变量所引用时,立即回收机制就会回收它所占的空间。

      从Java内存的角度来考虑程序运行的效率,如果我们编程的时候不注意对象的合理分配,疯狂的使用new来申请对象,导致JVM内存变小,降低程序的运行效率。垃圾回收机制回收对象的时候,后台是会再起一条线程,无端的经常运行会导致程序的运行效率变慢。

      我们用一段代码和UML图来查看Java对象的状态:

     1 package com.sunyard.test;
     2 
     3 public class NodeTest{
     4     public static void main(String[] args) {
     5         Node n1 = new Node("第一个节点");
     6         Node n2 = new Node("第二个节点");
     7         Node n3 = new Node("第三个节点");
     8         n1.next = n2;
     9         n2 = null;
    10         n3 = n2;
    11     }
    12 }
    13 
    14 class Node {
    15     Node next;
    16     String name;
    17     public Node(String name){
    18         this.name = name;
    19     }
    20     @Override
    21     public String toString() {
    22         return "Node [next=" + next + ", name=" + name + "]";
    23     }
    24 }

    上面的代码定义了三个Node对象,并通过一些基本的逻辑关系组合在一起,下面再用一张图来绘制他们在内存中的关系。

    从上图可以看出,起初创建三个Node对象的时候,都是从main开始,各自的指向各自的对象,但是我们额外又增加了一些操作,当代码执行到n1.next = n2,n1指向n2,n2=null,相当于把n2的指向为空,当程序执行到n3 = n2;的时候,相当于n3的引用指向n2的内存。
     
    下面来说说对象的状态,当一个对象在堆内存中运行时,根据它在对应有向图中的状态,可以把它所处的状态分成如下3种:
    1.可达状态:当一个对象被创建后,有一个以上的变量引用它。在有向图中可从起始顶点导航到该对象,那它就处于可达状态,程序可通过引用变量来调用该对象的属性和方法。
    2.可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它将先进入可恢复状态,此时从有向图的起始定点不能导航到该对象。在这个状态下,系统的垃圾回收机制准备回收该对象所占用的内存。在回收该对象之前,系统会调用可恢复状态的对象finalize方法进行资源清理,如果系统在在调用finalize方法重新让一个以上引用变量引用该对象,则这个对象会再次变为可达状态;否则,对象将进入不可达状态。
    3.不可达状态:当对象的所有关联都被切断,且系统调用所有对象的finalize方法依然没有使该对象变成可达状态,那这个对象将永久性地失去引用,最后变成不可达状态。只有当一个对象变成不可达状态时,系统才会真正的回收该对象所占有的资源。
    下图显示了三种状态之间的切换:
     1 package com.sunyard.test;
     2 
     3 public class StatusTranfer {
     4     public static void test(){
     5         String a = new String("今天天气真好");  // 1
     6         a = new String("今天天气不好");  // 2
     7     }
     8     
     9     public static void main(String[] args) {
    10         test();  // 3
    11     }
    12 }

    当程序执行到1处的时候,创建了一个String对象今天天气真好”,引用变量a指向它,这个时候“今天天气真好”处于可达状态,执行到2处代码的时候,"今天天气真好"就处于可恢复状态了,而"今天天气不好"处于可达状态,引用变量a指向它。到这里我们可以得出一个结论,判断一个对象是否可回收的标准就在于该对象是否被引用,因此引用也是JVM进行内存管理的一个重要概念。为了更好地管理对象的引用,从JDK1.2开始,Java在java.lang.ref包下提供了3个类:SoftReference、PhantomReference、WeakReference。它们分别代表了系统对对象的3种引用方式:软引用、虚引用和弱引用。归纳起来,Java语言对对象的引用有如下4种:强引用,软引用,虚引用和弱引用。

    强引用:

    这是Java程序中最常见的一个方式,程序创建一个对象,并把这个对象赋给一个引用变量,这个引用变量就是强引用。Java程序可通过强引用来访问实际的对象,前面介绍的所有的引用变量都是强引用的方式。当一个对象被一个或一个以上的强引用变量所引用时,它处于可达状态,它不可能被系统垃圾回收机制回收。由于JVM肯定不会回收强引用所引用的Java对象,因此强引用是造成Java内存泄漏的主要原因之一。

    软引用:

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

    使用场景:

    当程序需要大量创建某个类的新对象。

    例如,需要访问1000个Person对象,可以有两种方式:

    1.依次创建1000个Person对象,但只有一个Person引用指向最后一个Person对象;

    2.定义一个长度为1000的Person数组,每个数组元素引用一个Person对象。

    我们在这里只说明第二种情况,这种情况每个Person对象没有耦合性相对第一种情况来说,但是弱点也很大。如果系统堆内存空间紧张,而1000个Person对象都被强引用引用着,垃圾回收机制也不可能回收它们的堆内存空间,系统性能将变得非常差,甚至因为内存不足导致程序终止。

    例如:下面程序创建了一个SoftReference数组,通过SoftReference数组来保存100个Person对象,当系统内存充足时,SoftReference引用和强引用并没有太大的区别。

    代码如下:

     1 package com.sunyard.test;
     2 
     3 import java.lang.ref.SoftReference;
     4 
     5 public class ReferenceTest {
     6     @SuppressWarnings("unchecked")
     7     public static void main(String[] args) {
     8         SoftReference<Person>[] people = new SoftReference[100];
     9         for(int i = 0;i < people.length;i++){
    10             people[i] = new SoftReference<Person>(new Person("名字" + i, (i + 1) * 4 % 100));
    11         }
    12         System.out.println(people[2].get());
    13         System.out.println(people[4].get());
    14         //通知系统进行垃圾回收
    15         System.gc();
    16         System.runFinalization();
    17         //垃圾回收机制运行之后,SoftReference数组里的元素保持不变
    18         System.out.println(people[2].get());
    19         System.out.println(people[4].get());
    20     }
    21 }
    22 
    23 class Person {
    24     String name;
    25     int age;
    26     public Person(String name, int age) {
    27         this.name = name;
    28         this.age = age;
    29     }
    30     @Override
    31     public String toString() {
    32         return "Person [name=" + name + ", age=" + age + "]";
    33     }
    34 }

    控制台输出:

    1 Person [name=名字2, age=12]
    2 Person [name=名字4, age=20]
    3 Person [name=名字2, age=12]
    4 Person [name=名字4, age=20]

    这个是内存足够的情况,如果我们把堆内存强制命令修改为只有两M,并且循环次数改为10000,其实会看到输出很多null,这个时候软引用的作用就体现出来了,如果这个时候用强引用来创建对象,会抛出虚拟机内存溢出的异常。

    弱引用:

    弱引用与软英文有点相似,区别在于弱引用所引用对象的生存期更短。弱引用通过WeakReference类实现,弱引用和软引用很像,但弱引用的引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。当然,并不是说当一个对象只有弱引用时,它就会立即被回收--正如那些失去引用的对象一样,必须等到系统垃圾回收机制运行时候才会被回收。

    下面的代码显示了弱引用对象也会被系统垃圾回收的过程。

     1 package com.sunyard.test;
     2 
     3 import java.lang.ref.WeakReference;
     4 
     5 public class WeakReferenceTest {
     6     public static void main(String[] args) {
     7         //创建一个字符串对象
     8         String str = new String("今天天气真好");
     9         //创建一个弱引用,让此弱引用引用到"今天天气真好"字符串
    10         WeakReference<String> wr = new WeakReference<String>(str);  // 1
    11         //切断str引用和"今天天气真好"字符串之间的引用
    12         str = null; // 2
    13         //取出弱引用所引用的对象
    14         System.out.println(wr.get());  // 3
    15         //强制垃圾回收
    16         System.gc();
    17         System.runFinalization();
    18         //再次取出弱引用所引用的对象
    19         System.out.println(wr.get());  // 4
    20     }
    21 }

     这段代码的运行中对象的引用变化如下图所示:

  • 相关阅读:
    java 线程状态和转化
    初学Spring
    unittest --- 单元测试
    Python单例模式的两种实现方式
    python records操作数据库
    数据可视化之——matplotlib基础学习
    python使用list维护成一个队列
    Python将list列表维护成一个栈
    Python使用rsa模块实现非对称加密与解密
    python +OpenCV实现rtmp视频流媒体的播放
  • 原文地址:https://www.cnblogs.com/yujiwei/p/7260486.html
Copyright © 2020-2023  润新知