手写一个内存泄露的程序是面试官常常问的问题。java
形成内存泄漏,就是让运行的程序没法访问存储在内存中的对象,下面是Java实现:面试
这个方法之因此奏效,是由于ThreadLocal保留了对该对象的引用,对象引用保留了对Class的引用,而Class引用又保留了对ClassLoader的引用。反过来,ClassLoader会保留经过它加载的全部类的引用。ide
(在许多JVM实现中状况更糟,尤为Java 7以前版本。由于Class和ClassLoader会直接分配到permgen中,GC不进行回收)。可是,不管JVM如何处理类卸载,ThreadLocal仍然会阻止被回收的Class对象)。this
这种方案还能够变化为,频繁地从新部署碰巧用到ThreadLocal的应用程序。这时像Tomcat这样的应用程序容器会像筛子同样泄漏内存。(由于应用程序容器会像上面那样启动线程,而且每次从新部署应用程序时,都会使用新的ClassLoader)spa
ClassLoaderLeakExample.java.net
import java.io.IOException; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.Path; /** * ClassLoader泄漏演示 * * <p>要查看实际运行效果,请将此文件复制到某个临时目录, * 而后运行: * <pre>{@code * javac ClassLoaderLeakExample.java * java -cp .ClassLoaderLeakExample * }</pre> * * <p>能够看到内存不断增长!在个人系统上,使用JDK 1.8.0_25,开始 * 短短几秒钟就收到了OutofMemoryErrors * * <p>这个类用到了一些Java 8功能,主要用于 * I/O 操做一样的原理能够适用于 * Java 1.2之后的任何Java版本 */ 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(); } } /** * 线程的实现只是循环调用 * {@link #loadAndDiscard()} */ 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; } } } } /** * 这是一个简单的ClassLoader实现,只能加载一个类 * 即LoadedInChildClassLoader类.这里须要解决一些麻烦 * 必须确保每次获得一个新的类 * (而非系统class loader提供的 * 重用类).若是此子类所在JAR文件不在系统的classpath中, * 不须要这么麻烦. */ 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方法会建立一个新的ClassLoader, 加载一个类, * 而后丢弃对它们的全部引用.从理论上讲,应该不会影响GC * 由于没有引用能够逃脱该方法! 但实际上, * 结果会像筛子同样泄漏内存. */ static void loadAndDiscard() throws Exception { ClassLoader childClassLoader = new ChildOnlyClassLoader(); Class<?> childClass = Class.forName( LoadedInChildClassLoader.class.getName(), true, childClassLoader); childClass.newInstance(); // 该方法返回时,将没法访问 // childClassLoader或childClass的引用, // 可是这些对象仍会成为GC Root! } /** * 一个看起来人畜无害的类,没有作什么特别的事情. */ public static final class LoadedInChildClassLoader { // 获取一些bytes.对于泄漏不是必需的, // 只是让效果出得更快一些. // 注意:这里开始真正泄露内存,这些bytes // 每次迭代都为这个final静态字段建立了! static final byte[] moreBytesToLeak = new byte[1024 * 1024 * 10]; private static final ThreadLocal<LoadedInChildClassLoader> threadLocal = new ThreadLocal<>(); public LoadedInChildClassLoader() { // 在ThreadLocal中存储对这个类的引用 threadLocal.set(this); } } }