ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Surviving Abrupt Shutdown

by Budi Kurniawan
03/26/2003

In many circumstances, you need a chance to do some clean-up when the user shuts down your application. The problem is, the user does not always follow the recommended procedure to exit. Java provides an elegant way for programmers to execute code in the middle of the shutdown process, thus making sure your clean-up code is always executed. This article shows how to use a shutdown hook to guarantee that clean-up code is always run, regardless of how the user terminates the application.

You may have code that must run just before an application completely exits. For example, if you are writing a text editor with Swing and your application creates a temporary edit file when it starts, this temporary file must be deleted when the user closes your application. If you are writing a servlet container such as Tomcat or Jetty, you must call the destroy method of all loaded servlets before the application shuts down.

In many cases, you rely on the user to close the application as prescribed. For instance, in the first example, you may provide a JButton that, when clicked, runs the clean up code before exiting. Alternatively, you may use a Window listener that listens to the windowClosing event. Tomcat uses a batch file that can be executed for a proper shutdown. However, you know that the user is the king; he or she can do whatever they want with the application. He or she might be nice enough to follow your instruction, but could just close the console or log off of the system without first closing your application.

In Java, the virtual machine shuts down itself in response to two types of events: first, when the application exits normally, by calling the System.exit method or when the last non-daemon thread exits. Second, when the user abruptly forces the virtual machine to terminate; for example, by typing Ctrl+C or logging off from the system before closing a running Java program.

Related Reading

Java Performance Tuning
By Jack Shirazi

Fortunately, the virtual machine follows this two-phase sequence when shutting down:

  1. The virtual machine starts all registered shutdown hooks, if any. Shutdown hooks are threads registered with the Runtime. All shutdown hooks are run concurrently until they finish.
  2. The virtual machine calls all uninvoked finalizers, if appropriate.

In this article, we are interested in the first phase, because it allows the programmer to ask the virtual machine to execute some clean-up code in the program. A shutdown hook is simply an instance of a subclass of the Thread class. Creating a shutdown hook is simple:

  1. Write a class extending the Thread class.
  2. Provide the implementation of your class' run method. This method is the code that needs to be run when the application is shut down, either normally or abruptly.
  3. In your application, instantiate your shutdown hook class.
  4. Register the shutdown hook with the current runtime's addShutdownHook method.

As you may have noticed, you don't start the shutdown hook as you would other threads. The virtual machine will start and run your shutdown hook when it runs its shutdown sequence.

The code in Listing 1 provides a simple class called ShutdownHookDemo and a subclass of Thread named ShutdownHook. Note that the run method of the ShutdownHook class simply prints the string "Shutting down" to the console. Of course, you can insert any code that needs to be run before the shutdown.

After instantiation of the public class, its start method is called. The start method creates a shutdown hook and registers it with the current runtime.

ShutdownHook shutdownHook = new ShutdownHook();
Runtime.getRuntime().addShutdownHook(shutdownHook);

Then, the program waits for the user to press Enter.

System.in.read();

When the user does press Enter, the program exits. However, the virtual machine will run the shutdown hook, printing the words "Shutting down."

Listing 1: Using ShutdownHook

package test;

public class ShutdownHookDemo {
    public void start() {
        System.out.println("Demo");
        ShutdownHook shutdownHook = new ShutdownHook();
        Runtime.getRuntime().addShutdownHook(shutdownHook);
    }

    public static void main(String[] args) {
        ShutdownHookDemo demo = new ShutdownHookDemo();
        demo.start();
        try {
            System.in.read();
        }
        catch(Exception e) {
        }
    }
}

class ShutdownHook extends Thread {
    public void run() {
        System.out.println("Shutting down");
    }
}

As another example, consider a simple Swing application whose class is called MySwingApp (see Figure 1). This application creates a temporary file when it is launched. When closed, the temporary file must be deleted. The code for this class is given in Listing 2 on the following page.

Figure 1: A simple Swing application

Listing 2: A simple Swing application

package test;

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;

public class MySwingApp extends JFrame {
    JButton exitButton   = new JButton();
    JTextArea jTextArea1 = new JTextArea();

    String dir      = System.getProperty("user.dir");
    String filename = "temp.txt";

