东方龙马 | 慎用java.lang.ref.SoftReference实现缓存

  • 时间:
  • 浏览:0

  在JVM内控 实现缓存容器,东方龙马认为最麻烦的事情是要对缓存大小进行控制。为何在么在在那我说?当亲们缓存的是某些值对象(ValueObject)时,一几条 难点是计算你这种些对象(及对象引用的大小)。JVM的API并这麼 赋予亲们通过简单的调用即可获得对象(及其引用)大小的能力。当然,我能 通过ObjectOutputStream又为何让自定义的最好的最好的办法将对象转去掉 二进制数据[bytes],从而做到精确控制缓存占用的内存,为何让带来的一几条 问题图片图片是对象的序列化与反序列化带来的开销。

  JVM的Reference(java.lang.ref.Reference:Since JDK1.2)的跳出似乎给开发者带来了美好的前景。关于Java编程中的引用,粗略介绍如下:

  1.强引用

  这是使用最普遍的引用。为何让一几条 对象具有强引用,那就这类于必不可少的生活用品,垃圾回收器绝不必回收它。当内存空 间匮乏,Java虚拟机宁愿抛出OutOfMemoryError错误,使应用多多线程 异常终止,本来必靠随意回收具有强引用的对象来处理内存匮乏问题图片图片。

  强引用的例子:最好的最好的办法局部变量、JNI变量、类变量,概括起来,本来所有GC Root引用可达的还会 强引用;

  2.软引用(SoftReference)

  为何让一几条 对象只具有软引用,那就这类于可有可无的生活用品。为何让内存空间足够,垃圾回收器就不必回收它,为何让内存空间匮乏了,就会回收哪些对象的内存。本来垃圾回收器这麼 回收它,该对象就能非要被应用多多线程 使用。软引用可用来实现内存敏感的高速缓存。

  软引用能非要和一几条 引用队列(ReferenceQueue)联合使用,为何让软引用所引用的对象被垃圾回收,Java虚拟机就会把你这种软引用加入到与之关联的引用队列中。

  3.弱引用(WeakReference)

  为何让一几条 对象只具有弱引用,那就这类于可有可无的生活用品。 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器应用多多线程 扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够是是是不是,还会 回收它的内存。不过,为何让垃圾回收器是一几条 优先级很低的应用多多线程 , 为何让不还会 快一点 发现哪些只具有弱引用的对象。

  弱引用能非要和一几条 引用队列(ReferenceQueue)联合使用,为何让弱引用所引用的对象被垃圾回收,Java虚拟机就会把你这种弱引用加入到与之关联的引用队列中。

  4.虚引用(PhantomReference)

  "虚引用"顾名思义,本来形同虚设,与某些几种引用还会 同,虚引用无须会决定对象的生命周期。为何让一几条 对象仅持有虚引用,这麼 它就和这麼 任何引用一样,在任何前一天 都为何让被垃圾回收。

  虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一几条 区别在于:虚引用都要和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一几条 对象时,为何让发现它还有虚引用,就会在回收对象的内存前一天 ,把你这种虚引用加入到与之关联的引用队列中。应用多多线程 能非要通过判断引用队列中是 否为何让加入了虚引用,来了解被引用的对象是是是不是将要被垃圾回收。应用多多线程 为何让发现某个虚引用为何让被加入到引用队列,这麼 就能非要在所引用的对象的内存被回收前一天 采 取必要的行动。

  实际上,虚引用的get,一个劲返回null。

  java.lang.ref你这种包(很重是java.lang.ref.SoftReference)似乎把开发者从繁琐的以及容易出问题图片图片的内存管理中解放了出来:既不担心在内存消耗越多时怎么快速地释放内存,本来担心缓存管理不当带来的内存泄漏,事实亲们说这麼 么?让亲们来看一几条 实际的案例。

  某用户使用Gerrit2作为其代码管理的工具。系统运维工程师反映,近期系统在运行过程中频繁跳出性能问题图片图片,最终用户使用系统时跳出挂起(无响应)。运行环境如下:

  OS:Linux

  中间件:Gerrit2

  JDK:Sun JDK1.8_0_x

  JVM Heap分配:16G/32G

