介绍finalize()方法以及怎么进行垃圾回收

  • A+
所属分类:编程茶楼


共计 2446 个字符,预计需要花费 7 分钟才能阅读完成。

《Thinking in Java》第五章中5.5小节“清理:终结处理和垃圾回收”,开发中总离不开new对象,常常会忘记对象的清理工作也同样重要。

finalize()
当然Java有自己的垃圾回收器,它会处理无用对象占据的内存资源,不过,同样也会存在特殊情况:对象不是被new出来的,因为垃圾回收器只知道释放通过new开辟的内存;

针对这块特殊的内存空间,Java允许在类中定义finalize()方法:一旦垃圾回收器准备好释放对象占用的内存时,就首先调用其finalize()方法,并且在下次垃圾回收动作发生时,才会真正清理出占用的内存。

finalize()是基类Object的方法,因此如果遇到特殊场景需要手动释放内存时,需实现该方法。

/**
* @author plm
* @create 2021/2/16 22:24
*/
public class Demo {
private int num;

private String name;

@Override
protected void finalize() throws Throwable {
super.finalize();
}
}
书中提及三点注意:

对象可能不被垃圾及回收;
垃圾回收并不等于“析构”;— 此处‘析构’是指:某些C++程序员会将finalize()当做C++中的析构函数(C++中销毁对象必须用到这个函数)
垃圾回收至于内存有关。 — 也许大多数人会发现,只要程序存储空间没有濒临用完的那一刻,对象占用的内存就总也得不到释放
记住,无论是“垃圾回收”还是“终结”,都不保证一定会发生。如果Java虚拟机(JVM)并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收释放内存的。

特殊用法finalize()
/**
* @author plm
* @create 2021/2/16 22:40
*/
public class Tank {
private String status; // 状态可以是empty 或者 full

public Tank() {
}

public Tank(String status) {
this.status = status;
}

void checkIn() {
this.status = “empty”;
}

@Override
protected void finalize() throws Throwable {
if (“full”.equals(status)) {
System.out.println(“Error: Tank is full.”);
super.finalize();
}
}
}

class Test {
public static void main(String[] args) {
Tank t = new Tank(“full”);
t.checkIn(); // 正常流程

new Tank(“full”); // 缺少了一步,就会被finalize方法检测出来

System.gc(); // 强制垃圾回收器清理对象释放内存
}
}

/*
Error: Tank is full.

*/
通过重复的执行程序,最终都可找出错误的Task对象。

垃圾回收器
引用计数
定义:一种简单但是速度很慢的垃圾回收:每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1;当引用离开作用域或者被置为null时,引用计数减1。引用计数为0的那个对象会被清理释放内存空间
缺陷:如果对象之间存在循环引用,就可能出现“对象应当被回收,但是引用计数却不为0”的情况,对于垃圾回收器而言,定位这样的对象交互引用的对象组而言,所需的工作量极大。

好在该技术可做了解,并未应用于Java虚拟机中。

自适应
停止-复制(一种工作模式):此做法先暂停程序的运行(所以他不属于后台回收模式),然后将所有活的对象从当前堆复制到另一个堆,没有被复制的都是垃圾需要被回收。当对象被复制到新堆时,是保持紧凑排列的,这样每个对象就会有新的内存地址值;因此那些对象引用都必须修正到新地址。

缺点:这个所谓“复制式回收器”效率会降低:第一,先要保证有两个堆,然后在这两个分离的堆之间来回倒塌,这样会需要多一倍的空间,某些Java虚拟机就是按需从堆中分配几块较大的内存,复制动作就发生在这些大块内存之间;第二,从复制这个动作来看,有些时候程序稳定运行并不会产生很多垃圾,可是复制的动作依旧存在,这样就会很浪费。

标记-清扫(另一种工作模式):从堆栈和静态存储区开始,遍历所有引用,找出所有存活的对象,找到一个就标记一个,当所有标记工作全部完成的时候,清扫动作才会开始;清理过程中,那些没有被标记的对象就会被释放,不存在任何复制的动作。

缺点:剩下的堆空间是不连贯的,如果想要得到连续空间的话,就得重新整理剩下的对象了。

垃圾回收动作发生时,程序将会被暂停,包含以上两种(在早期Sun版本中)

代数(前面停止-复制中提到分配几块较大的“块”内存):通过相应的代数来记录它是否还存活,通常当某处被引用时,其代数会增加,垃圾回收期会将上次回收动作之后新分配的块进行整理,这样对处理大量的短命的临时对象很有帮助。

垃圾回收器会定期进行完整的清理————大型对象不会被复制(只是其代数会增加),内含小型对象的那些快会被复制并整理。Java虚拟机会进行监视:如果所有对象都稳定,垃圾回收器效率降低的话,就切换到“标记-清扫”方式;同样,Java虚拟机会跟踪:“标记-清扫”的效果,出现很多垃圾碎片,就切换回“停止-复制”方式。这就是“自适应”技术。

另外:Java虚拟机中还有“即时”编译器(Just-In-Time, JIT)的附加技术用以提升速度,(原本属于Java虚拟机的工作)把程序全部或部分翻译成本地机器码,运行速度得以提升。

即时编译器编译所有代码。此方式会花费更多的时间,会增加可执行代码的长度,拖慢程序运行速度。
惰性评估:即时编译器只在必要的时候才编译代码。(新版JDK中的Java HotSpot技术就采用类似方法,代码每次被执行的时候都会做一些优化,执行代码次数越多,速度就会越快)从不会执行的代码可能压根就不会被JIT编译

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: