面試必問:手寫一個內存泄漏的程序

手寫一個內存泄露的程序是面試官常常問的問題。java

形成內存泄漏,就是讓運行的程序沒法訪問存儲在內存中的對象,下面是Java實現:面試

  1. 建立一個長時間運行的線程(使用線程池泄露的速度更快)。
  2. 線程經過ClassLoader加載某個類(也能夠用自定義ClassLoader)。
  3. 這個類分配了大量內存(例如new byte[1000000]),賦給靜態字段存儲對它的強引用,而後在ThreadLocal中存儲對自身的引用。還能夠分配額外的內存,這樣泄漏的速度更快(其實只要泄漏Class實例就足夠了)。
  4. 這個線程會清除全部自定義類及加載它的ClassLoader的引用。
  5. 重複執行。

這個方法之因此奏效,是由於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);
    }
  }
}
相關文章
相關標籤/搜索