临城网站免费申请域名的方法和流程
news/
2025/9/23 0:32:08/
文章来源:
临城网站,免费申请域名的方法和流程,天津房产网,wordpress 未分类转载自 彻底理解JVM常考题之分级引用模型
本文通过探析Java中的引用模型#xff0c;分析比较强引用、软引用、弱引用、虚引用的概念及使用场景#xff0c;知其然且知其所以然#xff0c;希望给大家在实际开发实践、学习开源项目提供参考。
Java的引用
对于Java中的垃圾…转载自 彻底理解JVM常考题之分级引用模型
本文通过探析Java中的引用模型分析比较强引用、软引用、弱引用、虚引用的概念及使用场景知其然且知其所以然希望给大家在实际开发实践、学习开源项目提供参考。
Java的引用
对于Java中的垃圾回收机制来说对象是否被应该回收的取决于该对象是否被引用。因此引用也是JVM进行内存管理的一个重要概念。Java中是JVM负责内存的分配和回收这是它的优点使用方便程序不用再像使用C语言那样担心内存但同时也是它的缺点不够灵活。由此Java提供了引用分级模型可以定义Java对象重要性和优先级提高JVM内存回收的执行效率。
关于引用的定义在JDK1.2之前如果reference类型的数据中存储的数值代表的是另一块内存的起始地址就称为这块内存代表着一个引用JDK1.2之后Java对引用的概念进行了扩充将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种。
软引用对象和弱应用对象主要用于当内存空间还足够则能保存在内存之中如果内存空间在垃圾收集之后还是非常紧张则可以抛弃这些对象。很多系统的缓存功能都符合这样的使用场景。
而虚引用对象用于替代不靠谱的finalize方法可以获取对象的回收事件来做资源清理工作。 对象生命周期
无分级引用对象生命周期
前面提到分层引用的模型是用于内存回收没有分级引用对象下一个对象从创建到回收的生命周期可以简单地用下图概括对象被创建被使用有资格被收集最终被收集阴影区域表示对象“强可达”时间 有分级引用对象生命周期
JDK1.2引入java.lang.ref程序包之后对象的生命周期多了3个阶段软可达弱可达虚可达这些状态仅适用于符合垃圾回收条件的对象这些对象处于非强引用阶段而且需要基于java.lang.ref包中的相关的引用对象类来指示标明。 软可达 软可达对象用SoftReference来指示标明并没有强引用垃圾回收器会尽可能长时间地保留对象但是会在抛出OutOfMemoryError异常之前收集它。 弱可达 弱可达对象用WeakReference来指示标明并没有强引用或软引用垃圾回收器会随时回收对象并不会尝试保留它但是会在抛出OutOfMemoryError异常之前收集它。 在对象回收阶段中该对象在major collection期间被回收但是可以在minor collection期间存活 虚可达 虚可达对象用PhantomReference来指示标明它已经被标记选中进行垃圾回收并且它的finalizer(如果有)已经运行。在这种情况下术语“可达”实际上是用词不当因为您无法访问实际对象。 对象生命周期图中添加三个新的可选状态会造成一些困惑。逻辑顺序上是从强可达到软弱和虚最终到回收但实际的情况取决于程序创建的参考对象。但如果创建WeakReference但不创建SoftReference则对象直接从强可达到弱到达最终到收集。 强引用
强引用就是指在程序代码之中普遍存在的比如下面这段代码中的obj和str都是强引用
Object obj new Object();
String str hello world;只要强引用还存在垃圾收集器永远不会回收被引用的对象即使在内存不足的情况下JVM即使抛出OutOfMemoryError异常也不会回收这种对象。
实际使用上可以通过把引用显示赋值为null来中断对象与强引用之前的关联如果没有任何引用执行对象垃圾收集器将在合适的时间回收对象。
例如ArrayList类的remove方法中就是通过将引用赋值为null来实现清理工作的 /*** Removes the element at the specified position in this list.* Shifts any subsequent elements to the left (subtracts one from their* indices).** param index the index of the element to be removed* return the element that was removed from the list* throws IndexOutOfBoundsException {inheritDoc}*/public E remove(int index) {rangeCheck(index);modCount;E oldValue elementData(index);int numMoved size - index - 1;if (numMoved 0)System.arraycopy(elementData, index1, elementData, index,numMoved);elementData[--size] null; // clear to let GC do its workreturn oldValue;}引用对象
介绍软引用、弱引用和虚引用之前有必要介绍一下引用对象
引用对象是程序代码和其他对象之间的间接层称为引用对象。每个引用对象都围绕对象的引用构造并且不能更改引用值。 引用对象提供get()来获得其引用值的一个强引用垃圾收集器可能随时回收引用值所指的对象。 一旦对象被回收get()方法将返回null要正确使用引用对象下面使用SoftReference(软引用对象)作为参考示例 /*** 简单使用demo*/private static void simpleUseDemo(){ListString myList new ArrayList();SoftReferenceListString refObj new SoftReference(myList);ListString list refObj.get();if (null ! list) {list.add(hello);} else {// 整个列表已经被垃圾回收了做其他处理}}也就是说使用时 1、必须经常检查引用值是否为null 垃圾收集器可能随时回收引用对象如果轻率地使用引用值迟早会得到一个NullPointerException。 2、必须使用强引用来指向引用对象返回的值 垃圾收集器可能在任何时间回收引用对象即使在一个表达式中间。 /*** 正确使用引用对象demo*/private static void trueUseRefObjDemo(){ListString myList new ArrayList();SoftReferenceListString refObj new SoftReference(myList);// 正确的使用使用强引用指向对象保证获得对象之后不会被回收ListString list refObj.get();if (null ! list) {list.add(hello);} else {// 整个列表已经被垃圾回收了做其他处理}}/*** 错误使用引用对象demo*/private static void falseUseRefObjDemo(){ListString myList new ArrayList();SoftReferenceListString refObj new SoftReference(myList);// XXX 错误的使用在检查对象非空到使用对象期间对象可能已经被回收// 可能出现空指针异常if (null ! refObj.get()) {refObj.get().add(hello);}}3、必须持有引用对象的强引用 如果创建引用对象没有持有对象的强引用那么引用对象本身将被垃圾收集器回收。 4、当引用值没有被其他强引用指向时软引用、弱引用和虚引用才会发挥作用引用对象的存在就是为了方便追踪并高效垃圾回收。 软引用、弱引用和虚引用
引用对象的3个重要实现类位于java.lang.ref包下分别是软引用SoftReference、弱引用WeakReference和虚引用PhantomReference。
软引用
软引用用来描述一些还有用但非必需的对象。对于软引用关联着的对象在系统将要发生抛出OutOfMemoryError异常之前将会把这些对象列入回收范围之内进行第二次回收。如果这次回收还没有足够的内存才会抛出OutOfMemoryError异常。在JDK1.2之后提供了SoftReference类来实现软引用。
下面是一个使用示例
import java.lang.ref.SoftReference;public class SoftRefDemo {public static void main(String[] args) {SoftReferenceString sr new SoftReference( new String(hello world ));// hello worldSystem.out.println(sr.get());}
}JDK文档中提到软引用适用于对内存敏感的缓存每个缓存对象都是通过访问的 SoftReference如果JVM决定需要内存空间那么它将清除回收部分或全部软引用对应的对象。如果它不需要空间则SoftReference指示对象保留在堆中并且可以通过程序代码访问。在这种情况下当它们被积极使用时它们被强引用否则会被软引用。如果清除了软引用则需要刷新缓存。
实际使用上要除非缓存的对象非常大每个数量级为几千字节才值得考虑使用软引用对象。例如实现一个文件服务器它需要定期检索相同的文件或者需要缓存大型对象图。如果对象很小必须清除很多对象才能产生影响那么不建议使用因为清除软引用对象会增加整个过程的开销。
弱引用
弱引用也是用来描述非必需对象但是它的强度比软引用更弱一些被弱引用关联的对象只能生存到下一次垃圾收集发送之前。当垃圾收集器工作时无论当前内存是否足够都会回收掉只被弱引用关联的对象。
在JDK1.2之后提供了WeakReference类来实现弱引用。 /*** 简单使用弱引用demo*/private static void simpleUseWeakRefDemo(){WeakReferenceString sr new WeakReference(new String(hello world ));// before gc - hello world System.out.println(before gc - sr.get());// 通知JVM的gc进行垃圾回收System.gc();// after gc - nullSystem.out.println(after gc - sr.get());}可以看到被弱引用关联的对象在gc之后被回收掉。 有意思的地方是如果把上面代码中的
WeakReferenceString sr new WeakReference(new String(hello world ));改为
WeakReferenceString sr new WeakReference(hello world );程序将输出
before gc - hello world
after gc - hello world 这是因为使用Java的String直接赋值和使用new区别在于 new 会在堆区创建一个可以被正常回收的对象。 String直接赋值会在Java StringPool(字符串常量池)里创建一个String对象存于pergmen(永生代区)中通常不会被gc回收。
WeakHashMap 为了更方便使用弱引用Java还提供了WeakHashMap功能类似HashMap内部实现是用弱引用对key进行包装当某个key对象没有任何强引用指向gc会自动回收key和value对象。 /*** weakHashMap使用demo*/private static void weakHashMapDemo(){WeakHashMapString,String weakHashMap new WeakHashMap();String key1 new String(key1);String key2 new String(key2);String key3 new String(key3);weakHashMap.put(key1, value1);weakHashMap.put(key2, value2);weakHashMap.put(key3, value3);// 使没有任何强引用指向key1key1 null;System.out.println(before gc weakHashMap weakHashMap , size weakHashMap.size());// 通知JVM的gc进行垃圾回收System.gc();System.out.println(after gc weakHashMap weakHashMap , size weakHashMap.size());}程序输出
before: gc weakHashMap {key1value1, key2value2, key3value3} , size3
after: gc weakHashMap {key2value2, key3value3} , size2WeakHashMap比较适用于缓存的场景例如Tomcat的缓存就用到。
引用队列
介绍虚引用之前先介绍引用队列 在使用引用对象时通过判断get()方法返回的值是否为null来判断对象是否已经被回收当这样做并不是非常高效特别是当我们有很多引用对象如果想找出哪些对象已经被回收需要遍历所有所有对象。
更好的方案是使用引用队列在构造引用对象时与队列关联当gc垃圾回收线程准备回收一个对象时如果发现它还仅有软引用(或弱引用或虚引用)指向它就会在回收该对象之前把这个软引用或弱引用或虚引用加入到与之关联的引用队列ReferenceQueue中。
如果一个软引用或弱引用或虚引用对象本身在引用队列中就说明该引用对象所指向的对象被回收了所以要找出所有被回收的对象只需要遍历引用队列。
当软引用或弱引用或虚引用对象所指向的对象被回收了那么这个引用对象本身就没有价值了如果程序中存在大量的这类对象注意我们创建的软引用、弱引用、虚引用对象本身是个强引用不会自动被gc回收就会浪费内存。因此我们这就可以手动回收位于引用队列中的引用对象本身。 /*** 引用队列demo*/private static void refQueueDemo() {ReferenceQueueString refQueue new ReferenceQueue();// 用于检查引用队列中的引用值被回收Thread checkRefQueueThread new Thread(() - {while (true) {Reference? extends String clearRef refQueue.poll();if (null ! clearRef) {System.out.println(引用对象被回收, ref clearRef , value clearRef.get());}}});checkRefQueueThread.start();WeakReferenceString weakRef1 new WeakReference(new String(value1), refQueue);WeakReferenceString weakRef2 new WeakReference(new String(value2), refQueue);WeakReferenceString weakRef3 new WeakReference(new String(value3), refQueue);System.out.println(ref1 value weakRef1.get() , ref2 value weakRef2.get() , ref3 value weakRef3.get());System.out.println(开始通知JVM的gc进行垃圾回收);// 通知JVM的gc进行垃圾回收System.gc();}程序输出
ref1 value value1, ref2 value value2, ref3 value value3
开始通知JVM的gc进行垃圾回收
引用对象被回收, ref java.lang.ref.WeakReference48c6cd96, valuenull
引用对象被回收, ref java.lang.ref.WeakReference46013afe, valuenull
引用对象被回收, ref java.lang.ref.WeakReference423ea6e6, valuenull虚引用
虚引用也称为幽灵引用或者幻影引用不同于软引用和弱引用虚引用不用于访问引用对象所指示的对象相反通过不断轮询虚引用对象关联的引用队列可以得到对象回收事件。一个对象是否有虚引用的存在完全不会对其生产时间构成影响也无法通过虚引用来取得一个对象实例。虽然这看起来毫无意义但它实际上可以用来做对象回收时资源清理、释放它比finalize更灵活我们可以基于虚引用做更安全可靠的对象关联的资源回收。 finalize的问题 Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行 如果可用内存没有被耗尽垃圾收集器不会运行finalize方法也不会被执行。 性能问题 JVM通常在单独的低优先级线程中完成finalize的执行。 对象再生问题 finalize方法中可将待回收对象赋值给GC Roots可达的对象引用从而达到对象再生的目的。
针对不靠谱finalize方法完全可以使用虚引用来实现。在JDK1.2之后提供了PhantomReference类来实现虚引用。
下面是简单的使用例子通过访问引用队列可以得到对象的回收事件 /*** 简单使用虚引用demo* 虚引用在实现一个对象被回收之前必须做清理操作是很有用的,比finalize()方法更灵活*/private static void simpleUsePhantomRefDemo() throws InterruptedException {Object obj new Object();ReferenceQueueObject refQueue new ReferenceQueue();PhantomReferenceObject phantomRef new PhantomReference(obj, refQueue);// nullSystem.out.println(phantomRef.get());// nullSystem.out.println(refQueue.poll());obj null;// 通知JVM的gc进行垃圾回收System.gc();// null, 调用phantomRef.get()不管在什么情况下会一直返回nullSystem.out.println(phantomRef.get());// 当GC发现了虚引用GC会将phantomRef插入进我们之前创建时传入的refQueue队列// 注意此时phantomRef对象并没有被GC回收在我们显式地调用refQueue.poll返回phantomRef之后// 当GC第二次发现虚引用而此时JVM将phantomRef插入到refQueue会插入失败此时GC才会对phantomRef对象进行回收Thread.sleep(200);Reference? pollObj refQueue.poll();// java.lang.ref.PhantomReference1540e19dSystem.out.println(pollObj);if (null ! pollObj) {// 进行资源回收的操作}}比较常见的可以基于虚引用实现JDBC连接池锁的释放等场景。 以连接池为例调用方正常情况下使用完连接需要把连接释放回池中但是不可避免有可能程序有bug造成连接没有正常释放回池中。基于虚引用对Connection对象进行包装并关联引用队列就可以通过轮询引用队列检查哪些连接对象已经被GC回收释放相关连接资源。具体实现已上传github的caison-blog-demo仓库。 总结
对比一下几种引用对象的不同
引用类型GC回收时间常见用途生存时间强引用永不对象的一般状态JVM停止运行时软引用内存不足时对象缓存内存不足时终止弱引用GC时对象缓存GC后终止虚引用配合引用队列使用通过不断轮询引用队列获取对象回收事件。
虽然引用对象是一个非常有用的工具来管理你的内存消耗但有时它们是不够的或者是过度设计的 。例如使用一个Map来缓存从数据库中读取的数据。虽然可以使用弱引用来作为缓存但最终程序需要运行一定量的内存。如果不能给它足够实际足够的资源完成任何工作那么错误恢复机制有多强大也没有用。
当遇到OutOfMemoryError错误第一反应是要弄清楚它为什么会发生也许真的是程序有bug也许是可用内存设置的太低。
在开发过程中应该制定程序具体的使用内存大小而已要关注实际使用中用了多少内存。大多数应用程序在实际运行负载下程序的内存占用会达到稳定状态可以用此来作为参考来设置合理的堆大小。如果程序的内存使用量随着时间的推移而上升很有可能是因为当对象不再使用时仍然拥有对对象的强引用。引用对象在这里可能会有所帮助但更有可能是把它当做一个bug来进行修复。
文章所有涉及源码已经上传github地址https://github.com/caison/caison-blog-demo可以点击查看原文获取。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/910883.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!