Java — Towards The End Of Finalize

Faruk BOZAN
emlakjet
Published in
3 min readMar 11, 2022

Welcome on post about end of an era, the finalize. As we know Java has advantage of garbage collector. I think if you are familiar with GC also you use or hear about finalize method. It is a special method to be called by GC before instance is removed(because it is unreachable) and memory is given back to operating system. But if you do not use this finalize carefully it is a trap.

Before coding a sample, let’s have a look on javadoc of finalize.

“Deprecated
The finalization mechanism is inherently problematic. Finalization can lead to performance issues, deadlocks, and hangs. Errors in finalizers can lead to resource leaks; there is no way to cancel finalization if it is no longer necessary; and no ordering is specified among calls to finalize methods of different objects. Furthermore, there are no guarantees regarding the timing of finalization. The finalize method might be called on a finalizable object only after an indefinite delay, if at all. Classes whose instances hold non-heap resources should provide a method to enable explicit release of those resources, and they should also implement AutoCloseable if appropriate. The ref.Cleaner and ref.PhantomReference provide more flexible and efficient ways to release resources when an object becomes unreachable.”

As we see, finalize is deprecated from Java 9. Now time to implement why finalize may cause problems.

package com.farukbozan.medium.finalize;

public class FinalizeModel {

private int orderNumber;

public FinalizeModel(int orderNumber) {
this.orderNumber = orderNumber;
}

@Override
protected void finalize() throws Throwable {
System.out.println("finalize() called: " + orderNumber);
}

private static void createFinalizeModels() {
for (int i = 0; i < 40000; i++) {
new FinalizeModel(i);
}
}

private static void printFreeMemory() {
long freeMemory = Runtime.getRuntime().freeMemory();
System.out.println("free memory in kb: " + (freeMemory / 1024));
}

public static void main(String[] args) {
printFreeMemory();

createFinalizeModels();

printFreeMemory();

System.gc();

printFreeMemory();
}
}

It is very clear that we want to create some FinalizeModel object, print free memory of application and request GC to execute. We also give memory VM option to force using GC.

-Xmx5m

After executing main we will get logs like below.

free memory in kb: 4055
free memory in kb: 1514
finalize() called: 4098
finalize() called: 14344
free memory in kb: 2499
finalize() called: 30712
finalize() called: 31107
finalize() called: 31419
finalize() called: 31510
finalize() called: 31624
finalize() called: 31736
finalize() called: 31943
finalize() called: 31942
finalize() called: 31941
finalize() called: 31940
finalize() called: 31944
finalize() called: 32034
finalize() called: 32165
finalize() called: 32311
finalize() called: 32496
finalize() called: 32505
finalize() called: 32599
finalize() called: 32640
finalize() called: 32683
finalize() called: 32698
finalize() called: 32763

Yes! We did! GC run and finalize is called. Oops! But there is a problem. We create 40.000 instances but finalize was called for only 20–30 instances and also they were random, not sequential. All instances is not removed from memory by GC. JVM did free memory after main ends.

So if we implement business logic in finalize, seems that it may be never called.

Now, we will implement more meaningful sample. Creating a file writer and close resources in finalize. And see what happens.

package com.farukbozan.medium.finalize;

import java.io.*;

public class FileWriter {

private OutputStream os;
private BufferedOutputStream bos;

public FileWriter() throws FileNotFoundException {
File file = new File("/Users/farukbozan/Desktop/finalize.txt");
os = new FileOutputStream(file);
bos = new BufferedOutputStream(os);
}

public void writeFile() throws IOException {
bos.write("finalize".getBytes());
bos.flush();
}

private void closeResource() throws IOException {
if (os != null) {
os.close();
System.out.println("OutputStream closed");
}
if (bos != null) {
bos.close();
System.out.println("BufferedOutputStream closed");
}
}

@Override
protected void finalize() throws Throwable {
closeResource();
}

public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter();
fw.writeFile();
}
}

After run the application, we see no logs. And that means our system/file resources does not close properly. We may face problems while opening files.

You can get sources from GitLab. Thanks for reading.

--

--