有关 SoftReference 的一些事实

简介: Java 的 SoftReference 有很多年都没有被人惦记了。在 Javadoc 里, 它的描述是这样:   ”虚拟机在抛出 OutOfMemoryError 之前会保证所有的软引用对象已被清除。
Java 的 SoftReference 有很多年都没有被人惦记了。在 Javadoc 里, 它的描述是这样:
 
”虚拟机在抛出 OutOfMemoryError 之前会保证所有的软引用对象已被清除。此外,没有任何约束保证软引用将在某个特定的时间点被清除,或者确定一组不同的软引用对象被清除的顺序。不过,虚拟机的具体实现会倾向于不清除最近创建或最近使用过的软引用。“
 
这个类可以直接被用来实现简单的缓存,这个类或派生的子类也可用于较大的数据结构,来实现更加复杂的缓存。只要软引用可以到达该对象,就是说,该对象实际上是在使用,软引用就不会被清除。这样能够实现一个复杂的缓存,例如,使用强引用来关联最近使用的项目以防止对象被清除,而剩下的项目(使用软引用)抛给垃圾收集器去自由衡量。“
 
这里告诉我们什么?
1. 在你看到 OutOfMemoryError 前,Java 虚拟机一定会回收 SoftReference 对象;
2. Java 不保证 SoftReference 对象何时被清除,相关的机制是 JVM 实现相关的;
3. Java 提供 SoftReference 的期望是更好的实现缓存。
 
恩,看起来  很好很强大。JVM 会负责保留最近最新使用过的软引用,简直完美。但是,喂喂,有没有人在实际项目里用过 SoftReference 以及仔细观察过它的清除? 
 
结果告诉我,现实是骨感的:
1. 如果你的进程所占的内存不是满到要抛 OutOfMemoryError 的程度,JVM 根本不清理 SoftReference 占用的内存。
2. 软引用对象占用了一大堆内存,更糟糕的是它们都会进入 Old-Gen。这样你的进程会频繁触发 Full GC,但即使这样,JVM 也不一定会清理 SoftReference 占用的内存。
3. 因为 Old-Gen 现在是满负荷工作,你会发现一次 FullGC 的时间变得异常的长。
 
简直太坑爹了,那 JVM 什么时候才清理 SoftReference 呢? 
 
