好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

详解Java的引用类型及使用场景

每种编程语言都有自己操作内存中元素的方式,例如在 C 和 C++ 里是通过指针,而在 Java 中则是通过[引用]。在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱,今天这篇文章就简单介绍一下这四种类型,并简单说一下他们的使用场景。

1. 强引用(Strong Reference)

强引用类型,是我们最常讲的一个类型,我们先看一个例子:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

package cn.bridgeli.demo.reference;

 

/**

  * @author BridgeLi

  * @date 2021/2/26 10:02

  */

public class User {

 

     @Override

     protected void finalize() throws Throwable {

         super .finalize();

         System.out.println( "finalize" );

     }

 

}

 

package cn.bridgeli.demo.reference;

 

import org.junit.Test;

 

/**

  * @author BridgeLi

  * @date 2021/2/26 10:03

  */

public class StrongReferenceTest {

 

     @Test

     public void testStrongReference() {

         User user = new User();

         user = null ;

         System.gc();

         try {

             Thread.sleep( 1000 );

         } catch (InterruptedException e) {

             e.printStackTrace();

         }

     }

}

我们都知道当一个实例对象具有强引用时,垃圾回收器不会回收该对象,当内存不足时,宁愿 OOM,也就是抛出 OutOfMemeryError 异常也不会回收强引用的对象,因为 JVM 认为强引用的对象是用户正在使用的对象,它无法分辨出到底该回收哪个,强行回收有可能导致系统严重错误。但是当对象被赋值为 null 之后,会被回收,并且会执行对象的 finalize 函数,此时我们可以通过该函数拯救自己,但是有两点需要注意一个是只能拯救一次,当再次被垃圾回收的时候就不能拯救了,另一个就是有事没事千万不要重写次函数,本例只是为了说明问题重写了此函数,如果在工作中误重写了此函数,可能会导致垃圾不能回收,最终 OOM,另外有熟悉 GC 的同学没?猜一下我为什么要 sleep 一下?

2. 软引用(Soft Reference)

在我刚学 Java 的时候,并不知道怎么使用软引用,那时候只知道强引用,其实是通过 java.lang.ref.SoftReference 类来使用软引用的,为了说明软引用,我们先看一个例子:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

package cn.bridgeli.demo.reference;

 

import org.junit.Test;

 

import java.lang.ref.SoftReference;

 

/**

  * @author BridgeLi

  * @date 2021/2/26 10:21

  */

public class SoftReferenceTest {

 

     @Test

     public void testSoftReference() {

         SoftReference< byte []> softReference = new SoftReference<>( new byte [ 1024 * 1024 * 10 ]);

         System.out.println(softReference.get());

 

         System.gc();

 

         try {

             Thread.sleep( 1000 );

         } catch (InterruptedException e) {

             e.printStackTrace();

         }

 

         System.out.println(softReference.get());

 

         byte [] bytes = new byte [ 1024 * 1024 * 12 ];

 

         System.out.println(softReference.get());

     }

}

除了通过 get 方法获取我们的软引用对象之外,运行结果和强引用类型并没有什么区别是吧?结果和我们想的一样,但是别着急,加一个启动参数再试试:

?

1

-Xms20m -Xmx20m

我们都知道,这两个参数是控制 JVM 启动的时候堆的最大值和最小值的,这里面我们设置的最大值和最小值都是 20M,按照强引用的逻辑,我们一共申请了 22M 的空间,应该 OOM 才对,事实证明并没有,通过打印语句证明,我们的软引用被回收了,所以软引用的特点是:在内存足够的时候,软引用对象不会被垃圾回收器回收,只有在内存不足时,垃圾回收器则会回收软引用对象,当然回收了软引用对象之后仍然没有足够的内存,这时同样会抛出内存溢出异常。

看了软引用的特点,我们很容易想到软引用的使用场景:缓存。记得刚工作的时候,有个同事给我说,他做 Android,有一个加载图片的应用,特麻烦,会 OOM,其实使用软引用应该很轻松的能解决这个问题。

3. 弱引用(Weak Reference)

弱引用是通过 java.lang.ref.WeakReference 类来实现的,同样我们也先看一个例子:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

package cn.bridgeli.demo.reference;

 

import org.junit.Test;

 

import java.lang.ref.WeakReference;

 

/**

  * @author BridgeLi

  * @date 2021/2/26 10:30

  */

public class WeakReferenceTest {

 

     @Test

