Improve memory usage for headless generation of many projects, by using a shared Tigerstripe runtime instance per-thread.

Change-Id: I2e7fc06cbf167d3ea66a88a2b4079dacc42140b2
diff --git a/core/org.eclipse.tigerstripe.api/pom.xml b/core/org.eclipse.tigerstripe.api/pom.xml
index 9567968..e7a398a 100644
--- a/core/org.eclipse.tigerstripe.api/pom.xml
+++ b/core/org.eclipse.tigerstripe.api/pom.xml
@@ -42,6 +42,11 @@
 			<artifactId>velocity</artifactId>

 			<version>1.5</version>

 		</dependency>

+		<dependency>

+			<groupId>commons-io</groupId>

+			<artifactId>commons-io</artifactId>

+			<version>2.8.0</version>

+		</dependency>

 	</dependencies>

 

 	<build>

diff --git a/core/org.eclipse.tigerstripe.api/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/TigerstripeJarUtils.java b/core/org.eclipse.tigerstripe.api/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/TigerstripeJarUtils.java
index 2a2d480..1e262ab 100644
--- a/core/org.eclipse.tigerstripe.api/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/TigerstripeJarUtils.java
+++ b/core/org.eclipse.tigerstripe.api/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/TigerstripeJarUtils.java
@@ -8,6 +8,7 @@
 import java.util.jar.JarEntry;

 import java.util.jar.JarFile;

 

