main()方法在java中以不同方式处理垃圾收集

时间:2017-02-11 18:23:26

标签: java static garbage-collection main finalizer

以下是我的问题之前的代码。首先是一个界面:

public interface SomeAction {
    public void doAction();
}

然后有两个类:

public class SomeSubscriber {
    public static int Count;

    public SomeSubscriber(SomePublisher publisher) {
        publisher.subscribe(this);
    }

    public SomeAction getAction() {
        final SomeSubscriber me = this;
        class Action implements SomeAction {

            @Override
            public void doAction() {
               me.doSomething();
            }
        }

        return new Action();
    }

    // specify what to do just before it is garbage collected
    @Override
    protected void finalize() throws Throwable {
        SomeSubscriber.Count++;
        System.out.format("SomeSubscriber count: %s %n",  someSubscriber.Count);
    }

    private void doSomething() {
        // TODO: something
    }
}

第二节课:

public class SomePublisher {
    private List<SomeAction> actions = new ArrayList<SomeAction>();

    public void subscribe(SomeSubscriber subscriber) {
        actions.add(subscriber.getAction());
    }
}

这是用于测试两个类的代码:

public class Test {
    //output: "the answer is: 0" for the 1st run after compilation and running attemptCleanUp() first, stays 0 upon repeat run
    public static void main (String args []) {
        System.out. println("am in main()");
        SomePublisher publisher = new SomePublisher();
        for (int i = 0; i < 10; i++) {
            SomeSubscriber subscriber = new SomeSubscriber(publisher);
            subscriber = null;
        }
        attemptCleanUp();
   }

   //output: "the answer is: 0" for the 1st run after compilation and running attemptCleanUp() first, rising to 10, 20, 30 ...upon repeat run
    public static void answerIsNot0() {
        System.out. println("am in answerIsNot0()");
        SomePublisher publisher = new SomePublisher();
        for (int i = 0; i < 10; i++) {
            SomeSubscriber subscriber = new SomeSubscriber(publisher);
            subscriber = null;
        }
        attemptCleanUp();
   }

   private static void attemptCleanUp() {
        threadMessage("Before the gc attempt, the answer is: " + SomeSubscriber.Count);
        System.gc();
        System.runFinalization();
        threadMessage("After the gc attempt, the answer is: " + SomeSubscriber.Count);
   }

   private static void threadMessage(String message) {
        String threadName =
            Thread.currentThread().getName();
        System.out.format("%s: %s%n",
                          threadName,
                          message);
    }
}

main()的打印输出显示SomeSubscriber.Count值为1到10,而最后一行产生The answer is: 0,如下所示:

am in main()
main: Before the gc attempt, the answer is: 0
SomeSubscriber count: 1 
SomeSubscriber count: 2 
SomeSubscriber count: 3 
SomeSubscriber count: 4 
SomeSubscriber count: 5 
SomeSubscriber count: 6 
SomeSubscriber count: 7 
SomeSubscriber count: 8 
SomeSubscriber count: 9 
SomeSubscriber count: 10 
main: After the gc attempt, the answer is: 0

而对于answerIsNot0(),The answer is: <num>内的数字始终与SomeSubscriber count:系列中的最后一个数字匹配。

我的问题是:首先,非零值是否显示垃圾收集确实发生了10次?这与10 subscriber s仍然被Action实例中的本地类publisher的实例引用的概念相矛盾,因此不会进行垃圾收集。其次,SomeSubscriber.Count的值如何在main(String args []){}方法的最后语句中更改,而不是在answerIsNot0()方法中更改?换句话说,为什么相同的代码在放置在main()中时会对SomeSubscriber.Count产生不同的效果,而不是放在answerIsNot0()中?

2 个答案:

答案 0 :(得分:2)

首先,垃圾收集终结之间存在显着差异。两者都可能具有依赖于实现的行为,这是有意未指定的,但至少可以保证虚拟机在抛出OutOfMemoryError之前将执行垃圾收集以尝试回收内存。

另一方面,终结者不能保证完全跑步。从技术上讲,finalization只能在之后运行垃圾收集器确定对象无法访问并将它们排入队列。

这意味着finalize()方法不适合告诉您在正常情况下对象是否会被垃圾收集,即如果类没有自定义finalize()方法。

尽管如此,你的测试似乎已经触及,这引发了可达性问题

JLS, §12.6.1. Implementing Finalization
  

... 可达对象是任何可以从任何活动线程继续计算中访问的对象。

很明显,如果没有变量持有对象的引用,则“潜在的持续计算”不能访问它。这是检查这个的最简单方法。尽管如此,在您的示例中,没有潜在的持续计算可以访问publisher对象,因为没有代码执行对变量的任何访问。这很难检测,因此在JVM无论如何都要优化代码之前都不会发生这种情况。 §12.6.1明确说明:

  

可以设计优化程序的转换,以减少可达到的对象数量,使其少于可以被认为可达的对象数量。例如,Java编译器或代码生成器可以选择设置一个不再用于null的变量或参数,以使这种对象的存储可以更快地回收。

另请参阅“Can java finalize an object when it is still in scope?

这似乎是你的问题。在没有得到最大优化的短期运行程序中,局部变量引用的一些未使用的对象可能无法立即回收,而在多次运行后更深入优化时,它们可能会使用相同的代码在早期回收。它是main方法还是其他方法并不重要,它只是重要的,它被调用的频率或它运行的时间(被认为是一个热点),或者更确切地说,它将在多大程度上被调用在JVM的生命周期内得到优化。

您的代码的另一个问题与以下内容有关:

JLS, §12.6. Finalization of Class Instances
  

Java编程语言没有指定哪个线程将为任何给定对象调用终结器。

     

重要的是要注意许多终结器线程可能是活动的(有时在大型共享内存多处理器上需要),并且如果大型连接数据结构变为垃圾,则每个对象的所有最终化方法都是可以同时调用该数据结构,每个终结器调用在不同的线程中运行。

     

Java编程语言对finalize方法调用没有任何排序。终结者可以按任何顺序调用,甚至可以同时调用。

     

例如,如果循环链接的未终结对象组变得无法访问(或终结器可访问),则所有对象可能会一起最终确定。最终,可以按任何顺序调用这些对象的终结器,甚至可以使用多个线程同时调用这些对象的终结器。如果自动存储管理器稍后发现对象无法访问,则可以回收它们的存储。

由于您没有采取任何措施来确保线程安全访问变量SomeSubscriber.Count,因此可能会出现许多不一致之处。即使在终结器线程中已经更改了主线程中的零,也只是其中之一。你很幸运,你已经看到从1到10的升序数字,显然你的JRE中只有一个终结器线程。由于缺乏线程安全性,你可以看到任意顺序的数字,但也有一些数字出现多次而其他数字丢失,在完成十个对象之后不一定到达十。

答案 1 :(得分:1)

本地班级Action会保留对SomeSubscriber的引用(因此您可以在其上调用doSomething()Action的实例可通过SomePublisher访问。因此,在SomeSubscriber方法结束时仍可以访问main的实例。

这使他们没有资格进行垃圾收集。所以他们没有收集。

对于两个不同的结果,我假设您运行了两种方法。您获得的answer 10是从第一个版本中收集的10个实例。 (一旦方法结束SomePublisher超出范围并且可以收集,所有引用ActionSomeSubscriber

另外System.gc();只是提示应该运行垃圾收集,不能保证在方法运行后收集所有内容。