Java:GC机制

介绍

  • 在 C/C++中,编程者需要同时负责对象的创建与销毁。通常情况下编程者会忘记销毁无用的对象。由于这种忽视,在某个时间点可能会没有足够的内存空间去创建新的对象从而导致整个程序因为内存溢出而终止。
  • 在Java中,编程者不需要关心哪个对象不会再被用上。垃圾收集器会销毁这些对象。
  • 垃圾收集器是守护线程的最好例子因为它总是在后台运行。
  • 垃圾收集器的主要目的是通过摧毁不可到达的对象从而释放内存堆。

    重要术语

  1. 不可到达的对象(Unreachable objects): 如果一个对象没有任何指向它的索引那么我们称之为不可到达的对象。同样注意到属于隔离岛一部分的对象也是不可到达的。
    1
    2
    3
    4
    Integer i = new Integer(4);
    // 通过i中的索引,这个新的整型对象是可到达的
    i = null;
    // 那个整型对象不可到达了

img

  1. 垃圾收集的合格性(Eligibility for garbage collection):如果一个对象是不可到达的,那么就可以被垃圾收集。在上图中,在i = null;之后堆空间中的整型对象4就可以被垃圾收集了。

使对象可以被垃圾收集的方法

  • 即使编程者不需要负责摧毁无用的对象,但还是推荐把不再需要的对象变得不可到达。
  • 通常有四种方法让对象变得可以被垃圾收集。
    1. 让引用变量为空
    2. 重新分配引用变量
    3. 在方法内部创建对象
    4. 隔离岛

请求JVM运行垃圾收集器的方法

  • 一旦我们让对象可以被垃圾收集,它可能不会立即被垃圾收集器销毁。无论何时JVM运行垃圾收集程序,只有对象会被销毁。但是当JVM运行垃圾收集器,我们不能预计。
  • 我们也可以请求JVM运行垃圾收集器。有以下两种方法:
    1. 使用System.gc()方法: System类包含静态方法gc()来请求JVM运行垃圾收集器。
    2. 使用Runtime.getRuntime().gc()方法: Runtime类允许应用程序与运行应用程序的JVM进行交互。于是通过使用它的gc()方法,我们可以请求JVM运行垃圾收集器。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      // Java程序描述请求JVM去运行垃圾收集器
      public class Test
      {
      public static void main(String[] args) throws InterruptedException
      {
      Test t1 = new Test();
      Test t2 = new Test();

      //让引用变量为空
      t1 = null;

      //请求JVM运行垃圾收集器
      System.gc();

      //让引用变量为空
      t2 = null;

      //请求JVM运行垃圾收集器
      Runtime.getRuntime().gc();
      }

      @Override
      // finalize method is called on object once
      // before garbage collecting it
      protected void finalize() thorws Throwable
      {
      System.out.println("Garbage collector called");
      System.out.println("Object garbage collected:" + this);
      }
      }

注意:

  1. 无法保证以上两种方式中的任何一种绝对会运行垃圾处理器。
  2. 调用两种方法在效率上是等价的。

Finalization

  • 在销毁对象之前,垃圾收集器对对象调用finalize()方法来执行清理动作。一旦finalize()方法完成,垃圾收集器销毁对象。
  • fianlize()方法出现在Object类里,该方法的访问修饰符为protected,原型如下:
    1
    protected void finalize() throws Throwable

根据我们的需求,我们可以重写finalize()方法来执行我们的清理动作比如关闭数据库的连接。

注意:

  1. finalize()是由垃圾收集器调用的而不是JVM。虽然垃圾收集器是JVM的一个模型。
  2. Object类finalize()方法的执行为空,因此如果要部署系统资源或者执行其他清理的话需要重载这个方法。
  3. finalize()方法不会对一个对象调用多次。
  4. 如果finalize()方法抛出了一个不可捕捉的异常,异常会被忽略而且对象的终结会被终止。

垃圾收集器的使用

