Pregunta

I'm slowly working through Bruce Eckel's Thinking in Java 4th edition, and the following problem has me stumped:

Create a class with a finalize( ) method that prints a message. In main( ), create an object of your class. Modify the previous exercise so that your finalize( ) will always be called.

This is what I have coded:

public class Horse {
    boolean inStable;
    Horse(boolean in){
        inStable = in;
    }   
    public void finalize(){
        if (!inStable) System.out.print("Error: A horse is out of its stable!");
    }
}
public class MainWindow {
    public static void main(String[] args) {
        Horse h = new Horse(false);
        h = new Horse(true);
        System.gc();
    }
}

It creates a new Horse object with the boolean inStable set to false. Now, in the finalize() method, it checks to see if inStable is false. If it is, it prints a message.

Unfortunately, no message is printed. Since the condition evaluates to true, my guess is that finalize() is not being called in the first place. I have run the program numerous times, and have seen the error message print only a couple of times. I was under the impression that when System.gc() is called, the garbage collector will collect any objects that aren't referenced.

Googling a correct answer gave me this link, which gives much more detailed, complicated code. It uses methods I haven't seen before, such as System.runFinalization(), Runtime.getRuntime(), and System.runFinalizersOnExit().

Is anybody able to give me a better understanding of how finalize() works and how to force it to run, or walk me through what is being done in the solution code?

¿Fue útil?

Solución

When the garbage collector finds an object that is eligible for collection but has a finalizer it does not deallocate it immediately. The garbage collector tries to complete as quickly as possible, so it just adds the object to a list of objects with pending finalizers. The finalizer is called later on a separate thread.

You can tell the system to try to run pending finalizers immediately by calling the method System.runFinalization after a garbage collection.

But if you want to force the finalizer to run, you have to call it yourself. The garbage collector does not guarantee that any objects will be collected or that the finalizers will be called. It only makes a "best effort". However it is rare that you would ever need to force a finalizer to run in real code.

Otros consejos

Outside of toy scenarios, it's generally not possible to ensure that a finalize will always be called on objects to which no "meaningful" references exist, because the garbage collector has no way of knowing which references are "meaningful". For example, an ArrayList-like object might have a "clear" method which sets its count to zero, and makes all elements within the backing array eligible to be overwritten by future Add calls, but doesn't actually clear the elements in that backing array. If the object has an array of size 50, and its Count is 23, then there may be no execution path by which code could ever examine the references stored in the last 27 slots of the array, but there would be no way for the garbage-collector to know that. Consequently, the garbage-collector would never call finalize on objects in those slots unless or until the container overwrote those array slots, the container abandoned the array (perhaps in favor of a smaller one), or all rooted references to the container itself were destroyed or otherwise ceased to exist.

There are various means to encourage the system to call finalize on any objects for which no strong rooted references happen to exist (which seems to be the point of the question, and which other answers have already covered), but I think it's important to note the distinction between the set of objects to which strong rooted references exist, and the set of objects that code may be interested in. The two sets largely overlap, but each set can contain objects not in the other. Objects' finalizers` run when the GC determines that the objects would no longer exist but for the existence of finalizers; that may or may not coincide with the time code they cease being of interest to anyone. While it would be helpful if one could cause finalizers to run on all objects that have ceased to be of interest, that is in general not possible.

A call to garabage collecter (System.gc()) method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse (i.e its just a suggestion to the jvm, and does not bind it to perform the action then and there, it may or may not do the same). When control returns from the method call, the Java Virtual Machine has made a best effort to reclaim space from all discarded objects. finalize() is called by the garbage collector on an object when garbage collection determines that there are no more references to the object

run new constructor() and System.gc() more than twice.

public class Horse {
    boolean inStable;
    Horse(boolean in){
        inStable = in;
    }   
    public void finalize(){
        if (!inStable) System.out.print("Error: A horse is out of its stable!");
    }
}
public class MainWindow {
    public static void main(String[] args) {
        for (int i=0;i<100;i++){
            Horse h = new Horse(false);
            h = new Horse(true);
            System.gc();
        }
    }
}  

Here's what worked for me (partially, but it does illustrate the idea):

class OLoad {

    public void finalize() {
        System.out.println("I'm melting!");
    }
}

public class TempClass {

    public static void main(String[] args) {
        new OLoad();
        System.gc();
    }
}

The line new OLoad(); does the trick, as it creates an object with no reference attached. This helps System.gc() run the finalize() method as it detects an object with no reference. Saying something like OLoad o1 = new OLoad(); will not work as it will create a reference that lives until the end of main(). Unfortunately, this works most of the time. As others pointed out, there's no way to ensure finalize() will be always called, except to call it yourself.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top