Item5:消除过期对象的引用

  JVM为我们实现了GC(垃圾回收)的功能,让我们从手工管理内存中解放了出来,这固然很好,但并不意味着我们就再也不需要去考虑内存管理的事情了;我们用简单的栈实现的例子来解释:

public class Stack {
private Object[] elements;
private in size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack(){
elements = new Object[DEFAULT_INITIAL_CAPACITY];
} public void push(Object e){
ensureCapacity();
elements[size++] = e;
} public Object pop(){
if(size == 0){
return new EmptyStackException();
}
return element[--size];
} //保证每当栈满就会自动扩容为原来的两倍
private void ensureCapacity(){
if(elements.length == size){
elements = Arrays.copyOf(elements, 2*size+1);
}
}
}

这段程序没有什么明显的错误,无论怎么测试,结果似乎都是正确的,但不严格的讲,这段程序存在"内存泄漏"的风险,极端情况下,会导致磁盘交换(Disk Paging),甚至是程序失败(OutOfMemoryError);

为什么是内存泄漏?书中给出的解释是:从栈中pop出来的对象的引用还被栈内部维护着,这些引用被称作"过期引用"(指下标小于size的那些元素的引用),所以,GC不会去处理该对象以及该对象所引用的其他对象,内存就被这样渐渐"填满"导致溢出.

修复方法,改写pop()

public Object pop(){
if(size == 0){
throw new EmptyStackException();
}
Object result = element[--size];
elements[size] = null;    //清空出栈对象的引用,告知GC可以释放其资源
return result;
}

手动将过期引用告知垃圾回收器还有的好处就是,如果之后又用了此过期引用,程序就会报空指针异常,而不是一直"错误下去"..

内存泄漏的另一常见来源是缓存,对象引用在缓存中就很容易被忘记,如果不用它会长时间驻留缓存.对应的几种解决方法:

  1.使用WeakHashMap代表缓存,该类拥有expungeStaleEntries方法,用于清除那些外部没有引用并且缓存内存在的键,来段代码说明:

public class Test {
public static void main(String[] args) throws Exception {
String a = new String("a");
String b = new String("b");
Map weakmap = new WeakHashMap();
Map map = new HashMap();
map.put(a, "aaa");
map.put(b, "bbb"); weakmap.put(a, "aaa");
weakmap.put(b, "bbb"); map.remove(a); a=null;
b=null; System.gc(); } }

  由于HashMap移除了a的引用,且清除了a的外部引用,此时a的引用只有WeakHashMap来维护了,此时WeakHashMap会自动舍弃掉a,但HashMap还保存着b的引用,所以b不会被WeakHashMap丢弃掉。在工程当中WeakHashMap是很实用的,我们使用短时间内就过期的缓存时最好使用weakHashMap。

  2.可以将清除过期引用的任务交给“Time”或“ScheduledThreadPoolExecutor”来完成

  即定时清除过期引用。

  

  3.使用LinkedHashMap当做缓存,调用removeEldesttEntry方法清除过期引用

  

  4.直接使用java.lang.ref

  说道ref包,就绕不开Java的四类引用:强引用,软引用,弱引用,虚引用。这里大致概括一下,具体引用就不过多赘述,细节可以参照深入探讨java.lang.ref包

    强引用:只要引用存在,垃圾回收器永远不会回收,Object obj = new Object()的形式

    软引用:非必须引用,内存溢出之前进行回收,可以通过以下代码实现,SoftReference<Object> sf = new SoftReference<Object>(obj)的形式,通过sf.get()获取对象,软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。

    弱引用:第二次垃圾回收时回收,WeakReference<Object> wf = new WeakReference<Object>(obj)的形式,弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。

    虚引用:垃圾回收时回收,无法通过引用取到对象值,PhantomReference<Object> pf = new PhantomReference<Object>(obj)的形式,pf.isEnQueued()主要用于检测对象是否已经从内存中删除。

  

关于内存泄漏的检测,往往不会表现的很明显,但它能在系统中存在很多年,只有通过检查代码或借助于Heap剖析工具(Heap Profiler)发现问题,关于Heap Profiler我有机会在单独介绍。

Item6:避免使用终结方法(finalizer)

下面由一段代码引入:

/**
* @author YHW
* @ClassName: Test1
* @Description:
* @date 2019/1/4 20:55
*/
public class Test1 { public static void main(String[] args){
Bob bob = new Bob();
System.out.println(bob.getState());
Thread thread = new Thread(){
@Override
public void run() {
super.run();
try{
sleep(2000);
}catch(Exception e){
e.printStackTrace();
}finally{
bob.setClosed(true);
} }
};
thread.start();
System.out.println(bob.getState());
} }

运行结果截图:

在main方法中开了一个子线程,我故意睡了2秒,故根本走不到终结方法那里,主程序就”关闭“了,这时候子线程还没执行完,这有可能因此而产生终结对象的速度达不到对象入队的速度,出现内存泄漏而死掉,所以我们不应该以来终结方法来更新重要的持久状态,而且并没有很轻便的方法能够避免这样的问题;

由此引出了终结方法所具备的特性:

  1.finalizer方法的线程优先级比当前程序的其他线程优先级要低,且JAVA语言规范不保证任何线程中finalizer方法的执行;

  2.及时执行终结方法是JVM的一大功能,但在不同的JVM实现都大相径庭,有时候终结方法的线程优先级会非常低,造成JVM“没时间”释放不用的资源,引起OutOfMemoryError;

  3.唯一声称保证终结方法执行的System.runFinalizersOnExit和Runtime.runFinalizersOnExit都存在致命缺陷,已经废弃了;

  4.finalizer会有非常严重的性能损失;

最新文章

  1. 如何在ASP.NET Core中实现CORS跨域
  2. ORACLE 空表不能导出问题解决
  3. VC Windows API获得桌面所有窗口句柄的方法
  4. 回车和换行在linux下和windows下
  5. JavaPersistenceWithHibernate第二版笔记-第四章-Mapping persistent classes-003映射实体时的可选操作(&lt;delimited-identifiers/&gt;、PhysicalNamingStrategy、PhysicalNamingStrategyStandardImpl、、、)
  6. 用supervisor控制celery时的脚本
  7. phpmyadmin导出数据库为什么是php文件
  8. 浅析多线程 对象锁和Class锁
  9. Android Parcelable理解与使用(对象序列化)
  10. Numpy1
  11. Jenkins自动化部署war项目
  12. 设计Web程序,计算任意两个整数的和,并在网页上显示结果。要求在javabean中实现数据的求和功能。
  13. three.js raycaster射线碰撞的坑 (当canvas大小 不是屏幕大小是解决拾取物体的办法)
  14. 接口与virtual,override,new关键字
  15. 两个大数组foreach,找出相同的key数量,所用的时间对比
  16. 关于PHP写的投票网站之刷票终结版
  17. 小马哥课堂-统计学-t分布
  18. 严谨的程序案例Api
  19. 我的ngnix 配置内容
  20. python定时任务:apscheduler的使用(还有一个celery~)

热门文章

  1. 1068 Bash游戏 V3
  2. JavaScript-Tool:three.js
  3. Entity Framework之领域驱动设计实践
  4. [pe531]Chinese leftovers
  5. c++控制台 设置字体颜色
  6. 使用VSTO写的一个工作证打印软件
  7. 交互原型设计软件axure rp学习之路(一)
  8. matlab新手入门(二)(翻译)
  9. 盛大游戏技术总监徐峥:Unity引擎使用的三种方式
  10. springboot 之 controller