zoukankan      html  css  js  c++  java
  • 面试必问:手写一个内存泄漏的程序

    手写一个内存泄露的程序是面试官经常问的问题。

    造成内存泄漏,就是让运行的程序无法访问存储在内存中的对象,下面是Java实现:

    1. 创建一个长时间运行的线程(使用线程池泄露的速度更快)。
    2. 线程通过ClassLoader加载某个类(也可以用自定义ClassLoader)。
    3. 这个类分配了大量内存(例如new byte[1000000]),赋给静态字段存储对它的强引用,然后在ThreadLocal中存储对自身的引用。还可以分配额外的内存,这样泄漏的速度更快(其实只要泄漏Class实例就足够了)。
    4. 这个线程会清除所有自定义类及加载它的ClassLoader的引用。
    5. 重复执行。

    这个方法之所以奏效,是因为ThreadLocal保留了对该对象的引用,对象引用保留了对Class的引用,而Class引用又保留了对ClassLoader的引用。反过来,ClassLoader会保留通过它加载的所有类的引用。

    (在许多JVM实现中情况更糟,尤其Java 7之前版本。因为Class和ClassLoader会直接分配到permgen中,GC不进行回收)。但是,无论JVM如何处理类卸载,ThreadLocal仍然会阻止被回收的Class对象)。

    这种方案还可以变化为,频繁地重新部署碰巧用到ThreadLocal的应用程序。这时像Tomcat这样的应用程序容器会像筛子一样泄漏内存。(因为应用程序容器会像上面那样启动线程,并且每次重新部署应用程序时,都会使用新的ClassLoader)

    ClassLoaderLeakExample.java

    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);
        }
      }
    }
    
  • 相关阅读:
    angularJs中ngModel的坑
    Angular中ngModel的$render的详解
    typescript中的工具 tsd
    angula组件-通过键盘实现多选(原创)
    angular指令系列---多行文本框自动高度
    微信公众号系列 --- ionic在IOS的键盘弹出问题
    angular Jsonp的坑
    关于js的一些基本知识(类,闭包,变量)
    介绍Angular的注入服务
    2019 SDN大作业
  • 原文地址:https://www.cnblogs.com/kyoner/p/12189934.html
Copyright © 2011-2022 走看看