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会有非常严重的性能损失;

提升Java代码质量(二)的更多相关文章

  1. 提升Java代码质量(三)

    Item7:覆盖equals时需要遵守通用约定 在我们日常开发过程中,重写equals是比较常用的,但存在许多不合适的覆盖方式导致错误,最好的避免方法就是不去重写equals.但有时我们的业务又需要建 ...

  2. 提升Java代码质量(一)

    博主双12入手了一本"Effective Java第二版",本系列文章将初步梳理书中内容,我也查了些资料,我会针对知识点做一点展开,方便以后复习回顾; Item1.考虑用静态工厂代 ...

  3. 拔高你的Java代码质量吧:推荐使用枚举定义常量(转)

    提高你的Java代码质量吧:推荐使用枚举定义常量 一.分析 常量的声明是每一个项目中不可或缺的,在Java1.5之前,我们只有两种方式的声明:类常量和接口常量.不过,在1.5版之后有了改进,即新增了一 ...

  4. 提高Java代码质量的Eclipse插件之Checkstyle的使用详解

    提高Java代码质量的Eclipse插件之Checkstyle的使用详解 CheckStyle是SourceForge下的一个项目,提供了一个帮助JAVA开发人员遵守某些编码规范的工具.它能够自动化代 ...

  5. java代码解析二维码

    java代码解析二维码一般步骤 本文采用的是google的zxing技术进行解析二维码技术,解析二维码的一般步骤如下: 一.下载zxing-core的jar包: 二.创建一个BufferedImage ...

  6. 提高Java代码质量的Eclipse插件之Checkstyle的使用具体解释

    CheckStyle是SourceForge下的一个项目,提供了一个帮助JAVA开发者遵守某些编码规范的工具.它可以自己主动化代码规范检查过程.从而使得开发者从这项重要可是枯燥的任务中解脱出来. Ch ...

  7. java 性能优化:35 个小细节,让你提升 java 代码的运行效率

    前言 代码 优化 ,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没 ...

  8. JAVA性能优化:35个小细节让你提升java代码的运行效率

    代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是, ...

  9. Java代码质量度量工具大阅兵

    FindBugs FindBugs, a program which uses static analysis to look for bugs in Java code. It is free so ...

随机推荐

  1. 一般项目转为Maven项目所遇到的问题

    最近搞CI,准备使用Maven,但以前的项目不是Maven项目,需要把项目转换为Maven项目.这遇到几个小问题,一是jar包的依赖,二是从本地仓库取出依赖jar包. 由于没有本地仓库,要手动添加ja ...

  2. JS---计算两个日期之间相差多少天

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  3. 《SpringBoot揭秘 快速构建微服务体系》读后感(二)

    最简单的springBoot应用 package com.louis.test; import org.springframework.boot.SpringApplication; import o ...

  4. 3、scala数组

    一.Array .Array Buffer 1.Array 在Scala中,Array代表的含义与Java中类似,也是长度不可改变的数组. 此外,由于Scala与Java都是运行在JVM中,双方可以互 ...

  5. Spring入门第七课

    Bean之间的关系:继承和依赖. 继承Bean配置 Spring允许继承bean的配置,被继承的bean称为父bean,继承这个父bean的Bean称为子Bean. 子Bean从父Bean中继承配置, ...

  6. js中“||”和“&&”的高级用法

    例1:用于赋值&&:从左往右依次判断,当当前值为true则继续,为false则返回此值(是返回未转换为布尔值时的原值哦)|| : 从左往右依次判断,当当前值为false则继续,为tru ...

  7. 获得HttpWebResponse请求的详细错误内容

    try { } catch (WebException ex) { HttpWebResponse response = (HttpWebResponse)ex.Response; Console.W ...

  8. Unicode编码下字符串转换

    VC\MFC当中CString.string.char.char*.char数组.int等类型之间的转换令人晕头转向,特地搜集多篇文章资料,利用代码实例等清晰的理清他们之间的关系和如何转换,其实非常简 ...

  9. Ext.apply(src,apply) 和 Ext.applyIf(src,apply)比较(转)

    Ext.onReady(function(){ /* * Ext.apply(src,apply) 和 Ext.applyIf(src,apply) 两个方法的使用和区别比较 */ //Ext.app ...

  10. css清除浮动的方法总结

    在各种浏览器中显示效果也有可能不相同,这样让清除浮动更难了,下面总结8种清除浮动的方法,测试已通过 ie chrome firefox opera,需要的朋友可以参考下     清除浮动是每一个 we ...