垃圾收集可以使 Java 程序不会出现内存泄漏,至少对于比较狭窄的 “内存泄漏” 定义来说如此,但是这并不意味着我们可以完全忽略 Java 程序中的对象生存期(lifetime)问题。当我们没有对对象生命周期(lifecycle)引起足够的重视或者破坏了管理对象生命周期的标准机制时, Java 程序中通常就会出现内存泄漏。例如,上一次 我们看到了,不能划分对象的生命周期会导致,在试图将元数据关联到瞬时对象时出现意外的对象保持。还有一些其他的情况可以类似地忽略或破坏对象生命周期管理,并导致内存泄漏。
对象游离
一种形式的内存泄漏有时候叫做对象游离(object loitering),是通过清单 1 中的 LeakyChecksum 类来说明的,清单 1 中有一个 getFileChecksum() 方法用于计算文件内容的校验和。getFileChecksum() 方法将文件内容读取到缓冲区中以计算校验和。一种更加直观的实现简单地将缓冲区作为 getFileChecksum() 中的本地变量分配,但是该版本比那样的版本更加 “聪明”,不是将缓冲区缓存在实例字段中以减少内存 churn。该 “优化”通常不带来预期的好处;对象分配比很多人期望的更便宜。(还要注意,将缓冲区从本地变量提升到实例变量,使得类若不带有附加的同步,就不再是线程安全的了。直观的实现不需要将 getFileChecksum() 声明为 synchronized,并且会在同时调用时提供更好的可伸缩性。)
清单 1. 展示 “对象游离” 的类
// BAD CODE - DO NOT EMULATE
public class LeakyChecksum {
private byte[] byteArray;
public synchronized int getFileChecksum(String fileName) {
int len = getFileSize(fileName);
if (byteArray == null || byteArray.length < len)
byteArray = new byte[len];
readFileContents(fileName, byteArray);
// calculate checksum and return it
}
}
这个类存在很多的问题,但是我们着重来看内存泄漏。缓存缓冲区的决定很可能是根据这样的假设得出的,即该类将在一个程序中被调用许多次,因此它应该更加有效,以重用缓冲区而不是重新分配它。但是结果是,缓冲区永远不会被释放,因为它对程序来说总是可及的(除非 LeakyChecksum 对象被垃圾收集了)。更坏的是,它可以增长,却不可以缩小,所以 LeakyChecksum 将永久保持一个与所处理的最大文件一样大小的缓冲区。退一万步说,这也会给垃圾收集器带来压力,并且要求更频繁的收集;为计算未来的校验和而保持一个大型缓冲区并不是可用内存的最有效利用。
LeakyChecksum 中问题的原因是,缓冲区对于 getFileChecksum() 操作来说逻辑上是本地的,但是它的生命周期已经被人为延长了,因为将它提升到了实例字段。因此,该类必须自己管理缓冲区的生命周期,而不是让 JVM 来管理。
软引用
在 前一期文章 中我们看到了,弱引用如何可以给应用程序提供当对象被程序使用时另一种到达该对象的方法,但是不会延长对象的生命周期。Reference 的另一个子类 —— 软引用 —— 可满足一个不同却相关的目的。其中弱引用允许应用程序创建不妨碍垃圾收集的引用,软引用允许应用程序通过将一些对象指定为 “expendable” 而利用垃圾收集器的帮助。尽管垃圾收集器在找出哪些内存在由应用程序使用哪些没在使用方面做得很好,但是确定可用内存的最适当使用还是取决于应用程序。如果应用程序做出了不好的决定,使得对象被保持,那么性能会受到影响,因为垃圾收集器必须更加辛勤地工作,以防止应用程序消耗掉所有内存。
高速缓存是一种常见的性能优化,允许应用程序重用以前的计算结果,而不是重新进行计算。高速缓存是 CPU 利用和内存使用之间的一种折衷,这种折衷理想的平衡状态取决于有多少内存可用。若高速缓存太少,则所要求的性能优势无法达到;若太多,则性能会受到影响,因为太多的内存被用于高速缓存上,导致其他用途没有足够的可用内存。因为垃圾收集器比应用程序更适合决定内存需求,所以应该利用垃圾收集器在做这些决定方面的帮助,这就是件引用所要做的。
如果一个对象惟一剩下的引用是弱引用或软引用,那么该对象是软可及的(softly reachable)。垃圾收集器并不像其收集弱可及的对象一样尽量地收集软可及的对象,相反,它只在真正 “需要” 内存时才收集软可及的对象。软引用对于垃圾收集器来说是这样一种方式,即 “只要内存不太紧张,我就会保留该对象。但是如果内存变得真正紧张了,我就会去收集并处理这个对象。” 垃圾收集器在可以抛出 OutOfMemoryError 之前需要清除所有的软引用。
通过使用一个软引用来管理高速缓存的缓冲区,可以解决 LeakyChecksum 中的问题,如清单 2 所示。现在,只要不是特别需要内存,缓冲区就会被保留,但是在需要时,也可被垃圾收集器回收:
清单 2. 用软引用修复 LeakyChecksum
public class CachingChecksum {
private SoftReference<byte[]> bufferRef;
public synchronized int getFileChecksum(String fileName) {
int len = getFileSize(fileName);
byte[] byteArray = bufferRef.get();
if (byteArray == null || byteArray.length < len) {
byteArray = new byte[len];
bufferRef.set(byteArray);
}
readFileContents(fileName, byteArray);
// calculate checksum and return it
}
}
一种廉价的缓存
CachingChecksum 使用一个软引用来缓存单个对象,并让 JVM 处理从缓存中取走对象时的细节。类似地,软引用也经常用于 GUI 应用程序中,用于缓存位图图形。是否可使用软引用的关键在于,应用程序是否可从大量缓存的数据恢复。
如果需要缓存不止一个对象,您可以使用一个 Map,但是可以选择如何使用软引用。您可以将缓存作为 Map<K, SoftReference<V>> 或 SoftReference<Map<K,V>> 管理。后一种选项通常更好一些,因为它给垃圾收集器带来的工作更少,并且允许在特别需要内存时以较少的工作回收整个缓存。弱引用有时会错误地用于取代软引用,用于构建缓存,但是这会导致差的缓存性能。在实践中,弱引用将在对象变得弱可及之后被很快地清除掉 —— 通常是在缓存的对象再次用到之前 —— 因为小的垃圾收集运行得很频繁。
对于在性能上非常依赖高速缓存的应用程序来说,软引用是一个不管用的手段,它确实不能取代能够提供灵活终止期、复制和事务型高速缓存的复杂的高速缓存框架。但是作为一种 “廉价(cheap and dirty)” 的高速缓存机制,它对于降低价格是很有吸引力的。
正如弱引用一样,软引用也可创建为具有一个相关的引用队列,引用在被垃圾收集器清除时进入队列。引用队列对于软引用来说,没有对弱引用那么有用,但是它们可以用于发出管理警报,说明应用程序开始缺少内存。
垃圾收集器如何处理 References
弱引用和软引用都扩展了抽象的 Reference 类(虚引用(phantom references)也一样,这将在以后的文章中介绍)。引用对象被垃圾收集器特殊地看待。垃圾收集器在跟踪堆期间遇到一个 Reference 时,不会标记或跟踪该引用对象,而是在已知活跃的 Reference 对象的队列上放置一个 Reference。在跟踪之后,垃圾收集器就识别软可及的对象 —— 这些对象上除了软引用外,没有任何强引用。垃圾收集器然后根据当前收集所回收的内存总量和其他策略考虑因素,判断软引用此时是否需要被清除。将被清除的软引用如果具有相应的引用队列,就会进入队列。其余的软可及对象(没有清除的对象)然后被看作一个根集(root set),堆跟踪继续使用这些新的根,以便通过活跃的软引用而可及的对象能够被标记。
处理软引用之后,弱可及对象的集合被识别 —— 这样的对象上不存在强引用或软引用。这些对象被清除和加入队列。所有 Reference 类型在加入队列之前被清除,所以处理事后检查(post-mortem)清除的线程永远不会具有 referent 对象的访问权,而只具有 Reference 对象的访问权。因此,当 References 与引用队列一起使用时,通常需要细分适当的引用类型,并将它直接用于您的设计中(与 WeakHashMap 一样,它的 Map.Entry 扩展了 WeakReference)或者存储对需要清除的实体的引用。
引用处理的性能成本
引用对象给垃圾收集过程带来了一些附加的成本。每一次垃圾收集,都必须构造活跃 Reference 对象的一个列表,而且每个引用都必须做适当的处理,这给每次收集添加了一些每个 Reference 的开销,而不管该 referent 此时是否被收集。Reference 对象本身服从于垃圾收集,并且可在 referent 之前被收集,在这样的情况下,它们没有加入队列。
基于数组的集合
当数组用于实现诸如堆栈或环形缓冲区之类的数据结构时,会出现另一种形式的对象游离。清单 3 中的 LeakyStack 类展示了用数组实现的堆栈的实现。在 pop() 方法中,在顶部指针递减之后,elements 仍然会保留对将弹出堆栈的对象的引用。这意味着,该对象的引用对程序来说仍然可及(即使程序实际上不会再使用该引用),这会阻止该对象被垃圾收集,直到该位置被未来的 push() 重用。
清单 3. 基于数组的集合中的对象游离
public class LeakyStack {
private Object[] elements = new Object[MAX_ELEMENTS];
private int size = 0;
public void push(Object o) { elements[size++] = o; }
public Object pop() {
if (size == 0)
throw new EmptyStackException();
else {
Object result = elements[--size];
// elements[size+1] = null;
return result;
}
}
}
修复这种情况下的对象游离的方法是,当对象从堆栈弹出之后,就消除它的引用,如清单 3 中注释掉的行所示。但是这种情况 —— 由类管理其自己的内存 —— 是一种非常少见的情况,即显式地消除不再需要的对象是一个好主意。大部分时候,认为不应该使用的强行消除引用根本不会带来性能或内存使用方面的收益,通常是导致更差的性能或者 NullPointerException。该算法的一个链接实现不会存在这个问题。在链接实现中,链接节点(以及所存储的对象的引用)的生命期将被自动与对象存储在集合中的期间绑定在一起。弱引用可用于解决这个问题 —— 维护弱引用而不是强引用的一个数组 —— 但是在实际中,LeakyStack 管理它自己的内存,因此负责确保对不再需要的对象的引用被清除。使用数组来实现堆栈或缓冲区是一种优化,可以减少分配,但是会给实现者带来更大的负担,需要仔细地管理存储在数组中的引用的生命期。
结束语
与弱引用一样,软引用通过利用垃圾收集器在作出缓存回收决策方面的帮助,有助于防止应用程序出现对象游离。只有当应用程序可以忍受大量软引用的对象时,软引用才适合使用。
转自http://www.ibm.com/developerworks/cn/java/j-jtp01246.html
分享到:
相关推荐
2. 高速缓存:高速缓存用于存储操作结果以加快后续查询,但如果不加以控制,缓存中的数据会持续增长,可能导致内存泄漏。理想的解决方案是设置缓存容量上限,并定期移除旧数据,确保新数据有足够空间存储。 除此...
- **用途**:常用于实现内存敏感的高速缓存,例如,当内存不足时,软引用的对象会被回收以释放内存。 - **示例**:创建一个`SoftReference`对象,当内存不足时,软引用的引用会被解除。 3. **弱引用(Weak ...
软引用常用于内存敏感的高速缓存,例如图片缓存。当系统内存紧张时,软引用的对象会被释放,从而释放内存。 弱引用(WeakReference)比软引用更弱,它不会阻止对象被GC回收,只要没有强引用指向对象,即使有弱引用...
`VolatileImage`用于高速的离屏绘图,适用于高性能图形应用。 9. **使用Window Blitting** 窗口blitting技术可以加速窗口内容的移动和复制。 以上就是Java性能优化的一些实用技巧,涵盖不同领域的最佳实践。在...
基于的手势识别系统可控制灯的亮_3
untitled2.zip
S7-1500和分布式外围系统ET200MP模块数据
anaconda配置pytorch环境
高校教室管理系统,主要的模块包括查看首页、个人中心、教师管理、学生管理、教室信息管理、教师申请管理、学生申请管理、课时表管理、教师取消预约管理、学生取消预约管理等功能。
半挂汽车列车横向稳定性控制研究:基于模糊PID与制动力矩分配的联合仿真分析在典型工况下的表现,半挂汽车列车在典型工况下的横向稳定性控制研究:基于模糊PID与制动力矩分配的联合仿真分析,半挂汽车列车4自由度6轴整车model,横向稳定性控制,在低附着系数路面,进行典型3个工况,角阶跃,双移线,方向盘转角。 采用算法:模糊PID,制动力矩分配,最优滑移率滑膜控制。 以上基于trucksim和simulink联合仿真,有对应 p-a-p-e-r参考 ,关键词: 1. 半挂汽车列车 2. 4自由度6轴整车model 3. 横向稳定性控制 4. 低附着系数路面 5. 典型工况(角阶跃、双移线、方向盘转角) 6. 模糊PID算法 7. 制动力矩分配 8. 最优滑移率滑膜控制 9. Trucksim和Simulink联合仿真 10. P-A-P-E-R参考; 用分号隔开上述关键词为:半挂汽车列车; 4自由度6轴整车model; 横向稳定性控制; 低附着系数路面; 典型工况; 模糊PID算法; 制动力矩分配; 最优滑移率滑膜控制; Trucksim和Simulink联合仿真; P-A-P-E-R参考
路径规划人工势场法及其改进算法Matlab代码实现,路径规划人工势场法及其改进算法Matlab代码实现,路径规划人工势场法以及改进人工势场法matlab代码,包含了 ,路径规划; 人工势场法; 改进人工势场法; MATLAB代码; 分隔词“;”。,基于Matlab的改进人工势场法路径规划算法研究
本文介绍了范德堡大学深脑刺激器(DBS)项目,该项目旨在开发和临床评估一个系统,以辅助从规划到编程的整个过程。DBS是一种高频刺激治疗,用于治疗运动障碍,如帕金森病。由于目标区域在现有成像技术中可见性差,因此DBS电极的植入和编程过程复杂且耗时。项目涉及使用计算机辅助手术技术,以及一个定制的微定位平台(StarFix),该平台允许在术前进行图像采集和目标规划,提高了手术的精确性和效率。此外,文章还讨论了系统架构和各个模块的功能,以及如何通过中央数据库和网络接口实现信息共享。
三菱FX3U步进电机FB块的应用:模块化程序实现电机换算,提高稳定性和移植性,三菱FX3U步进电机换算FB块:模块化编程实现电机控制的高效性与稳定性提升,三菱FX3U 步进电机算FB块 FB块的使用可以使程序模块化简单化,进而提高了程序的稳定性和可移植性。 此例中使用FB块,可以实现步进电机的算,已知距离求得脉冲数,已知速度可以求得频率。 程序中包含有FB和ST内容;移植方便,在其他程序中可以直接添加已写好的FB块。 ,三菱FX3U;步进电机换算;FB块;程序模块化;稳定性;可移植性;距离与脉冲数换算;速度与频率换算;FB和ST内容;移植方便。,三菱FX3U步进电机换算FB块:程序模块化与高稳定性实现
光伏逆变器TMS320F28335设计方案:Boost升压与单相全桥逆变,PWM与SPWM控制,MPPT恒压跟踪法实现,基于TMS320F28335DSP的光伏逆变器设计方案:Boost升压与单相全桥逆变电路实现及MPPT技术解析,光伏逆变器设计方案TMS320F28335-176资料 PCB 原理图 源代码 1. 本设计DC-DC采用Boost升压,DCAC采用单相全桥逆变电路结构。 2. 以TI公司的浮点数字信号控制器TMS320F28335DSP为控制电路核心,采用规则采样法和DSP片内ePWM模块功能实现PWM和SPWM波。 3. PV最大功率点跟踪(MPPT)采用了恒压跟踪法(CVT法)来实现,并用软件锁相环进行系统的同频、同相控制,控制灵活简单。 4.资料包含: 原理图,PCB(Protel或者AD打开),源程序代码(CCS打开),BOM清单,参考资料 ,核心关键词:TMS320F28335-176; 光伏逆变器; 升压; 逆变电路; 数字信号控制器; 规则采样法; ePWM模块; PWM; SPWM波; MPPT; 恒压跟踪法; 原理图; PCB; 源程序代码; BOM
centos9内核安装包
昆仑通态触摸屏与两台台达VFD-M变频器通讯实现:频率设定、启停控制与状态指示功能接线及设置说明,昆仑通态TPC7062KD触摸屏与两台台达VFD-M变频器通讯程序:实现频率设定、启停控制与状态指示,昆仑通态MCGS与2台台达VFD-M变频器通讯程序实现昆仑通态触摸屏与2台台达VFD-M变频器通讯,程序稳定可靠 器件:昆仑通态TPC7062KD触摸屏,2台台达VFD-M变频器,附送接线说明和设置说明 功能:实现频率设定,启停控制,实际频率读取等,状态指示 ,昆仑通态MCGS; 台达VFD-M变频器; 通讯程序; 稳定可靠; 频率设定; 启停控制; 实际频率读取; 状态指示; 接线说明; 设置说明,昆仑通态MCGS与台达VFD-M变频器通讯程序:稳定可靠,双机控制全实现
研控步进电机驱动器方案验证通过,核心技术成熟可生产,咨询优惠价格!硬件原理图与PCB源代码全包括。,研控步进电机驱动器方案验证通过,核心技术掌握,生产准备,咨询实际价格,包含硬件原理图及PCB源代码。,研控步进电机驱动器方案 验证可用,可以生产,欢迎咨询实际价格,快速掌握核心技术。 包括硬件原理图 PCB源代码 ,研控步进电机驱动器方案; 验证可用; 可生产; 核心技术; 硬件原理图; PCB源代码,研控步进电机驱动器方案验证通过,现可生产供应,快速掌握核心技术,附硬件原理图及PCB源代码。
高质量的OPCClient_UA源码分享:基于C#的OPC客户端开发源码集(测试稳定、多行业应用实例、VS编辑器支持),高质量OPC客户端源码解析:OPCClient_UA C#开发,适用于VS2019及多行业现场应用源码分享,OPCClient_UA源码OPC客户端源码(c#开发) 另外有opcserver,opcclient的da,ua版本的见其他链接。 本项目为VS2019开发,可用VS其他版本的编辑器打开项目。 已应用到多个行业的几百个应用现场,长时间运行稳定,可靠。 本项目中提供测试OPCClient的软件开发源码,有详细的注释,二次开发清晰明了。 ,OPCClient_UA; OPC客户端源码; C#开发; VS2019项目; 稳定可靠; 详细注释; 二次开发,OPC客户端源码:稳定可靠的C#开发实现,含详细注释支持二次开发
毕业设计