以下是我的问题之前的代码。首先是一个界面:
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()中?
答案 0 :(得分:2)
首先,垃圾收集和终结之间存在显着差异。两者都可能具有依赖于实现的行为,这是有意未指定的,但至少可以保证虚拟机在抛出OutOfMemoryError
之前将执行垃圾收集以尝试回收内存。
这意味着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 InstancesJava编程语言没有指定哪个线程将为任何给定对象调用终结器。
重要的是要注意许多终结器线程可能是活动的(有时在大型共享内存多处理器上需要),并且如果大型连接数据结构变为垃圾,则每个对象的所有最终化方法都是可以同时调用该数据结构,每个终结器调用在不同的线程中运行。
Java编程语言对finalize方法调用没有任何排序。终结者可以按任何顺序调用,甚至可以同时调用。
例如,如果循环链接的未终结对象组变得无法访问(或终结器可访问),则所有对象可能会一起最终确定。最终,可以按任何顺序调用这些对象的终结器,甚至可以使用多个线程同时调用这些对象的终结器。如果自动存储管理器稍后发现对象无法访问,则可以回收它们的存储。
由于您没有采取任何措施来确保线程安全访问变量SomeSubscriber.Count
,因此可能会出现许多不一致之处。即使在终结器线程中已经更改了主线程中的零,也只是其中之一。你很幸运,你已经看到从1到10的升序数字,显然你的JRE中只有一个终结器线程。由于缺乏线程安全性,你可以看到任意顺序的数字,但也有一些数字出现多次而其他数字丢失,在完成十个对象之后不一定到达十。
答案 1 :(得分:1)
本地班级Action
会保留对SomeSubscriber
的引用(因此您可以在其上调用doSomething()
。Action
的实例可通过SomePublisher
访问。因此,在SomeSubscriber
方法结束时仍可以访问main
的实例。
这使他们没有资格进行垃圾收集。所以他们没有收集。
对于两个不同的结果,我假设您运行了两种方法。您获得的answer 10
是从第一个版本中收集的10个实例。 (一旦方法结束SomePublisher
超出范围并且可以收集,所有引用Action
和SomeSubscriber
)
另外System.gc();
只是提示应该运行垃圾收集,不能保证在方法运行后收集所有内容。