我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
当前回答
这里有一个非常简单的Java程序,它将耗尽空间
public class OutOfMemory {
public static void main(String[] arg) {
List<Long> mem = new LinkedList<Long>();
while (true) {
mem.add(new Long(Long.MAX_VALUE));
}
}
}
其他回答
对前面的答案有一点改进(为了更快地生成内存泄漏),就是使用从大型XML文件加载的DOM文档实例。
也许通过JNI使用外部本机代码?
使用纯Java,这几乎是不可能的。
但这是一种“标准”类型的内存泄漏,即您无法再访问内存,但它仍然属于应用程序。相反,您可以保留对未使用对象的引用,或者打开流而不关闭它们。
JDK 1.7之前内存泄漏的实时示例:
假设您读取了一个包含1000行文本的文件,并将其保存在String对象中:
String fileText = 1000 characters from file
fileText = fileText.subString(900, fileText.length());
在上面的代码中,我最初读取了1000个字符,然后执行了子字符串,只获得最后100个字符。现在,fileText应该只引用100个字符,所有其他字符都应该被垃圾收集,因为我丢失了引用,但是在JDK1.7之前,substring函数间接引用了最后100个字符的原始字符串,并阻止了整个字符串的垃圾收集,而整个1000个字符将一直保存在内存中,直到您丢失了对子字符串的引用。
您可以创建一个类似于上述的内存泄漏示例。
保存对象引用的静态字段(尤其是最终字段)
class MemorableClass {
static final ArrayList list = new ArrayList(100);
}
(未关闭)开放流(文件、网络等)
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设置。
Java中的内存泄漏不是典型的C/C++内存泄漏。
要了解JVM的工作原理,请阅读了解内存管理。
基本上,重要的部分是:
标记和扫描模型JRockit JVM使用标记和清除垃圾收集模型执行整个堆的垃圾收集。标记和扫描垃圾收集包括两个阶段,标记阶段和扫描阶段。在标记阶段,可以从Java访问的所有对象线程、本机句柄和其他根源标记为活动的,如以及可从这些对象访问的对象,等等向前地此过程识别并标记所有静止的对象使用,其余的可以被视为垃圾。在扫描阶段,将遍历堆以查找活动对象。这些差距记录在免费列表中可用于新对象分配。JRockit JVM使用标记和扫描的两个改进版本模型一种是同时进行标记和扫描,另一种是平行标记和扫描。你也可以将这两种策略结合起来例如主要是并发标记和并行扫描。
因此,在Java中创建内存泄漏;最简单的方法是创建一个数据库连接,做一些工作,而不是Close();然后在保持范围内的同时生成新的数据库连接。例如,这在循环中并不难做到。如果您有一个工作人员从队列中拉出并推送到数据库,那么您可以通过忘记Close()连接或在不需要时打开连接等方式轻松创建内存泄漏。
最终,您将通过忘记Close()连接来消耗已分配给JVM的堆。这将导致JVM垃圾疯狂收集;最终导致java.lang.OutOfMemoryError:java堆空间错误。应该注意,该错误可能并不意味着存在内存泄漏;这可能意味着你没有足够的记忆;例如,Cassandra和Elasticsearch等数据库可能会抛出错误,因为它们没有足够的堆空间。
值得注意的是,所有GC语言都是如此。以下是我作为SRE工作的一些例子:
Node.js使用Redis作为队列;开发团队每12小时创建一次新连接,但忘记关闭旧连接。最终,节点是OOMd,因为它消耗了所有内存。去吧(我犯了这个罪);使用JSON.Unmarshal解析大型JSON文件,然后通过引用传递结果并保持其打开状态。最终,这导致整个堆被我打开以解码JSON的意外引用所消耗。