假设要写一个程序去计算公司中工作的员工数(除去实习生),你需要垃圾收集器来完成这个程序。
任务描述:
写一个程序创建一个叫做Employee的类包含以下的数据成员:

  1. ID,存储分配给每个员工的独一无二的id
  2. 员工的姓名
  3. 员工的年龄
    同时提供以下方法:
  4. 一个带参构造函数用来初始化姓名和年龄,ID需要在构造器中被初始化
  5. 一个show()方法去展示ID,姓名和年龄
  6. 一个showNextId()去展示每个员工的下一个ID
    作为一个没有垃圾收集器知识的初学者会这样写:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public class Employee {
    private int ID;
    private String name;
    private int age;
    private static int nextId=1;

    public Employee(String name, int age){
    this.name = name;
    this.age = age;
    this.ID = nextId++;
    }
    public void show(){
    System.out.println("Id="+ID+"\nName="+name+"\nAge="+age);
    }
    public void showNextId(){
    System.out.println("Next employee id of "+ID" will be="+nextId);
    }
    }

    public class test {
    public static void main(String[] args) {
    Employee E = new Employee("GFG1", 56);
    E.showNextId();
    Employee F = new Employee("GFG2", 45);
    F.showNextId();
    Employee G = new Employee("GFG3", 25);
    G.showNextId();
    {
    //用块来保存实习生
    Employee X = new Employee("GFG4", 23);
    X.showNextId();
    Employee Y = new Employee("GFG5", 21);
    Y.showNextId();
    }
    G.showNextId();
    }
    }

输出:

1
2
3
4
5
6
Next employee id of 1 will be=2
Next employee id of 2 will be=3
Next employee id of 3 will be=4
Next employee id of 4 will be=5
Next employee id of 5 will be=6
Next employee id of 3 will be=6

类的定义中nextId属性为静态,被所有对象共享。第一个G.showNextId()输出的是4,第二个G.showNextId()输出的是6。
如何获得获得正确的输出(即让第二个G.showNextId()输出4):
现在垃圾收集器会看到两个空闲的对象,现在去减少nextId的值,垃圾收集器只有在编程者在类中重载的情况下才会调用方法finalize()。正如之前提到的,我们需要向垃圾收集器发起请求,我们要在子块的中写下一下三个步骤:

  1. 将引用变量设为空
  2. 调用System.gc();
  3. 调用System.runFinalization();
    现在计算员工(除了实习生)的正确代码是:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    public class Employee {
    private int ID;
    private String name;
    private int age;
    private static int nextId=1;

    public Employee(String name, int age){
    this.name = name;
    this.age = age;
    this.ID = nextId++;
    }
    public void show(){
    System.out.println("Id="+ID+"\nName="+name+"\nAge="+age);
    }
    public void showNextId(){
    System.out.println("Next employee id of "+ID" will be="+nextId);
    }
    // 供gc调用
    protected void finalize(){
    --nextId;
    }
    }

    public class test {
    public static void main(String[] args) {
    Employee E = new Employee("GFG1", 56);
    E.showNextId();
    Employee F = new Employee("GFG2", 45);
    F.showNextId();
    Employee G = new Employee("GFG3", 25);
    G.showNextId();
    {
    //用块来保存实习生
    Employee X = new Employee("GFG4", 23);
    X.showNextId();
    Employee Y = new Employee("GFG5", 21);
    Y.showNextId();
    X = Y = null; // 设置为空
    System.gc(); //调用垃圾收集器
    System.runFinalization(); // 执行finalize
    }
    G.showNextId();
    }
    }

输出:

1
2
3
4
5
6
Next employee id of 1 will be=2
Next employee id of 2 will be=3
Next employee id of 3 will be=4
Next employee id of 4 will be=5
Next employee id of 5 will be=6
Next employee id of 3 will be=4

参考资料

GeeksforGeeks


Q. How do you force garbage collection to occur at a certain point?
A. Call System.forceGc()
B. Call System.gc()
C. Call System.requireGc()
D. None of the above

Answer: D
While you can suggest to the JVM that it might want to run a garbage collection cycle, the JVM is free to ignore your suggestion.