• 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 }

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

  • 相关阅读:
    JDBC 复习4 批量执行SQL
    JDBC 复习3 存取Oracle大数据 clob blob
    Oracle复习
    Linux命令(1)grep
    JDBC 复习2 存取mysql 大数据
    JDBC 复习1 DBUtil
    php 环境搭建问题
    Windows 批处理 bat 开启 WiFi 菜单选项 设置ID PWD
    Bat 批处理启动和停止Oracle 服务
    docker 学习1 WSL docker ,Windows docker
  • 原文地址:https://www.cnblogs.com/yujiwei/p/7260486.html
Copyright © 2020-2023  润新知