我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
当前回答
我可以从这里复制我的答案:在Java中导致内存泄漏的最简单方法
“在计算机科学中,当计算机程序消耗内存但无法将其释放回操作系统时,就会发生内存泄漏。”(维基百科)
简单的答案是:你不能。Java执行自动内存管理,并将释放您不需要的资源。你无法阻止这种情况的发生。它将始终能够释放资源。在具有手动内存管理的程序中,这是不同的。可以使用malloc()在C中获得一些内存。要释放内存,您需要malloc返回的指针并对其调用free()。但是,如果您不再拥有指针(被覆盖或超过生存期),那么很遗憾,您无法释放此内存,因此会出现内存泄漏。
到目前为止,所有其他答案在我的定义中都不是真正的内存泄漏。他们的目标都是快速用毫无意义的东西填满记忆。但在任何时候,您仍然可以取消引用创建的对象,从而释放内存-->无泄漏。尽管我不得不承认,acconrad的答案非常接近,因为他的解决方案实际上是通过强制垃圾收集器进入一个无休止的循环来“崩溃”垃圾收集器)。
长时间的答案是:通过使用JNI为Java编写库,可以获得内存泄漏,JNI可以进行手动内存管理,从而产生内存泄漏。如果调用此库,Java进程将泄漏内存。或者,JVM中可能存在bug,从而导致JVM丢失内存。JVM中可能存在bug,甚至可能存在一些已知的bug,因为垃圾收集并不是那么简单,但它仍然是一个bug。根据设计,这是不可能的。您可能需要一些受此类错误影响的Java代码。很抱歉,我不知道,而且在下一个Java版本中,它可能不再是一个bug。
其他回答
这里有一个非常简单的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));
}
}
}
创建一个静态映射并不断添加硬引用。这些永远不会被垃圾收集。
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); }
}
什么是内存泄漏:
这是由错误或不良设计引起的。这是在浪费记忆。随着时间的推移,情况会变得更糟。垃圾收集器无法清理它。
典型示例:
对象缓存是一个很好的起点,可以让事情变得一团糟。
private static final Map<String, Info> myCache = new HashMap<>();
public void getInfo(String key)
{
// uses cache
Info info = myCache.get(key);
if (info != null) return info;
// if it's not in cache, then fetch it from the database
info = Database.fetch(key);
if (info == null) return null;
// and store it in the cache
myCache.put(key, info);
return info;
}
您的缓存不断增长。很快整个数据库就被吸进了内存。更好的设计使用LRUMap(仅将最近使用的对象保存在缓存中)。
当然,你可以让事情变得更加复杂:
使用ThreadLocal构造。添加更复杂的参考树。或由第三方库引起的泄漏。
经常发生的情况:
如果此Info对象引用了其他对象,则这些对象也引用了其他的对象。在某种程度上,您也可以认为这是某种内存泄漏(由糟糕的设计导致)。
内存泄漏是一种资源泄漏,当计算机程序错误地管理内存分配,导致不再需要的内存无法释放时,就会发生这种情况=>维基百科定义
这是一种相对基于上下文的主题,你可以根据自己的喜好创建一个主题,只要未使用的引用永远不会被客户使用,但仍然存在。
第一个例子应该是一个自定义堆栈,而不取消有效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中的内存泄漏不是典型的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的意外引用所消耗。