View on GitHub

我的学习笔记

好记性不如烂笔头

5.5 清理:终结处理和垃圾回收

导航

返回

  1. 5.5.1 finalize()用途何在
  2. 5.5.2 你必须实施清理
  3. 5.5.3 终结条件
  4. 5.5.4 垃圾回收器如何工作

5.5.1 finalize()用途何在

5.5.2 你必须实施清理

5.5.3 终结条件

//: initialization/TerminationCondition.java
// Using finalize() to detect an object that
// hasn't been properly clean up
class Book{
    boolean checkOut=false;
    Book(boolean checkOut){ this.checkOut=checkOut;}
    void checkIn(){ this.checkOut=false;}

    protected void finalize(){
        if(checkOut)  System.out.println("Error:Check Out");
        // Normally,you'll also do this:
        // super.finalize();  //Call the base-class version
    }
}

public class TerminationCondition{
    public static void main(String[] args) {
        Book novel=new Book(true);
        //Proper cleanup
        novel.checkIn();
        //Drop the reference, forget to clean up
        new Book(true);
        //Fource garbage coolecion & finalization
        System.gc();
    }
}/*Output:
Error:Check Out
 *///:~

5.5.4 垃圾回收器如何工作

1. 引用计数方法

  1. 实现方式: 每个对象都含有一个引用计数器.当有引用接至对象时,引用计数加1.当引用离开 作用域或被置为null,引用计数减1.
  2. 问题: 如果对象之间存在循环引用,可能会出现 对象应该被回收,但引用计数却不为零 的情况.对于垃圾回收器而言,定位这样的交互自引用的对象组所需的工作量极大.
  3. 使用场景: 常用来说明垃圾收集的工作方式,但并未应用于任何一种JVM实现中.
  4. 特点: 简单,但速度很慢

2. 更快速的垃圾回收技术

  1. 思想: 对任何”活的”对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用.这个引用链条可能会穿过数个对象层次.
  2. 实现:
    1. step 1: 从堆栈和静态存储区开始,遍历所有引用,就能找到所有”活”的对象.
    2. step 2: 对于发现的每个引用,必须追踪它所引用的对象,然后是这个对象所包含的所有引用,如此反复进行,直到根源于堆栈和静态存储区的引用所形成的网络全部被访问为止.访问过的对象必须都是”活”的
    3. 注意: 这解决了交互自引用的对象组的问题

3. 停止-复制(stop-and-copy)

  1. 一种 自适应 的垃圾回收技术
  2. 思想:
    1. 先暂停程序运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的都是垃圾
    2. 当对象被复制到新堆是,他们是一个挨着一个的,所以新堆保持紧凑排列,然后可以前述方法简单直接地分配内存空间了
  3. 注意点:
    1. 当把对象从一处搬到另一处时,所有指向它的那些引用都必须修正
    2. 位于堆或静态存储区的引用可以直接被修正,但是可能还有其他指向这些对象的引用,它们在遍历的过程中才能被找到
  4. 这种所谓的复制式回收器效率会降低
    1. 首先,维护比实际需要多一倍的空间.某些JVM对此问题的处理方式是:按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间
    2. 第二个问题在于复制.程序稳定后,可能只产生很少甚至没有垃圾,尽管如此,复制式回收器仍然会将所有内存自一处副知道另一处,这很浪费.

4. 标记-清扫(mark-and-swap)模式

  1. JVM进行检查,如果没有新垃圾产生,就会转换到另一种工作模式
  2. 思路:从堆栈和静态存储区出发,遍历所有引用,进而找出所有存活对象.
  3. 实现:
    1. 每当找到一个存活对象,就会给对象设一个标记,这个过程中不会回收任何对象.只有在全部标记工作完成的时候,清理动作才会开始.
    2. 在清理过程中,没有标记的对象将被释放,不会发生任何复制动作.
    3. 剩下的空间是不连续的

5. 停止-复制

  1. 这种垃圾回收动作不是在后台进行的;相反,垃圾回收动作发生的同时,程序将会被暂停
  2. 要求在释放旧有对象之前,必须先把所有存活对象从旧堆复制到新堆,这就导致大量内存复制行为

6.

  1. JVM中内存分配以较大的“块”为单位
  2. 如果对象较大,就会占用单独的块
  3. 有了块,垃圾回收器在回收的时候就可以往废弃的块里拷贝对象了
  4. 每个块都用对应的代数(generation count)来记录是否还存活.通常
  5. 通常,如果块在某处被引用,其代数会增加;垃圾回收器将会对上次回收动作之后新分配的块进行整理.这对处理大量短命临时对象很有帮助
  6. 垃圾回收器会定期进行完整的清理动作–大型对象仍然不会被复制,内含小型对象的那些块则会被复制并整理

7. 自适应技术

  1. JVM会进行监视.如果所有对象很稳定,垃圾回收器的效率降低的话,就切换到标记-清扫模式
  2. JVM会跟踪“标记-清扫”模式的效果,要是堆空间多处很多碎片,就会切换回停止-复制模式
  3. 即时(Just-In-Time,JIT)
    1. 这种技术可以吧程序全部或部分翻译成本地机器码,程序运行速度因此得到提升
    2. 当需要装载某个类时,编译器会先找到其.class文件,然后将该类的字节码装入内存
      1. 让即使编译器编译所有代码
        1. 缺陷一: 执行时间增长 这种加载动作散落在整个程序生命周期内,累加起来要花更多的时间
        2. 缺陷二: 执行速度降低 增加可执行代码的长度,浙江导致页面调度,从而导致降低程序速度
      2. 惰性评估(lazy evaluation): 即时编译器只在必要的时候才编译代码

返回