手寫一個內存泄露的程序是面試官常常問的問題。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); } } }