如何用Java创建内存泄漏

作者: Arvin Chen 分类: Java 来源: Break易站(www.breakyizhan.com)

我们知道Java有Java垃圾收集机制GC, 但是有些对象垃圾回收器没办法移除它们,因为他们还在被引用着,这就造成了Java的内存泄漏。为了更好地理解内存泄漏,我们来用Java创建内存泄漏.

如何用Java创建内存泄漏

以下是在Java中创建真正的内存泄漏(通过运行代码无法访问但仍保存在内存中的对象)的方法:

  1. 应用程序创建一个长时间运行的线程(或使用线程池来更快地泄漏)。
  2. 线程通过一个(可选的自定义)ClassLoader加载一个类。
  3. 该类分配一大块内存(例如new byte[1000000]),在静态字段中存储对它的强引用,然后将引用存储在ThreadLocal中。分配额外的内存是可选的(泄露Class实例就足够了),但它会使泄漏工作更快。
  4. 线程清除对自定义类或从其加载的ClassLoader的所有引用。
  5. 重复。

这是可行的,因为ThreadLocal保留对该对象的引用,该引用保持对其Class的引用,该引用继而保持对其ClassLoader的引用。ClassLoader反过来保持对它所加载的所有类的引用。

(在许多JVM实现中,特别是在Java 7之前,这种情况更糟糕,因为Classes和ClassLoader被直接分配到了permgen中,并且根本没有GC'd。但是,无论JVM如何处理类卸载,ThreadLocal仍然会阻止类对象被回收。)

这种模式的一个变种就是为什么应用程序容器(如Tomcat)会像筛子一样泄漏内存,如果您经常以任何方式重新部署恰巧使用ThreadLocals的应用程序。(由于应用程序容器使用了所描述的线程,并且每次重新部署应用程序时都会使用新的ClassLoader。)


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.
*
* To see it in action, copy this file to a temp directory somewhere,
* and then run:
* {@code
* javac ClassLoaderLeakExample.java
* java -cp . ClassLoaderLeakExample
* }
*
* And watch the memory grow! On my system, using JDK 1.8.0_25, I start
* getting OutofMemoryErrors within just a few seconds.
*
* 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.
*/
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.
*/
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);
}
}
}

 

本文来自:如何用Java创建内存泄漏

  •   本文标题:如何用Java创建内存泄漏 - Break易站
    转载请保留页面地址:https://www.breakyizhan.com/java/3547.html
      微信返利机器人
      免费:淘宝,京东,拼多多优惠券
      腾讯,爱奇艺,优酷的VIP视频免费解析,免费看
      即刻扫描二维码,添加微信机器人!

    发表笔记

    电子邮件地址不会被公开。 必填项已用*标注