+import org.apache.commons.io.IOUtils;

 import org.eclipse.tigerstripe.workbench.TigerstripeException;

 

 public class TigerstripeJarUtils {

@@ -25,15 +26,9 @@
             File file = File.createTempFile(name.substring(0, name.length() - extension.length()) + ".",

                     extension);

             file.deleteOnExit();

-            try ( 

-                InputStream input = jarFile.getInputStream(jarEntry); 

-                OutputStream output = new FileOutputStream(file);) {

-

-                int readCount;

-                byte[] buffer = new byte[4096];

-                while ((readCount = input.read(buffer)) != -1) {

-                    output.write(buffer, 0, readCount);

-                }

+            try (InputStream input = jarFile.getInputStream(jarEntry);

+                    OutputStream output = new FileOutputStream(file)) {

+                IOUtils.copy(input, output);

             }

             return file;

         } catch (IOException e) {

diff --git a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/runner/GenerationData.java b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/runner/GenerationData.java
new file mode 100644
index 0000000..248e629
--- /dev/null
+++ b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/runner/GenerationData.java
@@ -0,0 +1,40 @@
+/********************************************************************************
+ * Copyright (c) 2018 by Cisco Systems, Inc. All rights reserved.
+ * 
+ * This software contains proprietary information which shall not be reproduced or 
+ * transferred to other documents and shall not be disclosed to others or used for 
+ * manufacturing or any other purpose without prior permission of Cisco Systems.
+ ********************************************************************************/
+package org.eclipse.tigerstripe.runner;
+
+import java.io.File;
+
+import org.eclipse.tigerstripe.workbench.TigerstripeException;
+import org.eclipse.tigerstripe.workbench.internal.api.ITigerstripeRuntime;
+
+public class GenerationData {
+
+    private String loggingDir;
+    private String primitivesDir;
+    
+    GenerationData(ITigerstripeRuntime runtime) throws TigerstripeException {
+        setLoggingDir(runtime.getLoggingDir());
+        setPrimitivesDir(new File(runtime.getPhantomProject().getBaseDir(), "classes").getAbsolutePath());
+    }
+    
+    public String getLoggingDir() {
+        return loggingDir;
+    }
+    
+    void setLoggingDir(String loggingDir) {
+        this.loggingDir = loggingDir;
+    }
+    
+    public String getPrimitiviesDir() {
+        return primitivesDir;
+    }
+    
+    void setPrimitivesDir(String primitivesDir) {
+        this.primitivesDir = primitivesDir;
+    }
+}
diff --git a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/runner/ProjectGenerator.java b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/runner/ProjectGenerator.java
index 2c8ac34..3b8341e 100644
--- a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/runner/ProjectGenerator.java
+++ b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/runner/ProjectGenerator.java
@@ -10,6 +10,7 @@
 import java.io.File;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.util.AbstractMap;
 import java.util.Collection;
 
 import org.apache.commons.lang.StringUtils;
@@ -38,6 +39,16 @@
 public class ProjectGenerator {
 
     private static final Logger LOG = LoggerFactory.getLogger(ProjectGenerator.class);
+    
+    // To improve memory consumption and class loading overhead, we create just one
+    // Tigerstripe runtime instance per thread. This does mean things like
+    // Tigerstripe Profile and Installed Generators/Plugins are cached, so we may
+    // need to consider an option to not cache the instance for projects that want
+    // to run different profiles/generators within a single JVM.
+    // NOTE: This is technically a memory leak, as we don't have any way to release
+    // the instances once they are no longer needed. That said, we do "dispose" them
+    // to clean up some data that we know will not need to be referenced again.
+    private static final ThreadLocal<NewTigerstripeRuntime> threadRuntime = new ThreadLocal<>();
 
     /**
      * Runs generation of the given Tigerstripe project(s).
@@ -45,28 +56,32 @@
      * @param arguments
      *            - the generation arguments
      */
-    public static ITigerstripeRuntime generate(GeneratorArguments arguments) throws TigerstripeException {
+    public static GenerationData generate(GeneratorArguments arguments) throws TigerstripeException {
 
         File tigerstripeDir = arguments.getWorkingDirectory();
         String modelName = arguments.getProject() != null ? arguments.getProject().getName() : arguments.getModuleId();
         LOG.info("Starting Tigerstripe generation of {} at: {}", modelName, tigerstripeDir.getAbsolutePath());
 
-        ClassLoader originalContext = setThreadContextForGeneration(arguments);
+
+        AbstractMap.SimpleEntry<ClassLoader, URLClassLoader> classloaders = prepareThreadContextForGeneration(arguments);
+        NewTigerstripeRuntime runtime = null;
         try {
             long start = System.currentTimeMillis();
-            ITigerstripeRuntime runtime = prepareRuntime(tigerstripeDir);
+            runtime = prepareRuntime(tigerstripeDir);
             performGeneration(runtime, arguments);
             long duration = System.currentTimeMillis() - start;
             LOG.info("Generation of {} completed in {} milliseconds.", modelName, Long.valueOf(duration));
-            return runtime;
+            return new GenerationData(runtime);
         } finally {
-            NewTigerstripeRuntime.setThreadActiveRuntime(null);
-            resetThreadContext(originalContext);
+            if (runtime != null) {
+                runtime.dispose();
+            }
+            resetThreadContext(classloaders);
         }
     }
 
     @SuppressWarnings("resource")
-    private static ClassLoader setThreadContextForGeneration(GeneratorArguments arguments) {
+    private static AbstractMap.SimpleEntry<ClassLoader, URLClassLoader>  prepareThreadContextForGeneration(GeneratorArguments arguments) {
         if (!arguments.getExtraClasspathEntries().isEmpty()) {
             ClassLoader cl = Thread.currentThread().getContextClassLoader();
             URLClassLoader generationCL = URLClassLoader
@@ -77,25 +92,35 @@
             }
             LOG.trace("Setting updated thread classloader before generation");
             Thread.currentThread().setContextClassLoader(generationCL);
-            return cl;
+            return new AbstractMap.SimpleEntry<>(cl, generationCL);
         }
         return null;
     }
 
-    private static void resetThreadContext(ClassLoader context) {
-        if (context != null) {
+    private static void resetThreadContext(AbstractMap.SimpleEntry<ClassLoader, URLClassLoader> classloaders) {
+        if (classloaders != null) {
             LOG.trace("Resetting thread classloader context after generation");
-            Thread.currentThread().setContextClassLoader(context);
+            Thread.currentThread().setContextClassLoader(classloaders.getKey());
+            try {
+                classloaders.getValue().close();
+            } catch (Exception e) {
+                LOG.error("An error occurred closing the generation classloader", e);
+            }
         }
     }
 
-    private static ITigerstripeRuntime prepareRuntime(File tigerstripeDir) throws TigerstripeException {
-        // TODO - runtime/directory/init/thread-local should all be handled by constructor
-        NewTigerstripeRuntime runtime = new NewTigerstripeRuntime();
-        runtime.setRuntype(NewTigerstripeRuntime.STANDALONE_RUN);
+    private static NewTigerstripeRuntime prepareRuntime(File tigerstripeDir) throws TigerstripeException {
+        // Only instantiate a new runtime if we don't already have one cached in the thread.
+        NewTigerstripeRuntime runtime = threadRuntime.get();
+        if (threadRuntime.get() == null) {
+            runtime = new NewTigerstripeRuntime();
+            runtime.setRuntype(NewTigerstripeRuntime.STANDALONE_RUN);
+            NewTigerstripeRuntime.setThreadActiveRuntime(runtime);
+            threadRuntime.set(runtime);
+        }
+        
         // Set as the new thread local instance before calling init, otherwise
         // some code may acquire the old instance
-        NewTigerstripeRuntime.setThreadActiveRuntime(runtime);
         runtime.init(tigerstripeDir.getAbsolutePath());
 
         IWorkbenchProfile profile = runtime.getWorkbenchProfileSession().getActiveProfile();
diff --git a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/NewTigerstripeRuntime.java b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/NewTigerstripeRuntime.java
index 718f2b1..8d4f549 100644
--- a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/NewTigerstripeRuntime.java
+++ b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/NewTigerstripeRuntime.java
@@ -6,6 +6,7 @@
 import java.security.CodeSource;

 import java.text.SimpleDateFormat;

 import java.time.Year;

+import java.util.Enumeration;

 import java.util.GregorianCalendar;

 import java.util.Map;

 import java.util.TimeZone;

@@ -14,6 +15,7 @@
 import java.util.jar.Manifest;

 

 import org.apache.commons.lang.StringUtils;

+import org.apache.log4j.Appender;

 import org.apache.log4j.Level;

 import org.apache.log4j.Logger;

 import org.apache.log4j.PatternLayout;

@@ -38,9 +40,17 @@
 public class NewTigerstripeRuntime implements ITigerstripeRuntime {

 

     // Version of the currently active tigerstripe feature

-    public static final String TIGERSTRIPE_FEATURE_VERSION = "tigerstripe.feature.version";

+    public static final String TIGERSTRIPE_FEATURE_VERSION = "tigerstripe.feature.version"; // $NON-NLS-1$

 

-    public static final String CURRENT_YEAR = "tigerstripe.runtime.year";

+    public static final String CURRENT_YEAR = "tigerstripe.runtime.year"; // $NON-NLS-1$

+

+    public static final String TIGERSTRIPE_HOME_DIR = "tigerstripe"; // $NON-NLS-1$

+

+    public static final String TIGERSTRIPE_PLUGINS_DIR = "plugins"; // $NON-NLS-1$

+

+    public static final String TIGERSTRIPE_MODULES_DIR = "modules"; // $NON-NLS-1$

+

+    public static final String PRODUCT_NAME = "product.name"; // $NON-NLS-1$

 

     /** Signaling we're running as a generic Eclipse Run */

     public static final int ECLIPSE_GUI_RUN = 1;

@@ -56,7 +66,34 @@
 

     /** Signaling we're running thru the Eclipse Headless harness */

     public static final int STANDALONE_RUN = 4;

+    

+    private static final String LOG4J_FQCN = NewTigerstripeRuntime.class.getName();

+    

+    private static NewTigerstripeRuntime defaultInstance;

 

+    private static ThreadLocal<NewTigerstripeRuntime> threadLocalInstance = new ThreadLocal<>();

+

+    public static NewTigerstripeRuntime getThreadActiveRuntime() {

+        return threadLocalInstance.get() != null ? threadLocalInstance.get() : getDefaultInstance();

+    }

+    

+    private static synchronized NewTigerstripeRuntime getDefaultInstance() {

+        if (defaultInstance == null) {

+            defaultInstance = new NewTigerstripeRuntime();

+        }

+        return defaultInstance;

+    }

+    

+    public static void setThreadActiveRuntime(NewTigerstripeRuntime runtime) {

+        NewTigerstripeRuntime old = threadLocalInstance.get();

+        if (old != null) {

+            old.dispose();

+        }

+        threadLocalInstance.set(runtime);

+    }

+

+    private volatile boolean initialized = false;

+    

     /*

      * the current run type. This is set once and for all Valid values are

      * ECLIPSE_GUI_RUN, ECLIPSE_HEADLESS_RUN, ANT_RUN, CLI_RUN

@@ -66,27 +103,16 @@
     private Logger tigerstripeLogger;

 

     private String loggingDir = null;

-

-    private RollingFileAppender appender;

+    private String loggingFile = null;

 

     private Level defaultLoggingLevel = Level.ALL;

 

     private int maxNumBackupLogs = 9;

 

-    private final String LOG4J_FQCN = NewTigerstripeRuntime.class.getName();

-

     private String logStartTime = "";

 

     private String tigerstripeRuntimeRoot;

 

-    public static final String TIGERSTRIPE_HOME_DIR = "tigerstripe";

-

-    public static final String TIGERSTRIPE_PLUGINS_DIR = "plugins";

-

-    public static final String TIGERSTRIPE_MODULES_DIR = "modules";

-

-    public static final String PRODUCT_NAME = "product.name";

-

     private TigerstripePhantomProjectHandle phantomHandle = null;

 

     private PhantomTigerstripeProjectMgr tsProjectManager;

@@ -99,34 +125,6 @@
 

     private String ts_home;

 

-    private volatile boolean initialized = false;

-

-    // This can be called if you dpn;t need any specific instance data -

-    // eg in the ArtifactPersister, it is used to get the

-    // version. Don;t use for anything that needs phantom etc.

-

-    // THIS is used by the eclipse plugins, as there should only be one runtime

-    // instance there!

-    private static NewTigerstripeRuntime defaultInstance = new NewTigerstripeRuntime();

-

-    private static ThreadLocal<NewTigerstripeRuntime> threadLocalInstance = new ThreadLocal<>();

-

-    public static NewTigerstripeRuntime getThreadActiveRuntime() {

-        return threadLocalInstance.get() != null ? threadLocalInstance.get() : defaultInstance;

-    }

-    

-    public static void setThreadActiveRuntime(NewTigerstripeRuntime runtime) {

-        NewTigerstripeRuntime old = threadLocalInstance.get();

-        if (old != null) {

-            old.dispose();

-        }

-        threadLocalInstance.set(runtime);

-    }

-

-    public NewTigerstripeRuntime() {

-        tsProjectManager = new PhantomTigerstripeProjectMgr(this);

-    }

-

     public int getRuntype() {

         return runType;

     }

@@ -190,7 +188,7 @@
                 boolean outputFileExists = false;

                 if (outputFile.exists())

                     outputFileExists = true;

-                appender = new RollingFileAppender(patternLayout, outputFile.getAbsolutePath());

+                RollingFileAppender appender = new RollingFileAppender(patternLayout, outputFile.getAbsolutePath());

                 appender.setMaxBackupIndex(maxNumBackupLogs);

                 appender.setImmediateFlush(true);

                 tigerstripeLogger = Logger.getLogger(tigerstripeLoggerID);

@@ -201,6 +199,7 @@
                 tigerstripeLogger.addAppender(appender);

                 tigerstripeLogger.setAdditivity(false);

                 tigerstripeLogger.setLevel(defaultLoggingLevel);

+                loggingFile = appender.getFile();

             } catch (IOException e) {

                 e.printStackTrace(System.err);

             }

@@ -294,7 +293,7 @@
     }

 

     public String getLogPath() {

-        return appender.getFile();

+        return loggingFile;

     }

 

     protected static String findWorkbenchFeatureVersion() {

@@ -334,11 +333,16 @@
     }

 

     public IPhantomProjectManager getPhantomTigerstripeProjectMgr() {

+        synchronized (this) {

+            if (tsProjectManager == null) {

+                tsProjectManager = new PhantomTigerstripeProjectMgr(this);

+            }

+        }

         return tsProjectManager;

     }

 

     public IPhantomTigerstripeProject getPhantomProject() throws TigerstripeException {

-        synchronized (getPhantomTigerstripeProjectMgr()) {

+        synchronized (this) {

             if (phantomHandle == null) {

                 phantomHandle = new TigerstripePhantomProjectHandle(this);

                 phantomHandle.init();

@@ -349,7 +353,7 @@
     }

 

     public void resetPhantomProject() {

-        synchronized (getPhantomTigerstripeProjectMgr()) {

+        synchronized (this) {

             if (phantomHandle != null) {

                 phantomHandle.dispose();

                 phantomHandle = null;

@@ -390,22 +394,40 @@
         }

     }

     

-    private void dispose() {

+    public void dispose() {

+        // NOTE - We do not dispose the workbench profile or plugins, as they are very

+        // expensive to reload in case this instance is re-initialized again.

         if (classpathModuleManager != null) {

             classpathModuleManager.dispose();

             classpathModuleManager = null;

         }

-        if (pluginManager != null) {

-            pluginManager.dispose();

-            pluginManager = null;

+        if (phantomHandle != null) {

+            phantomHandle.dispose();

+            phantomHandle = null;

         }

-        if (workbenchProfileSession != null) {

-            workbenchProfileSession.dispose();

-            workbenchProfileSession = null;

+        if (tsProjectManager != null) {

+            tsProjectManager.dispose();

+            tsProjectManager = null;

         }

+        if (tigerstripeLogger != null) {

+            Enumeration<Appender> appenders = tigerstripeLogger.getAllAppenders();

+            if (appenders != null) {

+                while (appenders.hasMoreElements()) {

+                    Appender appender = appenders.nextElement();

+                    appender.close();

+                    tigerstripeLogger.removeAppender(appender);

+                }

+            }

+            tigerstripeLogger = null;

+        }

+        loggingDir = null;

+        loggingFile = null;

+        logStartTime = null;

+        tigerstripeRuntimeRoot = null;

+        ts_home = null;

         initialized = false;

     }

-

+    

     public String getTs_home() {

         return ts_home;

     }

diff --git a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/PluginManager.java b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/PluginManager.java
index 3c8f628..ad4a72d 100644
--- a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/PluginManager.java
+++ b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/PluginManager.java
@@ -220,7 +220,7 @@
                         }
                     }
 
-                    PluggablePlugin pluginBody = new PluggablePlugin(runtime,unZippedFile, zippedFile);
+                    PluggablePlugin pluginBody = new PluggablePlugin(runtime, unZippedFile);
                     if (pluginBody.isValid()) {
                         PluggableHousing housing = new PluggableHousing(pluginBody);
                         registerHousing(housing);
diff --git a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/ContributedPlugin.java b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/ContributedPlugin.java
index 8a0ceca..6b27b45 100644
--- a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/ContributedPlugin.java
+++ b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/ContributedPlugin.java
@@ -22,7 +22,7 @@
 	 * @throws TigerstripeException 
 	 */
 	public ContributedPlugin(ITigerstripeRuntime runtime, String path, Bundle bundle) throws TigerstripeException {
-		super(runtime, path, null);
+		super(runtime, path);
 		this.bundle = bundle;
 		this.bundleName = bundle.getHeaders().get("Bundle-Name");
 	}
diff --git a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/JarInJarClassLoader.java b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/JarInJarClassLoader.java
index 2e310ef..31f12f1 100644
--- a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/JarInJarClassLoader.java
+++ b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/JarInJarClassLoader.java
@@ -5,6 +5,8 @@
 import java.net.URL;

 import java.net.URLClassLoader;

 import java.util.Enumeration;

+import java.util.HashMap;

+import java.util.Map;

 import java.util.jar.JarEntry;

 import java.util.jar.JarFile;

 

@@ -35,8 +37,8 @@
     }

 

     private void addJarResource(File file) throws TigerstripeException {

-        try (JarFile jarFile = new JarFile(file);){

 

+        try (JarFile jarFile = new JarFile(file)) {

             addURL(file.toURL());

             Enumeration<JarEntry> jarEntries = jarFile.entries();

             while (jarEntries.hasMoreElements()) {

@@ -45,36 +47,38 @@
                     addJarResource(TigerstripeJarUtils.jarEntryAsFile(jarFile, jarEntry));

                 }

             }

-            

         } catch (IOException e) {

-            runtime.logErrorMessage(

-                    "Error adding Jar resource to classpath",

-                    e); 

+            runtime.logErrorMessage("Error adding Jar resource to classpath", e);

         }

     }

 

     @Override

-    protected synchronized Class<?> loadClass(String name, boolean resolve)

+    protected Class<?> loadClass(String name, boolean resolve)

             throws ClassNotFoundException {

-        try {

-            Class<?> clazz = findLoadedClass(name);

-            if (clazz == null) {

-                clazz = findClass(name);

+

+        synchronized (getClassLoadingLock(name)) {

+            try {

+                Class<?> clazz = findLoadedClass(name);

+                if (clazz == null) {

+                    clazz = findClass(name);

+                }

                 if (resolve) {

                     resolveClass(clazz);

                 }

+                return clazz;

+            } catch (ClassNotFoundException e) {

+                return super.loadClass(name, resolve);

             }

-            return clazz;

-        } catch (ClassNotFoundException e) {

-            return super.loadClass(name, resolve);

         }

     }

 

-

     private static boolean isJar(String fileName) {

         return fileName != null && (fileName.toLowerCase().endsWith(".jar"));

     }

-

-

-

+    

+    @Override

+    public void close() throws IOException {

+        this.runtime = null;

+        super.close();

+    }

 }

diff --git a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/PluggablePlugin.java b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/PluggablePlugin.java
index 749b357..cd0fd25 100644
--- a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/PluggablePlugin.java
+++ b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/PluggablePlugin.java
@@ -11,6 +11,7 @@
 package org.eclipse.tigerstripe.workbench.internal.core.plugin.pluggable;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
@@ -29,7 +30,6 @@
 import org.eclipse.tigerstripe.workbench.internal.core.plugin.base.BasePlugin;
 import org.eclipse.tigerstripe.workbench.internal.core.project.pluggable.M0ProjectDescriptor;
 import org.eclipse.tigerstripe.workbench.internal.core.project.pluggable.PluggablePluginProject;
-import org.eclipse.tigerstripe.workbench.internal.core.util.FileUtils;
 import org.eclipse.tigerstripe.workbench.plugins.EPluggablePluginNature;
 import org.eclipse.tigerstripe.workbench.plugins.IPluginClasspathEntry;
 import org.eclipse.tigerstripe.workbench.plugins.IPluginProperty;
@@ -46,8 +46,11 @@
  * @since 1.2
  */
 public class PluggablePlugin extends BasePlugin {
+    
+    public static final String TEMPLATE_PREFIX = "org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/resources"; // $NON-NLS-1$
 
-	private static final String DEFAULT_LOG_DIR = "logs";
+	private static final String DEFAULT_LOG_DIR = "logs"; // $NON-NLS-1$
+    private static final String REPORTTEMPLATE = "PLUGGABLE_REPORT.vm"; // $NON-NLS-1$
 
 	private boolean canDelete = true;
 	
@@ -58,47 +61,38 @@
 	 */
 	private String path;
 
-	/**
-	 * This is only passed around so that we can delete it.
-	 */
-	private String zippedFile;
-
 	protected IGeneratorDescriptor descriptor = null;
 
 	private String[] definedProperties = null;
 
-	private final static String REPORTTEMPLATE = "PLUGGABLE_REPORT.vm";
-
-	public final static String TEMPLATE_PREFIX = "org/eclipse/tigerstripe/workbench/internal/core/plugin/pluggable/resources";
-
 	private PluggablePluginReport report;
 	
 	private ITigerstripeRuntime runtime;
 
-	public ITigerstripeRuntime getRuntime() {
-        return runtime;
+    
+    public PluggablePlugin() {
+        
     }
-
-    public void setRuntime(ITigerstripeRuntime runtime) {
-        this.runtime = runtime;
-    }
-
+    
     /**
 	 * 
 	 * @param path
 	 *            - The path to the unzipped plugin directory
 	 * @throws TigerstripeException 
 	 */
-	public PluggablePlugin(ITigerstripeRuntime runtime, String path, String zippedFile) throws TigerstripeException {
+	public PluggablePlugin(ITigerstripeRuntime runtime, String path) throws TigerstripeException {
 		this.runtime = runtime;
 		this.path = path;
-		this.zippedFile = zippedFile;
 		loadProject();
 	}
-	
-	public PluggablePlugin() {
-	    
-	}
+
+    public ITigerstripeRuntime getRuntime() {
+        return runtime;
+    }
+
+    public void setRuntime(ITigerstripeRuntime runtime) {
+        this.runtime = runtime;
+    }
 	
 	public EPluggablePluginNature getPluginNature() {
 		return descriptor.getPluginNature();
@@ -108,8 +102,15 @@
 	    if (descriptor != null) {
 	        descriptor.dispose();
 		}
+	    if (classLoader != null) {
+	        try {
+	            classLoader.close();
+	        } catch (IOException e) {
+	            runtime.logErrorMessage("Failed to close plugin classloader", e);
+	        }
+	        classLoader = null;
+	    }
 		this.path = null;
-		this.zippedFile = null;
 		this.runtime = null;
 	}
 
diff --git a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/profile/PhantomTigerstripeProjectMgr.java b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/profile/PhantomTigerstripeProjectMgr.java
index c83fdd9..e1b7cc1 100644
--- a/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/profile/PhantomTigerstripeProjectMgr.java
+++ b/core/org.eclipse.tigerstripe.core/src/main/java/org/eclipse/tigerstripe/workbench/internal/core/profile/PhantomTigerstripeProjectMgr.java
@@ -40,6 +40,11 @@
 		}
 	}
 	
+	public void dispose() {
+	    reset();
+	    runtime = null;
+	}
+	
 	public synchronized PhantomTigerstripeProject getPhantomProject()
 			throws TigerstripeException {
 		if (phantomProject == null) {