我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
当前回答
下面将有一个不明显的Java泄漏案例,除了被遗忘的侦听器、静态引用、哈希图中的伪/可修改键,或者只是线程被卡住而没有机会结束其生命周期的标准案例之外。
File.deleteOnExit()-总是泄漏字符串,如果字符串是子字符串,则泄漏更严重(底层的char[]也泄漏)-在Java 7中,子字符串也会复制char[],因此后者不适用@丹尼尔,不过不需要投票。
我将集中讨论线程,以展示非托管线程的危险性,甚至不希望触及摆动。
Runtime.addShutdownHook,不删除。。。然后,即使使用removeShutdownHook,由于ThreadGroup类中关于未启动线程的错误,它也可能无法被收集,从而有效地泄漏了ThreadGroup。JGroup在GossipRouter中有漏洞。创建一个线程,但不是启动它,它属于与上面相同的类别。创建线程继承ContextClassLoader和AccessControlContext,加上ThreadGroup和任何InheritedThreadLocal,所有这些引用都是潜在的泄漏,以及类加载器加载的所有类和所有静态引用,以及ja-ja。这种效果在整个j.u.c.Executor框架中尤其明显,该框架具有超简单的ThreadFactory接口,但大多数开发人员对潜在的危险一无所知。此外,许多库确实会根据请求启动线程(太多行业流行的库)。ThreadLocal缓存;这些在很多情况下都是邪恶的。我相信每个人都看到过很多基于ThreadLocal的简单缓存,但坏消息是:如果线程在上下文ClassLoader的生命周期中继续运行超过预期,这是一个非常好的小泄漏。除非确实需要,否则不要使用ThreadLocal缓存。当ThreadGroup本身没有线程,但仍保留子ThreadGroups时,调用ThreadGroup.destroy()。一个严重的泄漏,将阻止ThreadGroup从其父级中删除,但所有子级都无法枚举。使用WeakHashMap和值(in)直接引用键。如果没有堆转储,这很难找到。这适用于可能将硬引用保留回受保护对象的所有扩展弱/软引用。将java.net.URL与HTTP(S)协议一起使用,并从(!)加载资源。这一个是特殊的,KeepAliveCache在系统ThreadGroup中创建了一个新线程,该线程泄漏了当前线程的上下文类加载器。当不存在活动线程时,线程会在第一个请求时创建,因此您可能会幸运,或者只是泄漏。泄漏在Java7中已经修复,创建线程的代码正确地删除了上下文类加载器。创建类似线程的情况很少(如ImageFetcher,也已修复)。使用充气器InputStream在构造函数(例如PNGImageDecoder)中传递新的java.util.zip充气器(),而不调用充气器的end()。好吧,如果你只传递一个新的构造函数,就没有机会。。。是的,如果将其作为构造函数参数手动传递,则对流调用close()不会关闭充气机。这不是真正的泄漏,因为它将由终结器释放。。。当它认为有必要时。直到那一刻,它会严重消耗本地内存,导致Linux oom_killer肆无忌惮地终止进程。主要的问题是,在Java中完成是非常不可靠的,G1使其更糟,直到7.0.2。故事的寓意:尽快释放本土资源;终结器太差了。与java.util.zip.Deflater的情况相同。这一情况更糟,因为Deflater在java中需要内存,即总是使用15位(最大值)和8个内存级别(最大值为9)来分配数百KB的本地内存。幸运的是,Deflater没有被广泛使用,据我所知,JDK没有任何误用。如果手动创建放气器或充气器,请始终调用end()。最后两种方法中最棒的一点是:您无法通过常规的分析工具找到它们。
(我可以根据要求再添加一些我遇到的时间浪费者。)
祝你好运,保持安全;泄漏是邪恶的!
其他回答
创建一个静态映射并不断添加硬引用。这些永远不会被垃圾收集。
public class Leaker {
private static final Map<String, Object> CACHE = new HashMap<String, Object>();
// Keep adding until failure.
public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}
也许通过JNI使用外部本机代码?
使用纯Java,这几乎是不可能的。
但这是一种“标准”类型的内存泄漏,即您无法再访问内存,但它仍然属于应用程序。相反,您可以保留对未使用对象的引用,或者打开流而不关闭它们。
内存泄漏是一种资源泄漏,当计算机程序错误地管理内存分配,导致不再需要的内存无法释放时,就会发生这种情况=>维基百科定义
这是一种相对基于上下文的主题,你可以根据自己的喜好创建一个主题,只要未使用的引用永远不会被客户使用,但仍然存在。
第一个例子应该是一个自定义堆栈,而不取消有效Java第6项中过时的引用。
当然,只要你愿意,还有很多,但如果我们看看Java内置类,它可能是
子列表()
让我们检查一些超级愚蠢的代码来产生泄漏。
public class MemoryLeak {
private static final int HUGE_SIZE = 10_000;
public static void main(String... args) {
letsLeakNow();
}
private static void letsLeakNow() {
Map<Integer, Object> leakMap = new HashMap<>();
for (int i = 0; i < HUGE_SIZE; ++i) {
leakMap.put(i * 2, getListWithRandomNumber());
}
}
private static List<Integer> getListWithRandomNumber() {
List<Integer> originalHugeIntList = new ArrayList<>();
for (int i = 0; i < HUGE_SIZE; ++i) {
originalHugeIntList.add(new Random().nextInt());
}
return originalHugeIntList.subList(0, 1);
}
}
实际上,还有另一个技巧,我们可以利用HashMap的查找过程,使用HashMap造成内存泄漏。实际上有两种类型:
hashCode()始终相同,但equals()不同;使用随机hashCode()和equals()始终为true;
Why?
hashCode()->bucket=>equals()来定位该对
我打算先提到substring(),然后再提到subList(),但这个问题似乎已经解决了,因为它的源代码在JDK8中。
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
Java内存泄漏的一个例子是当忘记调用ResultSets close方法时导致的MySQL内存泄漏错误。例如:
while(true) {
ResultSet rs = database.select(query);
...
// going to next step of loop and leaving resultset without calling rs.close();
}
这里有一个在纯Java中创建真正的内存泄漏(运行代码无法访问但仍存储在内存中的对象)的好方法:
应用程序创建一个长时间运行的线程(或者使用线程池更快地泄漏)。线程通过(可选的自定义)ClassLoader加载类。该类分配一大块内存(例如新字节[10000000]),在静态字段中存储对它的强引用,然后在ThreadLocal中存储对自身的引用。分配额外的内存是可选的(泄漏类实例就足够了),但这会使泄漏工作得更快。应用程序清除对自定义类或从中加载该类的ClassLoader的所有引用。重复
由于ThreadLocal在Oracle的JDK中的实现方式,这会造成内存泄漏:
每个线程都有一个私有字段threadLocals,它实际上存储线程本地值。此映射中的每个键都是对ThreadLocal对象的弱引用,因此在ThreadLocal对象被垃圾收集后,其条目将从映射中删除。但每个值都是一个强引用,因此当一个值(直接或间接)指向作为其键的ThreadLocal对象时,只要线程存在,该对象既不会被垃圾收集,也不会从映射中删除。
在本例中,强引用链如下所示:
线程对象→ threadLocals映射→ 示例类的实例→ 示例类→ 静态ThreadLocal字段→ ThreadLocal对象。
(ClassLoader在创建泄漏时并没有真正发挥作用,它只是因为这个额外的引用链而使泄漏变得更糟:example类→ 类加载器→ 它加载的所有类。在许多JVM实现中,尤其是在Java7之前,情况更糟,因为类和ClassLoader被直接分配到permagen中,根本不会被垃圾收集。)
这种模式的一个变体是,如果您经常重新部署碰巧使用ThreadLocal的应用程序,而这些应用程序在某种程度上指向自己,那么应用程序容器(如Tomcat)会像筛子一样泄漏内存。这种情况可能有许多微妙的原因,并且通常很难调试和/或修复。
更新:由于很多人一直在要求它,这里有一些示例代码显示了这种行为。