main method of some class.
The virtual machine terminates all its activity and exits when
one of two things happens:
exit method of class 
  Runtime or class System, and the exit
  operation is permitted by the security manager.
This implies that if an application doesn't start any threads itself,
the JVM will exit as soon as main terminates.
This is not the case, however, for a simple application
that creates and displays a java.awt.Frame:
        public static void main(String[] args) {
            Frame frame = new Frame();
            frame.setVisible(true);
         }
The reason is that AWT encapsulates asynchronous event dispatch
machinery to process events AWT or Swing components can fire. The
exact behavior of this machinery is implementation-dependent. In
particular, it can start non-daemon helper threads for its internal
purposes. In fact, these are the threads that prevent the example
above from exiting. The only restrictions imposed on the behavior of
this machinery are as follows:
EventQueue.isDispatchThread
       returns true if and only if the calling thread is the
       event dispatch thread started by the machinery;
  AWTEvents which were actually enqueued to a
       particular EventQueue (note that events being
       posted to the EventQueue can be coalesced) are
       dispatched:
       AWTEvent A is enqueued
	        to the EventQueue before
		AWTEvent B then event B will not be 
                dispatched before event A.
       Component.isDisplayable).
exit
  method of class Runtime or class System
  regardless of the presence of displayable components;
  Starting with 1.4, the behavior has changed as a result of the fix for 4030718. With the current implementation, AWT terminates all its helper threads allowing the application to exit cleanly when the following three conditions are true:
System.exit must:
Window.dispose
       on all top-level Windows. See
Frame.getFrames.
  EventQueue.
       The argument is that methods
       of AWT event listeners are typically executed on helper
       threads.
        <...>
        Runnable r = new Runnable() {
            public void run() {
                Object o = new Object();
                try {
                    synchronized (o) {
                        o.wait();
                    }
                } catch (InterruptedException ie) {
                }
            }
        };
        Thread t = new Thread(r);
        t.setDaemon(false);
        t.start();
        <...>
The Java™ Virtual Machine Specification
 guarantees
that the JVM doesn't exit until this thread terminates.