为了支持变长字段和更丰富的数据类型,我们在搜索项目中对lucene的FieldCache做了部分扩展;但使用下来碰到了内存泄漏,所以这里对 lucene的FieldCache做下细致的了解。FieldCache使用WeakHashMap做核心的cache管理,key是IR对象内部的 frequency文件对象,value是对应字段的数组。虽然WeakHashmap像个核武器,声称可以自动释放对象;为了分析问题,我们更改了解他 的工作原理,为了了解WeakHashmap,也需要知道WeakReference的工作机制。
1. java的Reference
java把引用(即Reference)分成4种类型,从强到弱分别是:Strong, Soft, Weak, Phantom。这4种不同的reference是java语言规定的内存管理和回收的规范。我们都知道,java的内存,用户只负责申请,不负责释放, 由jvm虚拟机通过garbage collection的形式进行回收。内存回收一般是通过mark-sweep的形式进行的,从当前所有线程堆栈和寄存器中的对象往外扩展,mark所有 引用路径上的对象;结束后,未被mark的对象可以认为是dead对象,可以回收。这种mark-sweep模式是比较好理解的;但java为了做朵花出 来,在mark的过程中,支持对对象进行分类(就是这四种reference了),而且每种分类垃圾回收的策略不一样:strong reference就是我们常用的,不做任何修饰,只要在引用路径上一定会被mark,防止被gc回收;SoftReference,如果到某个对象的最 后一个引用是soft,那么这个对象是否被mark,要视heap内存情况而定,空闲较多就mark,内存紧张就不mark让gc回 收;weakreference是soft的弱化版,如果最后一个引用是weak,gc时一定不会mark;而phantom纯粹是搞笑,唯一的用处就在 ReferenceQueue上。
比如说有一个WeakReference的数组或者一个hashmap的key是WeakReference(比如weakhashmap), 怎么知道哪些WeakReference指向的对象被回收了呢?一种办法是定期逐个扫描并调用其get方法,返回null就知道被内存回收了 (Phantom用不了,因为总是返回null),这种方法很明显低效。ReferenceQueue就是为了配合这个来使用的,构造 WeakReference时传递一个ReferenceQueue对象,这样当gc要回收它指向的对象时,会把这个reference对象加到 queue中,外部通过检查这个queue就可以知道哪些对象被回收了。
关于java的reference有一篇经典的介绍可以参考:http://weblogs.java.net/blog/enicholas/archive/2006/05/understanding_w.html。写惯了c/c++,用这种高级的玩意很不习惯。
2. WeakHashmap的实现
顺便说一下WeakHashMap的实现机制,key是一个weakreference对象,value是强对象。如果 WeakReference指向的key对象被gc默默回收了,而weakreference作为hashmap的key还存在,value是强对象也不 会被回收。WeakHashmap使用了ReferenceQueue来解决这个问题,key被gc回收的时候,对应的weakreference对象对 被加入到queue中;当weakhashmap发生方法调用时,在执行真正方法前,会去遍历下这个queue,把相应的key从hashmap中删 除,value自然也就没有引用了,下次gc时就可以被回收了。有一篇文档讲的很清楚,可以参考:http://mikab.iteye.com /blog/587995。
3. lucene的FieldCache实现
知道这些,我们就很容易理解lucene的FieldCache了,他的key是indexreader对象的frequency文件对象,不直接用indexreader,是为了避免reader之间的浅copy构造,导致一份数据多次cache。解释见:
// This is necessary so that cloned SegmentReaders (which
// share the underlying postings data) will map to the
// same entry in the FieldCache. See LUCENE-1579.
利用WeakHashmap的这个特性,当indexreader释放的时候,FieldCache中这个IR相关的cache都会被释放;但 当从代码中,我们可以看到,在添加entry时,lucene还是手动注册了一个reader closed事件,其中会手动删除这个reader在hashmap中的entry。按道理说,这两个是重复的,既然IR close时会把相应的cache都清楚掉,为什么还需要WeakHashmap的特性呢,lucene为什么这么做?也是有解释 的:https://issues.apache.org/jira/browse/LUCENE-2135
参考:
Understanding weak reference:http://weblogs.java.net/blog/2006/05/04/understanding-weak-references
Understanding weak reference 2:http://dinukaroshan.blogspot.com/2012/01/understanding-java-references.html
WeakHashMap的神话:http://mikab.iteye.com/blog/587995
IndexReader.close should forcefully evict entries from FieldCache: https://issues.apache.org/jira/browse/LUCENE-2135