Skip to content

Instantly share code, notes, and snippets.

@dpryden
Created October 20, 2014 00:01
Show Gist options
  • Save dpryden/b2bb29ee2d146901b4ae to your computer and use it in GitHub Desktop.
Save dpryden/b2bb29ee2d146901b4ae to your computer and use it in GitHub Desktop.
Example of a ClassLoader leak in Java
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);
}
}
}
@YiannisDermitzakis
Copy link

If you run it via an IDE it probably won't. Just follow the instructions in the Javadoc.

@dpryden
Copy link
Author

dpryden commented Oct 1, 2017

This code assumes it can find the compiled .class file for the LoadedInChildClassLoader class in the current directory. In an IDE that usually won't be the case. You can change the Path in the loadClass method to point to a different directory if you want to play around with it in an IDE. A more robust solution would be to scan the class path to find the file, but this is a quick-and-dirty proof of concept, not a production-quality implementation.

@a66766676
Copy link

Thanks, it works perfectly!!!!

@pshynin
Copy link

pshynin commented Sep 20, 2018

It doesn't work. Apparently, it can not find the LoadedInChildClassLoader. And I feel stupid.

https://gist.github.com/dpryden/b2bb29ee2d146901b4ae#file-classloaderleakexample-java-L124

Copy link

ghost commented Apr 22, 2020

Nice. It took me 10-15 seconds to get the leak. It seems my JVM (Graal 11) makes it take longer to leak?

@witekkij
Copy link

witekkij commented Dec 3, 2020

import java.net.URLClassLoader is never used ;P

@gogomarine
Copy link

Thanks, works like a charm

@DidierLoiseau
Copy link

To avoid dealing with paths, you could just use

byte[] classBytes = getResourceAsStream(LoadedInChildClassLoader.class.getName() + ".class").readAllBytes();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment