前言:
上次碰到个WeakHashMap,搞了一下,没明白, 碰到了引用包, 没太理解它的用处, 这次看JDK中Proxy的源码又碰到了,如鲠在喉, 一定要解决它
对象的回收过程:
标记阶段(此阶段, 一切工作必须停止, 如果有对象覆盖了finalize方法, 会先调用这个方法, 而暂时不做标记, 而推迟到下次gc的时候再做标记)
清理阶段(此阶段和工作可能并行进行)
这里大致为那些不懂gc的人解释一下:
标记阶段的主要任务就是寻找那些可以回收的对象(即不能通过你现有的所有引用直接或间接到达的对象), 这个阶段, 需要在JVM上面运行的程序暂停下来进行标记.
而清理阶段, 通常程序都不用暂停, 清理线程可以和程序并行执行, 因为他们所操作的内存并不相交.
引入引用包的目的, 就是把java中的常规引用标记为一种特定的引用, 在某些特定的情况下, gc可以对它进行回收, 而避免耗尽所有内存, 导致的JVM中常见的 OutOfMemoryError 错误的发生, 使得程序可以正常运转.
这种特殊的引用常用持有缓存的引用, 如将一个大图片放到内存中, 而避免从磁盘上读取, 当内存紧张时, 可以将其从内存中清除, 回收内存. 当再有需要时, 再将其读入内存.
如果是普通的java引用变量, 由于这个图片对象被引用, 所以一直都不会释放, 无法从内存中清除, 而用特殊的Reference来持有这个对象, 当你需要的时候, 你可以从Reference类中获取这个对象, 当这个对象被gc回收, Reference就会返回一个null, 这个时候, 你再加入额外的逻辑, 从磁盘中加载这个对象到内存中来.实现缓存的功能.
Reference下面有3个子类, 他们的回收时机各不一样:
SoftReference(合适做缓存)
当jvm把所有分配给他的内存都使用了,仍然不够, 那么gc就会收集软引用. 回收时(清除阶段), 软引用会被放入ReferenceQueue队列中, 并且解除它和引用对象的关联, 所以通过get()返回其所指向的引用对象时会null.
(将SoftReference放入ReferenceQueue对列是有原因的, 请看WeakHashMap部分)
WeakReference
和软引用类似, 区别在于gc发现WeakReference引用的对象时,就进行gc, 当在清除阶段时, 对象一旦清除, 就将其加入到ReferenceQueue中. 这时调用它的get()同样返回null
PhantomReference
这个和上面两个引用不同在于, 当它引用的对象一旦被gc发现,就会进行回收, 不过在清除之前,会现将其放入ReferenceQueue队列, 而不是等到引用对象的内存被清除了才放入. 它的get()方法始终返回null.
(get()方法始终返回null也是有原因的, PhantomReference常被用作finalize的一个替代品, 原因见下面的解释)
Why PhantomReference?
让通知更加精准, 即在finalize之后, 内存回收之前. 而不是只在finalize中.
用 finalize的时候, 首先发现此对象不可及, 为了避免此对象在finalize方法中把this赋给一个强引用造成的复活,而错误被收集的情况, gc不会在调用finalize之后立即清除此对象, 而是会在第二次gc的时候清除其内存.当内存很少, 很快就要OutOfMemoryError发生时, 这种等待是致命的, 而虚引用就没这种缺点, 一次gc即可收集.在标记阶段, 如果发现此虚引用的对象不可及了, 那么就加入队列, 在清理阶段就一次性回收了. (注意, 标记阶段是同步的, 清理阶段是异步的)
Why ReferenceQueue?
为什么要在引用们所指向的referent被gc之后, 要把引用加入到ReferenceQueue之中呢?
想象一下, 如果你将WeakReference<key>放入Map中,而不是key直接放入map中.那么当key只被WeakReference引用时,这个对象就会被gc回收, 当key被回收之后, 其对应的也没有存在的意义了.
怎么才能知道这个key被回收了呢?
只有知道key被回收了, 我们就能及时的清除, 减少内存的消耗.
简单, 因为一旦key被清除, WeakReference就会被放入ReferenceQueue, 你可以从ReferenceQueue中找到它, 并将其作为key, 去Map中索引, 用remove方法将其移除.
而 WeakHashMap就是帮你自动实现了这个Map<WeakReference<key>, >的类, 现在你再也不需要用WeakReference<key>作为key了, 你可以直接将key放入其中, WeakHashMap<key, >会自动为你做上述一切工作.
参考资料:
1. 理解弱引用
http://weblogs.java.net/blog/enicholas/archive/2006/05/understanding_w.html
2. 引用类使用指南
https://www.ibm.com/developerworks/cn/java/j-refs/