Fix byte array memory leak from MemoryBasedClassLoader

40 Mb of bytes of preloaded compiler classes during the compilation become 16
Mb after this change
This commit is contained in:
Alexander Udalov
2015-03-24 16:40:42 +03:00
parent 30916e37ce
commit 58b033d9fb
3 changed files with 27 additions and 4 deletions
@@ -17,6 +17,8 @@
package org.jetbrains.kotlin.preloading;
import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
@@ -29,8 +31,6 @@ public class ClassPreloadingUtils {
/**
* Creates a class loader that loads all classes from {@code jarFiles} into memory to make loading faster (avoid skipping through zip archives).
*
* NOTE: if many resources with the same name exist, only the first one will be loaded
*
* @param jarFiles jars to load all classes from
* @param classCountEstimation an estimated number of classes in a the jars
* @param parentClassLoader parent class loader
@@ -53,7 +53,15 @@ public class ClassPreloadingUtils {
parentClassLoader = preloadClasses(classpath, classCountEstimation, parentClassLoader, null, handler);
}
return new MemoryBasedClassLoader(classesToLoadByParent, parentClassLoader, entries, handler);
return new MemoryBasedClassLoader(classesToLoadByParent, createFallbackClassLoader(jarFiles, parentClassLoader), entries, handler);
}
private static URLClassLoader createFallbackClassLoader(Collection<File> files, ClassLoader parent) throws IOException {
List<URL> urls = new ArrayList<URL>(files.size());
for (File file : files) {
urls.add(file.toURI().toURL());
}
return new URLClassLoader(urls.toArray(new URL[urls.size()]), parent);
}
public static ClassLoader preloadClasses(
@@ -21,6 +21,14 @@ import java.net.URL;
import java.util.*;
@SuppressWarnings("unchecked")
/**
* A class loader which loads classes and resources from the given map.
*
* To save memory, as soon as any class is loaded, its bytecode is removed from the map.
* This means that once any class is loaded, it _cannot be found_ as a resource anymore.
* Therefore if you need to be able to find classes via findResource(), you should pass a parent
* class loader which is able to do that at any point of time.
*/
public class MemoryBasedClassLoader extends ClassLoader {
private final ClassCondition classesToLoadByParent;
private final ClassLoader parent;
@@ -77,6 +85,9 @@ public class MemoryBasedClassLoader extends ClassLoader {
Object resources = preloadedResources.get(internalName);
if (resources == null) return null;
// Clear the resource, we won't need it anymore
preloadedResources.remove(internalName);
ResourceData resourceData = resources instanceof ResourceData
? ((ResourceData) resources)
: ((List<ResourceData>) resources).get(0);
@@ -135,7 +135,11 @@ public class PathUtil {
@NotNull
public static File getResourcePathForClass(@NotNull Class aClass) {
String resourceRoot = PathManager.getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class");
String path = "/" + aClass.getName().replace('.', '/') + ".class";
String resourceRoot = PathManager.getResourceRoot(aClass, path);
if (resourceRoot == null) {
throw new IllegalStateException("Resource not found: " + path);
}
return new File(resourceRoot).getAbsoluteFile();
}