     public void testWeakReference() {

         WeakReference<User> weakReference = new WeakReference<>( new User());

         System.out.println(weakReference.get());

 

         System.gc();

 

         try {

             Thread.sleep( 1000 );

         } catch (InterruptedException e) {

             e.printStackTrace();

         }

 

         System.out.println(weakReference.get());

     }

}

通过例子我们可以看到,弱引用是一种比软引用更弱的引用类型:在系统 GC 时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。看到这里可能会有同学有疑问,GC 什么时候启动,除了我们显示调用外,我们并不能控制(其实就算我们显示调用,GC 也可能不会立即执行),而且 GC 之后,弱引用立即被回收,引用不到了,那么这个类型有什么用呢?其实这个类型还真有大用,我们鼎鼎大名的 ThreadLocal 类就是借助于这个类实现的,所以当你使用 ThreadLocal 的时候,就已经在使用弱类型了,我之前曾经写过关于 ThreadLocal 的文章,但是当时理解不是很准确,不过说明的例子是没有问题的,所以还有一定的参考价值,后面看看啥时候有机会重写一篇关于 ThreadLocal 的文章,详细说说这个类。

另外除了 ThreadLocal 类外还有一个类值得说一下,那就是 java.util.WeakHashMap 类,见名知意,我们就可以猜到这个类的特点。同样通过一个例子说明一下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

package cn.bridgeli.demo.reference;

 

import org.junit.Test;

 

import java.util.Map;

import java.util.WeakHashMap;

 

/**

  * @author BridgeLi

  * @date 2021/2/26 10:38

  */

public class WeakHashMapTest {

 

     @Test

     public void testWeakHashMap() {

         Map map = new WeakHashMap<String, Object>();

         for ( int i = 0 ; i < 10000 ; i++) {

             map.put( "key" + i, new byte [i]);

         }

 

//        Map map = new HashMap<String, Object>();

//        for (int i = 0; i < 10000; i++) {

//            map.put("key" + i, new byte[i]);

//        }

     }

}

记得启动的时候设置一下,设置一下启动的时候堆的大小,不要设置太大,可以看出区别。

4. 虚引用(Phantom Reference)

通过前面的例子,我们可以看到引用强度是越来越弱的,所以虚引用是最弱的一种引用类型,到底有多弱呢,我们同样通过一个例子来看,需要说明的是,虚引用是通过 java.lang.ref.PhantomReference 类实现的。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

package cn.bridgeli.demo.reference;

 

import org.junit.Test;

 

import java.lang.ref.PhantomReference;

import java.lang.ref.Reference;

import java.lang.ref.ReferenceQueue;

import java.util.ArrayList;

import java.util.List;

 

/**

  * @author BridgeLi

  * @date 2021/2/26 11:05

  */

public class PhantomReferenceTest {

 

     ReferenceQueue referenceQueue = new ReferenceQueue();

     List<Object> list = new ArrayList<>();

 

     @Test

     public void testPhantomReference() {

         PhantomReference<Object> phantomReference = new PhantomReference<>( new Object(), referenceQueue);

         System.out.println(phantomReference.get());

 

         new Thread(() -> {

             while ( true ) {

                 Reference reference = referenceQueue.poll();

                 if ( null != reference) {

                     System.out.println( "============ " + reference.hashCode() + " ============" );

                 }

             }

         }).start();

 

         new Thread(() -> {

             while ( true ) {

                 list.add( new byte [ 1024 * 1024 * 10 ]);

             }

         }).start();

 

         try {

             Thread.sleep( 500 );

         } catch (InterruptedException e) {

             e.printStackTrace();

         }

     }

}

我们看到了是什么?虽然软引用和弱引用也很弱,但是我们还是可以通过 get 方法获取到我们的引用对象,但是虚引用却不行,点进去看一下源码,我们可以看到虚引用的 get 方法,直接返回 null,也就是我们直接拿不到虚引用对象,那么这个类型又有什么使用场景呢?其实这个类型就不是给我们普通程序员使用的,在 io、堆外内存中有使用,所以对于我们普通程序员来说,了解到存在这个类型,另外通过上面的例子,我们还可以看到:当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,销毁这个对象,将这个虚引用加入引用队列。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。那么我们就可以在程序中发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些必要的行动。

以上就是详解Java的引用类型及使用场景的详细内容,更多关于Java 引用类型及使用场景的资料请关注其它相关文章!

原文链接:http://HdhCmsTestbridgeli.cn/archives/697

查看更多关于详解Java的引用类型及使用场景的详细内容...

  阅读:18次