Depending on your design there are two mechanisms in Java that you can use instead. finalize
is to be avoided mainly because it creates performance issues with garbage collection and it possible create new references to the object that is being collected leading to undefined behavior i.e. big problems.
References
ReferenceQueue
If you want to be informed of the collection of an object in Java, the safe way to do that is to use a Reference
and register it with a ReferenceQueue
. This can be a little confusing to work with because you need to make sure it's the reference that tells you what object was removed, the a reference to actual object that is was collected will not be passed to you. This is to avoid the problem mentioned above with finalize.
WeakHashMap
A similar but perhaps simpler approach to using a Reference
for this is to use a 'weak' Map such as WeakHashMap. You can periodically check the Map for what remains and by elimination determine what has been removed.
Potential problem with References
The issue with this in your case is that the object will not be collected immediately after you release all references to it. In fact it may never be collected at all. One scenario could be where you have a small number of medium-to-long-lived objects (including this one) that end up in a tenured generation but everything else is short-lived. The tenured generation never fills up and therefore never gets collected so even if the garbage collector runs, some of your table objects may not be collected.
The upshot is that if you need immediate and/or deterministic counting, you can't rely on garbage collection.
Closeable or AutoCloseable
Another option is to implement the Closeable
or AutoCloseable interface. This makes your class available for use in a 'try-with' block like so:
try (Table table = new Table()) {
/* Do things with table */
}
When the block exits (for any reason) the close
method on Table will be executed. This is probably the best possible option if tables exist within the scope of a method. If you are holding these in a structure that lives on the heap and they outlive the method in which they are defined, this won't work for you.
Explicit management
The last option you have is to explicitly call a method when you are no longer using the object.
Pools
A pool allows you to keep track of set of objects. The challenge is that you need to make sure each object get's released from the client-side. If you've ever used a connection pool with database connections, you should know that you need to call close when every you are done using a connection. If you don't, you will end up with a connection leak and the pool can eventually run out of available connections.
Inverted Control
One option I've used to put all the resource management in one place. To give an example of this Consider again the Connection pool example. The common way to use them is to have code all of over the application that requests a Connection and then when it's done, close will be called. This is fine if you use try-with blocks or otherwise guarantee it's being called at the right time. You can avoid the need to do that however with something like this:
class QueryExecutor {
public void execute(SqlQuery query, ResultSetHandler handler) {
try (Connection conn = pool.getConnection()) {
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery(query.toString())) {
handler.process(rs);
}
}
}
}
}
The advantage here (aside from DRY) is that you have one place to make sure all of this is being done correctly instead of having to worry about all the possible places where a close might have been missed.