    public MySwingApp() {
        exitButton.setText("Exit");
        exitButton.setBounds(new Rectangle(304, 248, 76, 37));
        exitButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(ActionEvent e) {
                exitButton_actionPerformed(e);
            }
        });

        this.getContentPane().setLayout(null);
        jTextArea1.setText("Click the Exit button to quit");
        jTextArea1.setBounds(new Rectangle(9, 7, 371, 235));
        this.getContentPane().add(exitButton, null);
        this.getContentPane().add(jTextArea1, null);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setBounds(0,0, 400, 330);
        this.setVisible(true);
        initialize();
    }

    private void initialize() {
        // create a temp file
        File file = new File(dir, filename);

        try {
            System.out.println("Creating temporary file");
            file.createNewFile();
        }
        catch (IOException e) {
            System.out.println("Failed creating temporary file.");
        }
    }

    private void shutdown() {
        // delete the temp file
        File file = new File(dir, filename);

        if (file.exists()) {
            System.out.println("Deleting temporary file.");
            file.delete();
        }
    }

    void exitButton_actionPerformed(ActionEvent e) {
        shutdown();
        System.exit(0);
    }

    public static void main(String[] args) {
        MySwingApp mySwingApp = new MySwingApp();
    }
}

When run, the application calls its initialize method. The initialize method, in turn, creates a temporary file called temp.txt in the user's directory:

private void initialize() {
    // create a temp file
    File file = new File(dir, filename);

    try {
        System.out.println("Creating temporary file");
        file.createNewFile();
    }
    catch (IOException e) {
        System.out.println("Failed creating temporary file.");
    }
}

When the user closes the application, the application must delete the temporary file. We hope that the user will always click the Exit button--by doing so, the shutdown method (which deletes the temporary file) will always be called. However, the temporary file will not be deleted if the user closes the application, by clicking the X button of the frame or by some other means.

Listing 3 offers a solution to this. It modifies the code in Listing 2 by providing a shutdown hook. The shutdown hook class is declared as an inner class so that it has access to all of the methods of the main class. In Listing 3, the shutdown hook's run method calls the shutdown method, guaranteeing that this method will be invoked when the virtual machine shuts down.

Listing 3: Using a shutdown hook in the Swing application

package test;

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;

public class MySwingAppWithShutdownHook extends JFrame {
    JButton exitButton   = new JButton();
    JTextArea jTextArea1 = new JTextArea();

    String dir      = System.getProperty("user.dir");
    String filename = "temp.txt";

    public MySwingAppWithShutdownHook() {
        exitButton.setText("Exit");
        exitButton.setBounds(new Rectangle(304, 248, 76, 37));
        exitButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(ActionEvent e) {
                exitButton_actionPerformed(e);
            }
        });

        this.getContentPane().setLayout(null);
        jTextArea1.setText("Click the Exit button to quit");
        jTextArea1.setBounds(new Rectangle(9, 7, 371, 235));
        this.getContentPane().add(exitButton, null);
        this.getContentPane().add(jTextArea1, null);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setBounds(0,0, 400, 330);
        this.setVisible(true);
        initialize();
    }

    private void initialize() {
        // add shutdown hook
        MyShutdownHook shutdownHook = new MyShutdownHook();
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        // create a temp file
        File file = new File(dir, filename);

        try {
            System.out.println("Creating temporary file");
            file.createNewFile();
        }
        catch (IOException e) {
            System.out.println("Failed creating temporary file.");
        }
    }

    private void shutdown() {
        // delete the temp file
        File file = new File(dir, filename);

        if (file.exists()) {
            System.out.println("Deleting temporary file.");
            file.delete();
        }
    }

    void exitButton_actionPerformed(ActionEvent e) {
        shutdown();
        System.exit(0);
    }

    public static void main(String[] args) {
        MySwingAppWithShutdownHook mySwingApp = new MySwingAppWithShutdownHook();
    }

    private class MyShutdownHook extends Thread {
        public void run() {
            shutdown();
        }
    }
}

Pay special attention to the initialize method in the class shown in Listing 3. The first thing it does is to create an instance of the inner class MyShutdownHook, which extends a Thread:

// add shutdown hook
MyShutdownHook shutdownHook = new MyShutdownHook();

Once you get an instance of the MyShutdownHook class, pass it to the addShutDownHook method of the Runtime, as in the following line:

Runtime.getRuntime().addShutdownHook(shutdownHook);

The rest of the initialize method is similar to the initialize method in the class given in Listing 2. It creates a temporary file and prints a string "Creating temporary file":

// create a temp file
File file = new File(dir, filename);

try { 
    System.out.println("Creating temporary file");
    file.createNewFile();
}
catch (IOException e) {
    System.out.println("Failed creating temporary file.");
}

Now, start the small application given in Listing 3. Check that the temporary file is always deleted, even if you abruptly shut down the application.

Summary

Sometimes we want our application to run some clean-up code prior to shutting down. However, it is impossible to rely on the user to always quit properly. The shutdown hook described in this article offers a solution that guarantees that the clean-up code is run, regardless of how the user closes the application.

Budi Kurniawan is a senior J2EE architect and author.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.