如何在java创建内存泄漏?

我刚接受了一次面试,我被要求用Java创建一个内存泄漏。

不用说,我觉得很愚蠢,甚至不知道如何开始创建一个。

一个例子是什么?

高分回答:

下面是在纯 Java 中创建真实内存泄漏(通过运行代码无法访问的对象但仍存储在内存中)的好方法:

1.该应用程序创建一个长运行的线程(或使用螺纹池泄漏甚至更快)。

2.线程通过(可选自定义)加载类。ClassLoader

3.类分配了大量内存(例如),在静态字段中存储对它的强引用,然后将引用存储在 a 中。分配额外的内存是可选的(泄漏类实例就足够了),但它会使泄漏工作得更快。new byte[1000000]ThreadLocal

  1. 该应用程序清除了自定义类或其加载的所有引用。ClassLoader
  2. 重复。

由于甲骨文的 JDK 中实现了此方法,这会产生内存泄漏:ThreadLocal

每个字段都有一个私有字段,实际上存储线程本地值。ThreadthreadLocals 此地图中的每个键都是对对象的弱引用,因此该对象被垃圾收集后,其条目将从地图中删除。ThreadLocalThreadLocal 但每个值都是一个强有力的参考,因此,当一个值(直接或间接)指向其关键对象时,只要线程保持生命,该对象既不会被垃圾收集,也不会从地图上删除。ThreadLocal 在此示例中,强引用链看起来像这样:

Thread对象→映射示例类→示例类→静态场→对象的示例类→实例。threadLocalsThreadLocalThreadLocal

(在创建泄漏方面,该漏洞并没有真正发挥作用,它只是由于此附加的参考链而使泄漏变得更糟:示例类→ →它加载的所有类。在许多 JVM 实现中,情况更糟,尤其是在 Java 7 之前,因为类和 s 被直接分配到永久基因中,并且根本不收集垃圾。ClassLoaderClassLoaderClassLoader

这种模式的一个变化是,为什么应用程序容器(如Tomcat)可以泄漏内存像筛子,如果你经常重新部署的应用程序,碰巧使用s,以某种方式指向自己。这可能发生在许多微妙的原因,往往很难调试和/或修复。ThreadLocal

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:o查看它的运行情况,将此文件复制到某个临时目录中,然后跑
 * <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.看着记忆成长!在我的系统上,使用jdk1.8.0\u25
*只需几秒钟就可以摆脱内存错误
 *
 * <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.
 这个类是使用一些Java8特性实现的,主要用于
*方便进行I/O。相同的基本机制适用于任何版本
*从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();
    }
  }

  /**
   * 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.
   构造一个新的类加载器,加载一个类,
*然后丢弃对它们的任何引用。理论上,应该有
*没有GC影响,因为没有引用可以转义这个方法!但是在
*练习这个会像筛子一样漏掉记忆。
   */
  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);
    }
  }
}

复制代码

简单的说: 不断创建对象。

高分回答:

静态字段保持对象引用[特别是最终字段]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}
复制代码

在长字符串上调用字符串 String.intern()

String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();
复制代码

(未关闭)开放流(文件、网络等)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}
复制代码

未关闭的连接

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}
复制代码

JVM 垃圾收集器无法到达的区域,例如通过本地方法分配的内存

在 Web 应用程序中,某些对象存储在应用范围中,直到应用程序被明确停止或删除。

getServletContext().setAttribute("SOME_MAP", map);
复制代码

不正确或不适当的 JVM 选项,例如 IBM JDK 上的选项,可防止未使用的类垃圾收集noclassgc

参见IBM jdk 设置。

原创文章,作者:睿达君,如若转载,请注明出处:https://zrrd.net.cn/907.html

发表评论

登录后才能评论
咨询电话
联系电话:0451-81320577

地址:哈尔滨市松北区中小企业总部基地13F

微信咨询
微信咨询
QQ咨询
分享本页
返回顶部