|
import java.io.IOException; |
|
import java.net.URLClassLoader; |
|
import java.nio.file.Files; |
|
import java.nio.file.Paths; |
|
import java.nio.file.Path; |
|
|
|
/** |
|
* Example demonstrating a ClassLoader leak. |
|
* |
|
* <p>To see it in action, copy this file to a temp directory somewhere, |
|
* and then run: |
|
* <pre>{@code |
|
* javac ClassLoaderLeakExample.java |
|
* java -cp . ClassLoaderLeakExample |
|
* }</pre> |
|
* |
|
* <p>And watch the memory grow! On my system, using JDK 1.8.0_25, I start |
|
* getting OutofMemoryErrors within just a few seconds. |
|
* |
|
* <p>This class is implemented using some Java 8 features, mainly for |
|
* convenience in doing I/O. The same basic mechanism works in any version |
|
* of Java since 1.2. |
|
*/ |
|
public final class ClassLoaderLeakExample { |
|
|
|
static volatile boolean running = true; |
|
|
|
public static void main(String[] args) throws Exception { |
|
Thread thread = new LongRunningThread(); |
|
try { |
|
thread.start(); |
|
System.out.println("Running, press any key to stop."); |
|
System.in.read(); |
|
} finally { |
|
running = false; |
|
thread.join(); |
|
} |
|
} |
|
|
|
/** |
|
* Implementation of the thread. It just calls {@link #loadAndDiscard()} |
|
* in a loop. |
|
*/ |
|
static final class LongRunningThread extends Thread { |
|
@Override public void run() { |
|
while(running) { |
|
try { |
|
loadAndDiscard(); |
|
} catch (Throwable ex) { |
|
ex.printStackTrace(); |
|
} |
|
try { |
|
Thread.sleep(100); |
|
} catch (InterruptedException ex) { |
|
System.out.println("Caught InterruptedException, shutting down."); |
|
running = false; |
|
} |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* A simple ClassLoader implementation that is only able to load one |
|
* class, the LoadedInChildClassLoader class. We have to jump through |
|
* some hoops here because we explicitly want to ensure we get a new |
|
* class each time (instead of reusing the class loaded by the system |
|
* class loader). If this child class were in a JAR file that wasn't |
|
* part of the system classpath, we wouldn't need this mechanism. |
|
*/ |
|
static final class ChildOnlyClassLoader extends ClassLoader { |
|
ChildOnlyClassLoader() { |
|
super(ClassLoaderLeakExample.class.getClassLoader()); |
|
} |
|
|
|
@Override protected Class<?> loadClass(String name, boolean resolve) |
|
throws ClassNotFoundException { |
|
if (!LoadedInChildClassLoader.class.getName().equals(name)) { |
|
return super.loadClass(name, resolve); |
|
} |
|
try { |
|
Path path = Paths.get(LoadedInChildClassLoader.class.getName() |
|
+ ".class"); |
|
byte[] classBytes = Files.readAllBytes(path); |
|
Class<?> c = defineClass(name, classBytes, 0, classBytes.length); |
|
if (resolve) { |
|
resolveClass(c); |
|
} |
|
return c; |
|
} catch (IOException ex) { |
|
throw new ClassNotFoundException("Could not load " + name, ex); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Helper method that constructs a new ClassLoader, loads a single class, |
|
* and then discards any reference to them. Theoretically, there should |
|
* be no GC impact, since no references can escape this method! But in |
|
* practice this will leak memory like a sieve. |
|
*/ |
|
static void loadAndDiscard() throws Exception { |
|
ClassLoader childClassLoader = new ChildOnlyClassLoader(); |
|
Class<?> childClass = Class.forName( |
|
LoadedInChildClassLoader.class.getName(), true, childClassLoader); |
|
childClass.newInstance(); |
|
// When this method returns, there will be no way to reference |
|
// childClassLoader or childClass at all, but they will still be |
|
// rooted for GC purposes! |
|
} |
|
|
|
/** |
|
* An innocuous-looking class. Doesn't do anything interesting. |
|
*/ |
|
public static final class LoadedInChildClassLoader { |
|
// Grab a bunch of bytes. This isn't necessary for the leak, it just |
|
// makes the effect visible more quickly. |
|
// Note that we're really leaking these bytes, since we're effectively |
|
// creating a new instance of this static final field on each iteration! |
|
static final byte[] moreBytesToLeak = new byte[1024 * 1024 * 10]; |
|
|
|
private static final ThreadLocal<LoadedInChildClassLoader> threadLocal |
|
= new ThreadLocal<>(); |
|
|
|
public LoadedInChildClassLoader() { |
|
// Stash a reference to this class in the ThreadLocal |
|
threadLocal.set(this); |
|
} |
|
} |
|
} |
|
|
This comment has been minimized.
It doesn't work. Apparently, it can not find the LoadedInChildClassLoader. And I feel stupid.