4 Replies Latest reply: Nov 18, 2011 5:37 PM by 823071 RSS

    Threadleak caused by JavaMail events?

    823071
      Hi,

      I am using Javamail in a web application running on Tomcat6.

      I noticed that the application starts consuming a lot of memory after a while. After looking at some thread dumps, I noticed that there seems to be a thread leak going on since there are tons of tons of these threads in there:

      "JavaMail-EventQueue" daemon prio=5 tid=10792a000 nid=0x127c0e000 in Object.wait() [127c0d000]
      java.lang.Thread.State: WAITING (on object monitor)
           at java.lang.Object.wait(Native Method)
           - waiting on <6c4153460> (a javax.mail.EventQueue)
           at java.lang.Object.wait(Object.java:485)
           at javax.mail.EventQueue.dequeue(EventQueue.java:108)
           - locked <6c4153460> (a javax.mail.EventQueue)
           at javax.mail.EventQueue.run(EventQueue.java:128)
           at java.lang.Thread.run(Thread.java:680)

      Locked ownable synchronizers:
           - None

      As you can see, this thread is in waiting state. It never gets terminated and thus after a while, memory is full.

      So, I further looked into the issue and noticed that these threads get created when the user clicks on a mail folder in my application which then fetches the according emails from the mail server and displays them.

      Since the thread dump message above states something about JavaMail events, I started looking into JavaMail events that I listen to. I couldn't find an issue here but maybe someone else does or has an idea what's going on:

      ---------------------------------------------

      /*
      * Wrapper class for JavaMail folders
      */
      public class CachedFolder {

           // Non-relevant properties and methods ommited
           
           private List<CachedMessage>               cachedMessages          = new Vector<CachedMessage>();
           private ConnectionListener               connectionListener     = null;
           private FolderListener                    folderListener          = null;
           private String                              fullPath;
           private int                                   holdsFolders          = -1;
           private int                                   holdsMessages          = -1;
           private long                              id;
           private Folder                              internalFolder          = null;
           private int                                   isOpen                    = -1;
           private boolean                              isRootFolder          = false;
           private String                              name                    = null;
           private int                                   numOfMessages          = -1;
           private int                                   numOfUnreadMessages     = -1;
           private CachedFolder                    parentFolder;
           private String                              path                    = null;
           private int                                   type                    = -1;

           /**
           * Default constructor
           */
           public CachedFolder() {
                super();
                createListeners();
           }

           /*
           * Ads event listeners to the given folder
           */
           private void addEventListeners(Folder folder) {
                handleConnectionEvents(folder);
                handleFolderEvents(folder);
           }
           
           /**
           * Clears the cache
           *
           * @param reopen
           * Set to true if the folder should be first closed and then
           * reopened. False, if not.
           * @param clearMessages
           * Set to true if messages should be cleared as well
           */
           public void clear(boolean reopen, boolean clearMessages) {
                System.out.println("clear() called");
                if (reopen) {
                     reopen();
                }
                name = null;
                numOfMessages = -1;
                numOfUnreadMessages = -1;
                holdsFolders = -1;
                holdsMessages = -1;
                type = -1;
                if (clearMessages) {
                     if (cachedMessages != null) {
                          System.out.println("Clearing messages");
                          cachedMessages.clear();
                     }
                }
                cachedFolders = null;
                path = null;
                System.out.println("done clear");
           }
           
           /*
           * Creates a new connection listener instance
           */
           private void createConnectionListener() {
                connectionListener = new ConnectionListener() {
                     @Override
                     public void closed(ConnectionEvent arg0) {
                          clear(false, true);
                          System.out.println("EVENT FROM SERVER! Javmail event in CachedFolder.java: Connection closed - Cache cleared.");
                     }

                     @Override
                     public void disconnected(ConnectionEvent arg0) {
                          // This method cannot be called from a folder!
                          // clear(false);
                     }

                     @Override
                     public void opened(ConnectionEvent arg0) {
                          clear(false, true);
                          System.out.println("EVENT FROM SERVER! Javmail event in CachedFolder.java: Connection openend - Cache cleared");
                     }
                };
           }

           /*
           * Creates a new folder listener instance
           */
           private void createFolderListener() {
                folderListener = new FolderListener() {
                     @Override
                     public void folderCreated(FolderEvent event) {
                          System.out.println("Folder create event called");
                     }

                     @Override
                     public void folderDeleted(FolderEvent event) {
                          // Not working properly. Better rely on events from Store.
                     }

                     @Override
                     public void folderRenamed(FolderEvent event) {
                          // Not working properly. Better rely on events from Store.
                     }
                };
           }

           /*
           * Creates listener instances
           */
           private void createListeners() {
                createFolderListener();
                createConnectionListener();
           }
           
           /*
           * Handles folder-events related to connections
           */
           private void handleConnectionEvents(Folder folder) {
                // Handle connection events on folder (onClose, onOpen etc...)
                if (folder != null) {
                     try {
                          folder.addConnectionListener(connectionListener);
                     } catch (Exception e) {
                          handleException(e, false, null, null);
                     }
                }
           }

           /*
           * Handles general folder events (creation, rename, delete...)
           */
           private void handleFolderEvents(Folder folder) {
                try {
                     folder.addFolderListener(folderListener);
                } catch (Exception e) {
                     handleException(e, false, null, null);
                }
           }

           /*
           * Closes and then opens the folder
           */
           private void reopen() {
                Folder folder = getInternalFolder();
                if (folder != null) {
                     if (folder.isOpen()) {
                          int oldmode = folder.getMode();
                          try {
                               folder.close(true);
                               folder.open(oldmode);
                          } catch (MessagingException e) {
                               handleException(e, false, null, null);
                          }
                     }
                }
           }
      }

      ---------------------------------------------

      /*
      * Wrapper class for JavaMail Service/Store instances
      */
      public abstract class MailServer extends Server {

           // Non-relevant properties and methods ommited

           String                              CONNECTION_TIMEOUT     = "5000";
           ConnectionListener               connectionListener     = null;
           FolderListener                    folderListener          = null;
           Service                              service;

           /**
           * Default constructor
           */
           public MailServer() {
                super();
                createListeners();
           }

           /*
           * Creates a listener instance for connection events
           */
           private void createConnectionListener() {
                connectionListener = new ConnectionListener() {
                     @Override
                     public void closed(ConnectionEvent arg0) {
                     }

                     @Override
                     public void disconnected(ConnectionEvent arg0) {
                     }

                     @Override
                     public void opened(ConnectionEvent arg0) {
                          System.out.println("EVENT called: Connection opened");
                     }
                };
           }

           /*
           * Creates a listener instance for folder events
           */
           private void createFolderListener() {
                folderListener = new FolderListener() {
                     @Override
                     public void folderCreated(FolderEvent event) {
                          System.out.println("Folder EVENT called: folderCreated for" + event.getFolder().getFullName());
                          onFolderCreated(event.getFolder());
                     }

                     @Override
                     public void folderDeleted(FolderEvent event) {
                          onFolderDeleted(event.getFolder());
                     }

                     @Override
                     public void folderRenamed(FolderEvent event) {
                          onFolderRenamed(event.getFolder(), event.getNewFolder());
                     }
                };
           }

           /*
           * Creates listener instances for ConnectionEvent and FolderEvent
           */
           private void createListeners() {
                createConnectionListener();
                createFolderListener();
           }

           /**
           * Event handler. Gets called when a new folder was created.
           */
           protected abstract void onFolderCreated(Folder folder);

           /**
           * Event handler. Gets called when a new folder was created.
           */
           protected abstract void onFolderDeleted(Folder deletedFolder);

           /**
           * Event handler. Gets called when a new folder was created.
           */
           protected abstract void onFolderRenamed(Folder oldFolder, Folder newFolder);
           
           /**
           * Setter for service
           * @param service The service to set
           */
           public void setService(Service service) {
                connectionSettingChanged = true;
                if (this.service != null) {
                     try {
                          this.service.removeConnectionListener(connectionListener);
                     } catch (Exception e) {
                          handleException(e, false, null, null);
                     }
                     if (this.service instanceof Store) {
                          Store thisStore = (Store) this.service;
                          try {
                               thisStore.removeFolderListener(folderListener);
                          } catch (Exception e) {
                               handleException(e, false, null, null);
                          }
                     }
                }
                if (service != null) {
                     try {
                          service.addConnectionListener(connectionListener);
                     } catch (Exception e) {
                          handleException(e, false, null, null);
                     }
                     if (service instanceof Store) {
                          Store store = (Store) service;
                          try {
                               store.addFolderListener(folderListener);
                          } catch (Exception e) {
                               handleException(e, false, null, null);
                          }
                     }
                }
                this.service = service;
           }
      }

      ---------------------------------------------

      Note: I handle each click on a folder in a separate thread which is being started like this:

      ServletThread.EXECUTOR_SERVICE.submit(thread);

      /**
      * Thread starter
      */
      public class ServletThread implements ServletContextListener {
           public static final ExecutorService     EXECUTOR_SERVICE     = Executors.newFixedThreadPool(1000);

           public void contextDestroyed(ServletContextEvent sce) {
                EXECUTOR_SERVICE.shutdownNow();
           }

           public void contextInitialized(ServletContextEvent sce) {
           }
      }
        • 1. Re: Threadleak caused by JavaMail events?
          Bill Shannon-Oracle
          The threads are cleaned up when the folders are closed. Are you sure you're closing the folders?
          The only call to close in the code you included is followed immediately by reopening the folder.
          • 2. Re: Threadleak caused by JavaMail events?
            823071
            No, I don't close JavaMail folders after fetching the emails since I learned in this forum that JavaMail Message instances become invalid after closing the parent folder.
            I guess that's not a good idea?
            • 3. Re: Threadleak caused by JavaMail events?
              Bill Shannon-Oracle
              That's right, once you close the folder you can no longer access the messages in the folder.
              Clearly there are tradeoffs you have to make.

              If you want to keep lots and lots of folders open, and you want to use listeners with those
              folders, you're going to need lots of memory for all the threads.

              Maybe you need to timeout user sessions accessing the folder and close folders more aggressively?

              You can copy the messages you need and keep them in memory, close the folder, and then reopen
              the folder and resynchronize your in-memory state with the folder state when you need to do folder
              operations or fetch more messages. That's more complicated, but possible.

              Or you can move to a model that doesn't depend on listeners. All of the events delivered to listeners
              are also available through synchronous folder operations.
              • 4. Re: Threadleak caused by JavaMail events?
                823071
                Thanks Bill for your thorough reply.

                Since I noticed that IMAP and POP3 are both very verbose and thus slow protocols, I want to minimize the number of (re)syncs as good as possible.
                Also, using synchronous event listening is not an option to me.

                Thus, I guess that my best bet is to somehow check if a user logged out (or his session expired) and then close all folders manually.