这里的正确答案是 ”这是 JVM 的自由,凡人无法干涉“。恩,尽管凡人无法干涉 JVM,但是可以使点小手段欺骗:
try { 
    Object[] ignored = new Object[(int) Runtime.getRuntime().maxMemory()];
} catch (Throwable e) {
    // Ignore OME
}
 (来源: http://stackoverflow.com/questions/3785713/how-to-make-the-java-system-release-soft-references
 
上面这段代码可以让 JVM 立即回收 SoftReference,很猛很暴力。
 
那么,常见的 JVM,例如 HotSpot 是怎么回收 SoftReference 的呢? 谢天谢地,已经有人给出了研究结果:
 
直接翻译一下结论,是这样的:
 
”发生 GC 的时候,是否清理 SoftReference 取决于两个因素:
1. 引用的时间戳;
2. 有多少可用内存。
 
计算公式非常简单,首先定义:
free_heap    - 堆里的空闲内存数量,单位是 MB
interval       - 上一次 GC 时间与与引用记录的时间戳之间的时间间隔
ms_per_mb - 是一个毫秒数常量,表示每 MB 空闲堆中保留的 SoftReference 数量。
 
判定公式是:
interval <= free_heap * ms_per_mb
 
其中 ms_per_mb 是一个可以设置的 JVM 参数:-XX:SoftRefLRUPolicyMSPerMB,结合公式很容易看明白,这个参数决定 FullGC 保留的 SoftReference 数量,参数值越大,GC 后保留的软引用对象就越多。
 
有些博客在推荐 JVM 参数时,建议 -XX:SoftRefLRUPolicyMSPerMB 配置成 0 ,这样可以避免在 GC 后保留 SoftReference。是否这样就可以完全避免软引用回收的问题?我想只有 JVM 知道了。
 
这里也揭示了 JVM 回收 SoftReference 的算法,注意它并不是真正淘汰最久最少访问的对象,而是根据内存余量,淘汰最近未访问的对象。相比真正的 LRU 淘汰算法,这显得比较粗放。
 
上面这些事实背后,我的结论是,使用 SoftReference 前需要谨慎考虑:
1. 你的应用的确需要把这些对象保留在 JVM 中,如果内存够用就永不清理吗?
2. 这些软引用对象会不会过分占用内存,导致你的应用内存压力增加,频繁 Full GC?
3. 除了 SoftReference, 你有没有更好管理这些对象的机制?
 
最后,决定使用 SoftReference 的同学,请三思~
目录
相关文章
FEC1 Waiting for PHY auto negotiation to complete......... TIMEOUT !
FEC1 Waiting for PHY auto negotiation to complete......... TIMEOUT !
533 0
|
算法 Python
LightGBM高级教程:自动调参与超参数优化
LightGBM高级教程:自动调参与超参数优化【2月更文挑战第5天】
1535 2
|
数据挖掘 数据格式
跟着Cell学作图 | 6.时间序列分析(Mfuzz包)
这篇2020年发表在cell上关于新冠的组学文章里面有大量的生信内容。今天带大家复现其中的一个Supplemental Figure:时间序列分析图。
1105 0
跟着Cell学作图 | 6.时间序列分析(Mfuzz包)
|
11月前
|
SQL 监控 Oracle
Oracle SQL性能优化全面指南
在数据库管理领域,Oracle SQL性能优化是确保数据库高效运行和数据查询速度的关键
1442 6
|
SQL 分布式计算 MaxCompute
MaxCompute异常问题之运行语句异常如何解决
MaxCompute异常涉及到在使用阿里云MaxCompute大数据计算服务时遇到的各种错误和问题;本合集将提供针对MaxCompute异常的分析和解决方案,帮助用户处理数据处理、分析任务中的异常情况。
|
Oracle 关系型数据库 数据库
Oracle数据恢复—Oracle数据库误truncate table的数据恢复案例
北京某国企客户Oracle 11g R2数据库误truncate table CM_CHECK_ITEM_HIS,表数据丢失,业务查询到该表时报错,数据库的备份不可用,无法查询表数据。 Oracle数据库执行Truncate命令的原理:在执行Truncate命令后ORACLE会在数据字典和Segment Header中更新表的Data Object ID,但不会修改实际数据部分的块。由于数据字典与段头的DATA_OBJECT_ID与后续的数据块中的并不一致,所以ORACLE服务进程在读取全表数据时不会读取到已经被TRUNCATE的记录,但是实际数据未被覆盖。
Oracle数据恢复—Oracle数据库误truncate table的数据恢复案例
|
C++
<iomanip>库中setw(),setfill()等函数的使用
<iomanip>库中setw(),setfill()等函数的使用
401 0
|
存储 缓存 监控
快速掌握Redis优化要点,告别性能瓶颈!
# Redis优化指南 了解如何提升Redis性能,从读写方式(整体与部分)、KV size、Key数量、读写峰值、命中率、过期策略、平均穿透加载时间、可运维性、安全性等方面着手。选择合适的读写策略,如只整体读写或部分读写变更,优化KV size避免过大或差异过大,合理管理Key数量,应对不同读写峰值,监控命中率并持续优化,设置智能过期策略,减少平均穿透加载时间,确保高可运维性并强化安全性。一起探索Redis的性能潜力!
2682 5
|
存储 弹性计算 大数据
什么是云服务器ECS及其优势、购买、使用方式和部署建议
什么是阿里云云服务器ECS及其优势、购买、使用方式和部署建议,阿里云服务器全方位介绍包括云服务器ECS优势、云服务器租用价格、云服务器使用场景及限制说明,阿里云百科分享云服务器ECS介绍、个人和企业免费试用、云服务器活动、云服务器ECS规格、优势、功能及应用场景详细说明
788 0
|
存储 搜索推荐 Java
衣物搭配系统 毕业设计 JAVA+Vue+SpringBoot+MySQL
衣物搭配系统 毕业设计 JAVA+Vue+SpringBoot+MySQL
374 0