西方龙马 | 慎用java.lang.ref.SoftReference实现缓存
发表光阴 >2019-05-23 16:21 来源 网络

  在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)

  "虚引用"顾名思义,便是形同虚设,与其余几种引用都分歧,虚引用并不会决定对象的性命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任什么时候候都可能被渣滓收受接收。

西方龙马LOGO贴图15

  虚引用重要用来跟踪对象被渣滓收受接收的运动。虚引用与软引用和弱引用的一个差别在于:虚引用必需和引用队列(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为100%的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 @ 0x7ff59077b508| 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 |95846830

  int |tableSize |3200

  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 @ 0x7ffbf473ee30| 48 | 8,264

  org.eclipse.jgit.internal.storage.file.ByteArrayWindow @ 0x7ffbf473b980| 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,602 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,504

  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 @ 0x7ffbeee8bc50| 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,080

  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 | 130,335,888

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

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff5d36b1350| 128 | 111,606,864

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

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

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

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

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

  org.eclipse.jgit.internal.storage.file.PackFile @ 0x7ff5923c0150| 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,600

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

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

  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,602个,每个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对象生计的时长(单位毫秒)。简略地将这个参数懂得为一个常量就好,默认值是1000;Sun JVM可以或许或许颠末过程参数:-XX:SoftRefLRUPolicyMSPerMB停止设置;

  西方龙马上述的判断简略地懂得便是:如果SoftReference引用对象的生计时长<=空闲内存可对峙软引用的最大光阴规模,则不清除SoftReference所引用的对象;否则,则将其清除;

  举例:有一个SoftReference,其属性timestamp值为2000,末了一次GC clock值为5000,ms_per_mb值为1000,而且空闲空间为1MB,那么表达式:

  5000-2000<=1000*1

  上述表达式返回值为false(3000>1000),因此,这个SoftReference所引用的对象,会被GC所收受接收;

  如果此时咱咱咱们有4MB的空闲内存,那么这个表达式:

  5000-2000<=1000*4

  上述表达式返回值为true(3000<4000),因此,这个SoftReference所引用的对象,不会被GC所收受接收;

  必要注意的是,JVM老是保留GC以后访过的SoftReference引用的对象。为何?因为GC以后访过的对象,clock-timestamp老是等于0,即使你颠末过程参数-XX:SoftRefLRUPolicyMSPerMB设置ms_per_mb=0,表达式interval<=free_heap*ms_per_mb老是返回true,所以得出上述的结论;

  参考上述的实践,咱咱咱们大概可以或许或许估算一下当一个对象仅有SoftReference引用可达时,其最大性命的周期环境:

  SoftRefLRUPolicyMSPerMB:1000ms(默认值)

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

  1M: 1S

  10M: 10S

  100M: 100S

  1000M 1000S

  SoftRefLRUPolicyMSPerMB:100ms

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

  1M 0.1S

  10M 1S

  100M 10S

  1000M 100S

  SoftRefLRUPolicyMSPerMB:10ms

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

  1M 0.01S

  10M 0.1S

  100M 1S

  1000M 10S

  10000M 100S

  SoftRefLRUPolicyMSPerMB:5ms

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

  2M 0.01S

  20M 0.1S

  200M 1S

  2000M 10S

  20000M 100S

  SoftRefLRUPolicyMSPerMB:1ms

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

  1M 0.001S

  10M 0.01S

  100M 0.1S

  1000M 1S

  10000M 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余年,盼望咱咱咱们的宝贵经验和独到见解可以或许或许帮助到你。

西方龙马-商务素材2
下一篇:没有了
友情链接:优质网络科技资讯网  量海科技新闻网  股票入门网  大河报旅游网  香港都市日报网  互动钓鱼网  绳艺小说  科技日报网  蓝夸克发型网  山西理财财经网