在JVM内部实现缓存容器,东方龙马认为最麻烦的事情是要对缓存大小进行控制。为何这样说?当我们缓存的是一些值对象(ValueObject)时,一个难点是计算这一些对象(及对象引用的大小)。JVM的API并没有赋予我们通过简单的调用即可获得对象(及其引用)大小的能力。当然,你可以通过ObjectOutputStream又或者自定义的方式将对象转换成二进制数据[bytes],从而做到精确控制缓存占用的内存,但是带来的一个问题是对象的序列化与反序列化带来的开销。
这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
强引用的例子:方法局部变量、JNI变量、类变量,概括起来,就是所有GC Root引用可达的都是强引用;
如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
如果一个对象只具有弱引用,那就类似于可有可无的生活用品。 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
虚引用主要用来对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采 取必要的行动。
某用户使用Gerrit2作为其代码管理的工具。系统运维工程师反映,近期系统在运行过程中频繁出现性能问题,最终用户使用系统时经常出现挂起(无响应)。运行如下:
接到这个问题,遵循既定的思,让用户做一定的准备,调整JVM的参数捕获故障时的现场信息进行问题分析。最后定位为JVM Heap频繁的Full GC问题导致应用出现性能故障,参考如下:
JVM GC日志显示,每一次GC以后,JVM Heap空闲的空间仍然有1GB以上的空间可用;
原始的JVM GC日志显示,在故障时间点附近,有非常频繁的Full GC,触发的原因为JVM Old区满,并且每次Full GC后,Old区能出来的空闲空间相当少;但是整个JVM总计的空闲Heap仍然有1GB以上的空间。
笔者尝试分析一下缓存的机制,容器组件RepositoryCache以及WindowCache 其使用的是正是java.lang.ref.SoftReference对缓存对象进行引用。并且,RepositoryCache组件没有缓存消耗机制(例如缓存的对象的数量或者缓存总计大小),而WindowCache组件虽然有控制缓存文件数量及总计内存大小,但是最终的结果与实际想要控制的差距太大,并未如设想那样有效地控制内存消耗。
既然程序是使用java.lang.ref.SoftReference保持对缓存对象的引用,参考原来Sun的说法,如果一个对象只有软引用可达,在内存不足时,是可以被回收的,那关键的问题是JVM的GC如何判定这个SoftReference引用的对象何时被回收?
是否保留SoftReference引用对象的判断参考表达式,true为不回收,lse 为回收:
东方龙马上述的判断简单地理解就是:如果SoftReference引用对象的时长=空闲内存可保持软引用的最大时间范围,则不清除SoftReference所引用的对象;否则,则将其清除;
参考上述的理论,我们大概可以估算一下当一个对象仅有SoftReference引用可达时,其最大生命的周期情况:
设置较大的JVM Heap时,因为Sun的New Generation与Old Generation比例关系,每一次GC以后,New Generation出来的空闲空间的数量,总是使SoftReference引用的对象的周期保持在一个较大的值,换言而之,其淘汰的速度较慢。而Old Generation满频繁触发的Full GC以及内存碎片整理,使得整个JVM非常卡顿;
而设置更大的JVM Heap后,使得每一次GC以后,New Generation出来的空闲空间的数量更多,从而加剧了这种故障的情况;
推荐:
网友评论 ()条 查看