接到你这种问题图片图片,遵循既定的思路,让用户做一定的准备,调整JVM的参数捕获故障时的现场信息进行问题图片图片分析。最后定位为JVM Heap频繁的Full GC问题图片图片原因分析 应用跳出性能故障,参考如下:

  JVM GC日志显示,每一次GC前一天 ,JVM Heap空闲的空间仍然有1GB以上的空间可用;

  为何让有Overhead为60 %的GC清况 ;

  分析GC Completed以及Overhead清况 ,在接近故障点时,有明显的GC频繁及GC时间上升(峰值5923ms);

  原始的JVM GC日志显示,在故障时间点附近,有非常频繁的Full GC,触发的原为何让JVM Old区满,为何让每次Full GC后,Old区能释放出来的空闲空间相当少;为何让整个JVM总计的空闲Heap仍然有1GB以上的空间。

  性能问题图片图片原因分析 :JVM Old区满,频繁的Full GC原因分析 应用性能下降非常严重;

  附注:

  GC Completed or GC :Time(millisecond) spent during garbage collection.

  Overhead: Ratio(%) time spent in allocation failure vs. time between AF

  继续深入分析问题图片图片,亲们发现了内存中趋于稳定的大对象:

  Class Name | Shallow Heap | Retained Heap

  ---------------------------------------------------------------------------------------------------

  org.eclipse.jgit.internal.storage.file.WindowCache @ 0x7ff59077b60 8| 104 | 20,638,034,208

  ---------------------------------------------------------------------------------------------------

  Type |Name |Value

  -------------------------------------------------------------------------------------------------------

  ref |openBytes |20382985278

  ref |openFiles |1859

  int |windowSize |8192

  int |windowSizeShift|13

  boolean|mmap |false

  long |maxBytes |10485760

  int |maxFiles |16384

  int |evictBatch |64

  ref |evictLock |java.util.concurrent.locks.ReentrantLock @ 0x7ff590c04510

  ref |locks |org.eclipse.jgit.internal.storage.file.WindowCache$Lock[16384] @ 0x7ff590e9c7c0

  ref |table |java.util.concurrent.atomic.AtomicReferenceArray @ 0x7ff59077b5c0

  ref |clock |95846860

  int |tableSize |360

  ref |queue |java.lang.ref.ReferenceQueue @ 0x7ff59077b570

  -------------------------------------------------------------------------------------------------------

  Class Name | Shallow Heap | Retained Heap

  ------------------------------------------------------------------------------------------------------

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf48e46a0| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf47ba558| 48 | 48

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf478bff0| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf478bf40| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf478be90| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf473ef90| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf473eee0| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf473ee60 | 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf473b960 | 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf4736210| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf47344e0| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf47343d0| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf4727498| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf46640d0| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf4664020| 48 | 8,264

  Total: 15 of 2,488,60 2 entries; 2,488,587 more | |

  ------------------------------------------------------------------------------------------------------

  评析:

  Class Name | Shallow Heap | Retained Heap

  -----------------------------------------------------------------------------------------------------

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbf42d39e0| 112 | 6,312

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbf3999e48| 112 | 5,752

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbf385dd28| 112 | 264

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbf27e1c20| 112 | 12,60 4

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbf148de08| 112 | 10,048

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbf0b97010| 112 | 12,240

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbef2869e0| 112 | 9,352

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbeee8bc60 | 112 | 41,408

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbeee26698| 112 | 10,000

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbec1c1318| 112 | 9,888

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbec1ba1a0| 112 | 9,920

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbeb619898| 112 | 47,144

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbe94a62a0| 112 | 11,696

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbe90dd688| 112 | 9,060

  org.eclipse.jgit.internal.storage.file.FileRepository @ 0x7ffbe56b3f88| 112 | 12,344

  Total: 15 of 3,379 entries; 3,364 more | |

  -----------------------------------------------------------------------------------------------------

  评析:

  。

  Class Name | Shallow Heap | Retained Heap

  -----------------------------------------------------------------------------------------------

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff593248670| 128 | 168,684,904

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff5ca5e57e0| 128 | 163,743,112

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff65d2797c8| 128 | 160 ,335,888

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff67ed5a5a0| 128 | 116,092,248

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff5d36b1360 | 128 | 111,60 6,864

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff741d9c960 | 128 | 92,786,784

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff5c56577d0| 128 | 55,945,60 8

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff5d4cb7ed0| 128 | 31,60 6,712

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff5e3ec9c60 | 128 | 26,108,840

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff593a07f60 | 128 | 21,771,144

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff5923c060 | 128 | 20,065,688

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff5b7dd8768| 128 | 17,462,328

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff5d74ec5c0| 128 | 16,689,60 0

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff65327b220| 128 | 15,634,496

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff677da56e0| 128 | 13,699,60 8

  Total: 15 of 6,459 entries; 6,444 more | |

  -----------------------------------------------------------------------------------------------

  org.eclipse.jgit.internal.storage.file.WindowCache.openBytes接近20G,org.eclipse.jgit.internal.storage.file.ByteArrayWindow对象实例达2,488,60 一几条 ,每个8K,总计19,908,816KB(20,386,627,584Byte)。org.eclipse.jgit.internal.storage.file.FileRepository对象实例3,379个,org.eclipse.jgit.internal.storage.file.PackFile对象实例6,459个。

  问题图片图片来到这里基本上就清晰了:JGit4.1 org.eclipse.jgit.lib.RepositoryCache以及org.eclipse.jgit.internal.storage.file.WindowCache缓存的PackFile以及ByteArrayWindow占用了大片的内存空间。缓存占用了大片Old区的内存,为何让触发了频繁的Full GC原因分析 性能问题图片图片的趋于稳定。开始英文英语 的时侯,笔者也犯了一几条 同样肤浅的错误,建议客户通过增大JVM Heap对问题图片图片进行缓解,但最终的结果是:服务器趋于稳定问题图片图片的频率比设置32G的时侯更频繁;

  笔者尝试分析一下缓存的机制,容器组件RepositoryCache以及WindowCache 其使用的是正是java.lang.ref.SoftReference对缓存对象进行引用。为何让,RepositoryCache组件这麼 缓存消耗机制(这类缓存的对象的数量为何让缓存总计大小),而WindowCache组件我我觉得有控制缓存文件数量及总计内存大小,为何让最终的结果与实际你可不后能 控制的差距越多,并未如设想那样有效地控制内存消耗。

  既然应用多多线程 是使用java.lang.ref.SoftReference保持对缓存对象的引用,参考那我Sun的说法,为何让一几条 对象非要软引用可达,在内存匮乏时,是能非要被回收的,那关键的问题图片图片是JVM的GC怎么判定你这种SoftReference引用的对象什么时间被回收?

  通过Google大神,东方龙马终于找到相关参考的文章,以下为原文参考:

  对于java.lang.ref.SoftReference对象,有一几条 全局的变量clock(实际上本来java.lang.ref.SoftReference的类变量clock,如下图代码所示):其保持了最后一次GC的时间点(以毫秒为单位),即每一次GC趋于稳定时,该值均会被重新设置。 同时,java.lang.ref.SoftReference对象实例均有一几条 timestamp的属性,其被设置为最后一次成功通过SoftReference对象获取其引用对象时的clock的值(最后一次GC)。本来,java.lang.ref.SoftReference对象实例的timestamp属性,保持的是你这种对象被访问时的最后一次GC的时间戳;

  当GC趋于稳定时,以下一几条 因素影响SoftReference引用的对象是是是不是被回收:

  1、SoftReference 对象实例的timestamp有多旧;

  2、内存空闲空间的大小;

  是是是不是保留SoftReference引用对象的判断参考表达式,true为不回收,false 为回收:

  interval<=free_heap*ms_per_mb

  说明:

  interval:最后一次GC时间和SoftReference对象实例timestamp的属性的差。简单理解本来你这种SoftReference引用对象的生存的时长;

  free_heap:JVM Heap中空闲空间大小,单位为MB

  ms_per_mb:每1M空闲空间可保持的SoftReference对象生存的时长(单位毫秒)。简单地将你这种参数理解为一几条 常量就好,默认值是60 0;Sun JVM能非要通过参数:-XX:SoftRefLRUPolicyMSPerMB进行设置;

  东方龙马上述的判断简单地理解本来:为何让SoftReference引用对象的生存时长<=空闲内存可保持软引用的最大时间范围,则不清除SoftReference所引用的对象;为何让,则将其清除;

  举例:有一几条 SoftReference,其属性timestamp值为60 0,最后一次GC clock值为60 00,ms_per_mb值为60 0,为何让空闲空间为1MB,这麼 表达式:

  60 00-60 0<=60 0*1

  上述表达式返回值为false(60 0>60 0),为何让,你这种SoftReference所引用的对象,会被GC所回收;

  为何让此时亲们有4MB的空闲内存,这麼 你这种表达式:

  60 00-60 0<=60 0*4

  上述表达式返回值为true(60 0<60 0),为何让,你这种SoftReference所引用的对象,不必被GC所回收;

  都要注意的是,JVM一个劲保留GC前一天 访问过的SoftReference引用的对象。为何在么在在?为何让GC前一天 访问过的对象,clock-timestamp一个劲等于0,即使你通过参数-XX:SoftRefLRUPolicyMSPerMB设置ms_per_mb=0,表达式interval<=free_heap*ms_per_mb一个劲返回true,本来得出上述的结论;

  参考上述的理论,亲们大慨能非要估算一下当一几条 对象仅有SoftReference引用可达时,其最大生命的周期清况 :

  SoftRefLRUPolicyMSPerMB:60 0ms(默认值)

  空闲空间 清理间隔(生存周期上限)

  1M: 1S

  10M: 10S

  60 M: 60 S

  60 0M 60 0S

  SoftRefLRUPolicyMSPerMB:60 ms

  空闲空间 清理间隔(生存周期上限)

  1M 0.1S

  10M 1S

  60 M 10S

  60 0M 60 S

  SoftRefLRUPolicyMSPerMB:10ms

  空闲空间 清理间隔(生存周期上限)

  1M 0.01S

  10M 0.1S

  60 M 1S

  60 0M 10S

  60 00M 60 S

  SoftRefLRUPolicyMSPerMB:5ms

  空闲空间 清理间隔(生存周期上限)

  2M 0.01S

  20M 0.1S

  60 M 1S

  60 0M 10S

  60 00M 60 S

  SoftRefLRUPolicyMSPerMB:1ms

  空闲空间 清理间隔(生存周期上限)

  1M 0.001S

  10M 0.01S

  60 M 0.1S

  60 0M 1S

  60 00M 10S

  至此,对于上述案例的故障成因,东方龙马有了一几条 更高度次的认识:

  设置较大的JVM Heap时,为何让Sun的New Generation与Old Generation比例关系,每一次GC前一天 ,New Generation释放出来的空闲空间的数量,一个劲使SoftReference引用的对象的生存周期保持在一几条 较大的值,换言而之,其淘汰的高度较慢。而Old Generation满频繁触发的Full GC以及内存碎片收集,使得整个JVM非常卡顿;

  而设置更大的JVM Heap后,使得每一次GC前一天 ,New Generation释放出来的空闲空间的数量更多,从而加剧了你这种故障的清况 ;

  当然,故障的根本成因,是应用应用多多线程 代码并未对缓存进行控制;

  上述案例,在未改动代码及特征的清况 下,通过增大大JVM Heap,以及通过设置参数:-XX:SoftRefLRUPolicyMSPerMB=0处理;

  其它:IBM的JVM针对SoftReference的回收控制,同样有这类参数:-Xsoftrefthreshold进行控制。以下是关于-Xsoftrefthreshold的描述:

  Sets the number of GCs after which a soft reference will be cleared if its referent has not been marked. The default is 32, meaning that on the 32nd GC where the referent is not marked the soft reference will be cleared.

  开始英文英语 语:

  JVM的Reference(java.lang.ref.Reference:Since JDK1.2)并未像其描述的那样美好,很重是java.lang.ref.SoftReference的使用。同样地,即使是使用Reference实现In-Box的缓存,也都要充分考虑其对内存的消耗。那我才使亲们的应用运行得更稳定。

  东方龙马凭借在数据库,中间件领域耕耘20余年,希望亲们的宝贵经验和独到见解能非要帮助到你。