类的创建与销毁(二)

销毁实例时注意事项

内存泄露

出现内存泄露的3种常见情况及应对原则

  • 类中存在自我管理内存现象

类中存在容器(如数组)维持其他对象,凡是存在与该数组中的对象都会被强制持有,垃圾回收器不会对其进行回收,如果该容器中存在已过期对象时,这就导致了内存泄漏。我们应该强制将该容器的该元素置为空,让该过期对象失去引用,进入垃圾回收器的视野中

  • 缓存技术的使用

我们建立缓存机制的时候,要有合理的以时间、活跃度为排序原则的容器,并且设立缓存容器的大小,但达到回收条件时(大小/数量达到最大值),首先按时间、活跃度排序定义回收优先级。

  • 监听器或者回调的使用

因为业务时序的非正常进行导致监听/回调对象的未取消注册,此时被注册对象依然持有该listener,但注册者已销毁或者已不关注该回调。最优的做法就是保持该对象以弱引用的方式被使用,一旦其他显示引用不存在,该弱引用会进入垃圾回收器的视野中


终结方法(finalizer)

终结方法指的就是finalize()方法

  • 工作原理

当GC检测到对象已废弃后,会检测对象的finalize()方法是否被复写,未复写进行正常回收,否则将该对象加入一个队列,通过一个低优先级线程轮询执行每个对象的finalize()方法,执行过的对象被移除出该队列,GC会再次检测对象是否有效,无效执行回收,有效则“复活”

  • 不可预期

该方法不能保证能得到及时执行,甚至不能保证被执行,这会导致依赖该方法的资源回收操作变得不可预期(如文件操作的描述符回收),并且背压(销毁对象的速度赶不上待销毁对象加入垃圾回收器的速度)的存在,加剧了这一现象,由于终结方法的执行线程低优先级,可能你的终结方法一直得不到执行

  • 可移植性差

执行终结方法是GC的一个重要特性,但GC在不同的JVM上的规则是不一样的,所以依赖该方法的操作会导致程序在不同的JVM上表现可能大有不同,甚至导致了业务逻辑的不统一,这无疑增加了移植的成本

  • 终结方法抛出异常

通常情况下,未捕捉的异常会终止线程,并打出堆栈信息。但如果该异常发生在终结方法的执行过程中,连警告信息都不会打出来,很难进行debug

  • 性能损耗

《effective java》一书中作者举例,正常销毁一个对象耗时5.6ns,而增加了终结方法后,耗时增加到2400ns(未验证)

  • 显式定义的业务终止方法+ try-finally

通过在finally语句块中,调用自定义的显式的终止方法释放资源,这是最经济有效的方式,而不是直接通过终结方法释放资源

  • 终止方法+终结方法

终结方法可以做没有调用显式终止方法时的第二层防护(safety net),毕竟延迟释放总比不释放好。另外如果到达了第二层终结方法出才进行资源释放,证明程序存在内存泄漏的BUG,应及时修复

  • 本地对等体

将java世界的对象通过JNI赋值给native世界的对象,该native对象无法被GC回收,如果在没有显式的JNI方法能做这个回收动作的情况下,终结方法便是最好的选择。

  • 复写时要注意

复写父类的finalize()方法的时候,一定要在finally子句中调用super.finalize(),否则父类的终结方法不会执行。

总结: 除了二次防护用法和终止本地对等体对象用法,请不要使用终结方法(finalize()

你可能感兴趣的:(类的创建与销毁(二))