主要内容:
- 介绍
- 垃圾回收器
- 问题
- 强引用
- 软引用
- 弱引用
- 虚引用
- 总结
介绍
在java中有以下四种引用:
- 强应用(Strong References)
- 软引用(Soft References)
- 弱引用(Weak References)
- 虚引用(Phantom References)
这些引用的区别主要在于垃圾回收器管理上。如果你从来没有听过这些引用,说明你仅仅使用过强引用。知道这些引用的区别,对你开发有帮助。尤其是当你需要存储临时对象,并且不想使用一些缓存库如:EHcache、Guava。
由于这些引用类型与JVM垃圾回收器关联紧密,下面将简单回顾下垃圾回收器知识。
垃圾回收器
Java和C++的主要区别在于内存管理。在Java中,开发者不需要知道内存是如何工作的(但是应该要知道!),因为JVM使用垃圾回收器对内存进行管理。
当你创建一个对象时,对象被JVM分配到堆中。堆在内存中是有大小限制的。因此,JVM需要经常删除对象来达到释放内存空间的目的。为了销毁一个对象,JVM需要知道这个对象是否活跃。一个对象是否在使用,是通过GC root(garbage collection root)是否可达判断的。例如:
如果一个对象C被对象B引用,对象B被对象A引用,对象A被一个GC root引用,因此C,B和A被认为是活跃,被使用,可达的(如图Case 1)。但是,如果B不再被B引用,这样C和B就不再是活跃的,因此可以被回收(如图Case 2)。
由于这篇文章不是主要介绍垃圾回收器的,所以不会深入介绍。主要有四种类型的GC roots:
- 局部变量(Local variables)
- 活跃线程(Active Java threads)
- 静态变量(Static variables)
- JNI引用(JNI References):Java对象包含本地代码,并且内存不被JVM管理。
Oracle公司没有指定JVM实现中如何管理内存的规范,因此有多种实现。但是主要思想都是一样的,如下:
- JVM使用一种recurent算法来找到不活跃对象并标记
- 被标记的对象被确定(finalized),通过调用finalize()方法,然后被回收。
- JVM有时会移动存活对象,主要为了留出一段连续的大内存区域,减少内存碎片。
问题
既然JVM负责管理内存,为什么你还需要关心?因为你还需要担心内存泄露问题!
大部分情况下,你正在使用GC roots,而没有察觉。例如:在你的程序中,需要存储对象,因为初始化比较费资源。你可能会使用静态集合类(List、Map等)去存储,并且在程序的其他地方获取这些对象。
public static Map<K, V> myStoredObjects= new HashMap<>();
但是,通过这种做法,会阻止垃圾回收器回收对象。如果使用不当,则会导致OutOfMemoryError。例如:
public class OOM { public static List<Integer> myCachedObjects = new ArrayList<>(); public static void main(String[] args) { for (int i = 0; i < 100_000_000; i++) { myCac(i); } } }
输出如下:
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
Java提供了不同的引用类型来避免OutOfMemoryError。
即使在程序仍然需要使用的时候,一些类型允许JVM释放对象。这类问题都是开发者的职责。
强应用
强引用是标准的引用。当你像下面这样新建一个对象时:
MyClass obj = new MyClass ();
你正在使用一个强引用"obj"指向MyClass的实例。当垃圾回收器查找不活跃对象时,仅仅会判断这些对象是否强可达(strongly reachable,即被GC root强引用连接)。
强引用可以强制JVM保留对象在堆中,直到对象不被使用为止。
软引用
根据java API中软引用描述:
“Soft reference objects, which are cleared at the discretion of the garbage collector in response to memory demand”
上面描述指的是,软引用的行为是可变的,不同的JVM(Oracle’s Hotspot, Oracle’s JRockit, IBM’s J9, …)中,你的程序可能执行结果不同。
下面以Oracle的JVM Hotspot(大多数使用的标准JVM)为例来看看是怎么管理软引用的。下面是Oracle的文档描述:
“The default value is 1000 ms per megabyte, which means that a soft reference will survive (after the last strong reference to the object has been collected) for 1 second for each megabyte of free space in the heap”
下面是具体的例子:假设堆的大小512Mb,空闲400Mb。
我们创建对象A,被对象cache软引用和被B强应用。因为A被B强引用,因此是强可达,不会被垃圾回收器回收(上图Case 1描述)
假设现在B被删除,因此A仅仅被对象cache软引用。如果A没有被强引用,则下一个400s,A将在超时后被删除回收掉(上图Case 2描述)。
下面是一个软引用的例子:
public class ExampleSoftRef { public static class A{ } public static class B{ private A strongRef; public void setStrongRef(A ref) { = ref; } } public static SoftReference<A> cache; public static void main(String[] args) throws InterruptedException{ //initialisation of the cache with a soft reference of instanceA Exam instanceA = new Exam(); cache = new SoftReference<Exam>(instanceA); instanceA=null; // instanceA is now only soft reachable and can be deleted by the garbage collector after some time T(5000); ... Exam instanceB = new Exam(); //since cache has a SoftReference of instance A, we can't be sure that instanceA still exists //we need to check and recreate an instanceA if needed instanceA=cac(); if (instanceA ==null){ instanceA = new Exam(); cache = new SoftReference<Exam>(instanceA); } in(instanceA); instanceA=null; // instanceA a is now only softly referenced by cache and strongly referenced by B so it cannot be cleared by the garbage collector ... } }
但是即使软引用的对象被垃圾回收器自动回收,软引用(soft references ,也是一种对象)却不会被删除!因此,你仍然需要清理掉它们。例如在一个只有64Mb大小的堆中,下面的代码会在使用软引用的时候,抛出OutOfMemoryException异常。
public class TestSoftReference1 { public static class MyBigObject{ //each instance has 128 bytes of data int[] data = new int[128]; } public static int CACHE_INITIAL_CAPACITY = 1_000_000; public static Set<SoftReference<MyBigObject>> cache = new HashSet<>(CACHE_INITIAL_CAPACITY); public static void main(String[] args) { for (int i = 0; i < 1_000_000; i++) { MyBigObject obj = new MyBigObject(); cac(new SoftReference<>(obj)); if (i%200_000 == 0){ Sy("size of cache:" + cac()); } } Sy("End"); } }
输出如下:
size of cache:1 size of cache:200001 size of cache:400001 size of cache:600001 Exception in thread “main” java.lang.OutOfMemoryError: GC overhead limit exceeded
Oracle提供了ReferenceQueue来存放软应用。使用这个队列,可以清理掉软应用,因此避免OutOfMemoryError问题。
使用软引用队列,同样的代码,在同样的64Mb堆大小下,可以存储更多的数据(5 million vs 1 million),如下测试:
public class TestSoftReference2 { public static int removedSoftRefs = 0; public static class MyBigObject { //each instance has 128 bytes of data int[] data = new int[128]; } public static int CACHE_INITIAL_CAPACITY = 1_000_000; public static Set<SoftReference<MyBigObject>> cache = new HashSet<>( CACHE_INITIAL_CAPACITY); public static ReferenceQueue<MyBigObject> unusedRefToDelete = new ReferenceQueue<>(); public static void main(String[] args) { for (int i = 0; i < 5_000_000; i++) { MyBigObject obj = new MyBigObject(); cac(new SoftReference<>(obj, unusedRefToDelete)); clearUselessReferences(); } Sy("End, removed soft references=" + removedSoftRefs); } public static void clearUselessReferences() { Reference<? extends MyBigObject> ref = unu(); while (ref != null) { if (ref)) { removedSoftRefs++; } ref = unu(); } } }
输出如下:
End, removed soft references=4976899
当你想存储更多数据,并在JVM删除对象的时候,可以重新实例化的情况下,使用软应用效果更佳。
弱引用
弱引用是一个比软引用更不稳定的类型。下面是Java API介绍:
“Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable. At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues.”
上面的描述是指当垃圾回收器检查所有对象时,如果检查到一个对象只有弱引用可达GC root(没有强引用或软引用可达),这个对象则会被标记为回收,并别尽快删除。弱引用的用法和软引用类似。因此,参考软引用时举的例子。
Oracle提供了一个基于软引用的非常有趣的例子即:WeakHashMap。这个Map的特点是key是软引用。WeakHashMap可以当做标准的Map使用,和Map的区别在于当key被销毁的时候,会自动清理( automatically clear itself),如下面例子:
public class ExampleWeakHashMap { public static Map<Integer,String> cache = new WeakHashMap<Integer, String>(); public static void main(String[] args) { Integer i5 = new Integer(5); cac(i5, "five"); i5=null; //the entry {5,"five"} will stay in the Map until the next garbage collector call Integer i2 = 2; //the entry {2,"two"} will stay in the Map until i2 is no more strongly referenced cac(i2, "two"); //remebmber the OutOfMemoryError at the chapter "problem", this time it won't happen // because the Map will clear its entries. for (int i = 6; i < 100_000_000; i++) { cac(i,S(i)); } } }
例如,我使用过WeakHashMap来解决下面的问题:存储事务的多种信息。我使用这种结构:WeakHashMap<String,Map<K,V>> ,其中key的String包含了事务的id,Map<K,V>包含了事务生命周期中的信息。使用这种结构,我能通过事务id获取到信息,当事务销毁的时候,key中会被销毁,也不用清理对应的Map<K,V>信息。
Oracle建议使用WeakHashMap作为规范的(canonicalized)Map。
虚引用
在垃圾回收过程中,没有被GC root强/软引用的对象会被删除回收。在被删除之前,方法finalize()会被调用。当一个对象被finalized的时候,但是还未被删除,此时变为“phantom reachable”,即在GC root和对象之间只有虚引用。
和软引用、弱引用不同的是,使用虚引用可以防止对象被删除。开发者需要移除虚引用,对象才能被销毁。为了清理虚引用,当一个对象处于finalized的时候,需要使用ReferenceQueue来保存虚引用。
虚引用不能获取引用的对象。通过get()方法,返回的始终都是null,因此开发者不能使虚引用对象重新回到强/软/弱引用。这是合理的,当对象不需要再工作时,比如覆盖finalize() 方法实现清理资源。
因为引用对象不可达,我没有看到虚引用的用处。一个例子是,如果你需要在一个对象处于finalized时,需要指定finalize() 行为。
总结
我希望你现在对引用有一个很好的认识。大部分情况下,你不用知道具体使用这些引用(你也不应该)。但是,很多框架都使用了这些引用。如果你知道一个事物是如果工作的,你就能更好理解这些概念。
如果你更喜欢看视频介绍,可以通过YouTube:。
个人总结:
是否回收:强引用不会回收;软应用在不同的JVM中实现不同,如果在Oracle的HotSpot中,则会定时过期清理,也会导致OOM问题;弱引用,在GC的时候会回收,当key设置null,则对象会自动回收,不会导致OOM;虚引用,会回收,被调用之前会调用finalize()方法。
使用场景:强应用经常使用;软引用在堆大小有限,可以存更多对象场景,使用时,需要判断对象是否存在,不存在则重新实例化,适合非必须大对象的缓存;若引用,WeakHashMap就使用了;虚引用,在NIO的堆外内存管理中使用到(下一篇文章进行分析)。
1.《【i2stay什么意思】专题java引用(翻译)》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《【i2stay什么意思】专题java引用(翻译)》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/yule/3333748.html