我刚接受采访,并被要求用Java创建 内存泄漏 。 毋庸置疑,我觉得自己很傻,甚至不知道如何开始创建一个。
一个例子是什么?
答案 0 :(得分:2147)
这是在纯Java中创建真正的内存泄漏(通过运行代码但仍然存储在内存中无法访问的对象)的好方法:
new byte[1000000]
),在静态字段中存储对它的强引用,然后在ThreadLocal中存储对自身的引用。分配额外的内存是可选的(泄漏Class实例就足够了),但它会使泄漏工作更快。这是有效的,因为ThreadLocal保留对该对象的引用,该对象保持对其Class的引用,而Class又保持对其ClassLoader的引用。反过来,ClassLoader保留对它已加载的所有类的引用。
(在许多JVM实现中更糟糕,特别是在Java 7之前,因为Classes和ClassLoader直接分配到permgen并且根本就没有GC。但是,无论JVM如何处理类卸载,ThreadLocal都会仍然阻止回收Class对象。)
此模式的一个变体是,如果您经常重新部署碰巧以任何方式使用ThreadLocals的应用程序,那么应用程序容器(如Tomcat)可能会像筛子那样泄漏内存。 (由于应用程序容器使用了所描述的线程,每次重新部署应用程序时都会使用新的ClassLoader。)
更新:由于很多人不断要求,here's some example code that shows this behavior in action。
答案 1 :(得分:1147)
静态字段保持对象引用[esp final field]
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 settings。
答案 2 :(得分:433)
一件简单的事情是使用不正确(或不存在)hashCode()
或equals()
的HashSet,然后继续添加“重复”。不应该忽略重复,而是只会增长,你将无法删除它们。
如果你想让这些坏键/元素闲逛,你可以使用像
这样的静态字段class BadKey {
// no hashCode or equals();
public final String key;
public BadKey(String key) { this.key = key; }
}
Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
答案 3 :(得分:258)
下面将会有一个非显而易见的案例,其中Java泄漏,除了被遗忘的侦听器的标准情况,静态引用,哈希映射中的虚假/可修改键,或只是线程卡住而没有任何机会结束其生命周期。
File.deleteOnExit()
- 总是泄漏字符串,char[]
,因此后者不适用; @Daniel,不需要投票。我将专注于线程,以显示大多数非托管线程的危险,不希望甚至触摸挥杆。
Runtime.addShutdownHook
并且不会删除...然后甚至使用removeShutdownHook由于ThreadGroup类中有关未启动线程的错误而无法收集它,有效地泄漏了ThreadGroup。 JGroup在GossipRouter中有漏洞。
Thread
创建但未启动,与上述类别相同。
创建一个线程继承ContextClassLoader
和AccessControlContext
,加上ThreadGroup
和任何InheritedThreadLocal
,所有这些引用都是潜在的泄漏,以及整个类由类加载器和所有静态引用加载,以及ja-ja。整个j.u.c.Executor框架具有超级简单的ThreadFactory
接口,但效果特别明显,但大多数开发人员都不知道潜伏的危险。此外,许多库都会根据请求启动线程(太多行业流行的库)。
ThreadLocal
缓存;在许多情况下这些都是邪恶的。我相信每个人都已经看到了很多基于ThreadLocal的简单缓存,这也是坏消息:如果线程持续超过预期生命的上下文ClassLoader,它是一个纯粹的小泄漏。除非确实需要,否则不要使用ThreadLocal缓存。
当ThreadGroup本身没有线程时调用ThreadGroup.destroy()
,但它仍然保留子ThreadGroups。一个错误的泄漏会阻止ThreadGroup从其父级中删除,但所有子级都变得不可枚举。
使用WeakHashMap并且值(in)直接引用该键。没有堆转储,这是一个很难找到的。这适用于所有可能将硬引用保留回受保护对象的扩展Weak/SoftReference
。
将java.net.URL
与HTTP(S)协议一起使用并从(!)加载资源。这个是特殊的,KeepAliveCache
在系统ThreadGroup中创建一个新线程,它泄漏当前线程的上下文类加载器。当没有活动线程存在时,线程是在第一个请求时创建的,所以你可能会幸运或只是泄漏。 泄漏已经在Java 7中得到修复,并且创建线程的代码正确地删除了上下文类加载器。还有更多的情况(像ImageFetcher ,也已修复 em>)创建类似的线程。
使用InflaterInputStream
在构造函数中传递new java.util.zip.Inflater()
(例如PNGImageDecoder
)而不是调用inflater的end()
。好吧,如果你只使用new
传入构造函数,那就没有机会了......是的,如果它作为构造函数参数手动传递,则在流上调用close()
不会关闭inflater。这不是真正的泄漏,因为它会被终结者释放......当它认为有必要时。直到那一刻它吃掉本机内存如此糟糕,它可能导致Linux oom_killer肆无忌惮地杀死进程。主要问题是Java中的最终化非常不可靠,G1在7.0.2之前变得更糟。故事的道德:尽快释放本地资源;终结者太穷了。
与java.util.zip.Deflater
相同的情况。这个更糟糕,因为Deflater在Java中需要内存,即总是使用15位(最大值)和8个内存级别(9个最大值)来分配几百KB的本机内存。幸运的是,Deflater
并未被广泛使用,据我所知,JDK不包含任何错误。如果您手动创建end()
或Deflater
,请务必致电Inflater
。最后两个中最好的部分:您无法通过常规的分析工具找到它们。
(我可以根据要求添加更多时间浪费。)
祝你好运,保持安全;泄漏是邪恶的!答案 4 :(得分:181)
这里的大多数例子都“过于复杂”。他们是边缘案件。使用这些示例,程序员犯了一个错误(比如不重新定义equals / hashcode),或者被JVM / JAVA(带静态类的类加载)的角落情况所咬。我认为这不是面试官想要的例子,甚至不是最常见的例子。
但内存泄漏确实存在更简单的情况。垃圾收集器只释放不再引用的内容。我们作为Java开发人员并不关心内存。我们在需要时分配它并让它自动释放。细
但任何长期存在的应用程序都倾向于共享状态。它可以是任何东西,静力学,单身......通常非平凡的应用程序往往会制作复杂的对象图。只是忘记设置null或更多的引用经常忘记从集合中删除一个对象就足以使内存泄漏。
当然,如果处理不当,所有类型的侦听器(如UI侦听器),缓存或任何长期共享状态都会产生内存泄漏。应该理解的是,这不是Java角落案例,也不是垃圾收集器的问题。这是一个设计问题。我们设计为一个长期存在的对象添加一个监听器,但是当不再需要时我们不会删除监听器。我们缓存对象,但我们没有策略将它们从缓存中删除。
我们可能有一个复杂的图形来存储计算所需的先前状态。但是之前的状态本身与之前的状态有关,依此类推。
就像我们必须关闭SQL连接或文件一样。我们需要设置对null的正确引用并从集合中删除元素。我们将有适当的缓存策略(最大内存大小,元素数量或计时器)。允许侦听器得到通知的所有对象都必须同时提供addListener和removeListener方法。当这些通知者不再使用时,他们必须清除他们的听众名单。
内存泄漏确实是可能的,并且完全可以预测。无需特殊语言功能或角落案例。内存泄漏可能是某些事情可能缺失甚至是设计问题的指标。
答案 5 :(得分:150)
答案完全取决于面试官的想法。
在实践中是否有可能使Java泄漏?当然是这样,其他答案中有很多例子。
但可能有多个元问题被提出?
我正在阅读你的元问题:“在这次访谈中我能用到什么答案”。因此,我将专注于面试技巧而不是Java。我相信你更有可能重复在面试中不知道问题答案的情况,而不是在需要知道如何使Java泄漏的地方。所以,希望这会有所帮助。
面试时你可以开发的最重要技能之一就是学会积极倾听问题并与面试官合作以提取他们的意图。这不仅可以让你以他们想要的方式回答他们的问题,而且还表明你有一些重要的沟通技巧。当谈到许多同样有才华的开发人员之间的选择时,我会聘请那些在每次回复之前倾听,思考和理解的人。
答案 6 :(得分:126)
如果你不理解JDBC,以下是一个非常毫无意义的例子。或者至少JDBC希望开发人员在丢弃它们或丢失对它们的引用之前关闭Connection
,Statement
和ResultSet
个实例,而不是依赖于finalize
的实现。
void doWork()
{
try
{
Connection conn = ConnectionFactory.getConnection();
PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
ResultSet rs = stmt.executeQuery();
while(rs.hasNext())
{
... process the result set
}
}
catch(SQLException sqlEx)
{
log(sqlEx);
}
}
上面的问题是Connection
对象没有关闭,因此物理连接将保持打开状态,直到垃圾收集器出现并看到它无法访问。 GC将调用finalize
方法,但是有些JDBC驱动程序没有实现finalize
,至少与实现Connection.close
的方式不同。由此产生的行为是,由于收集了无法访问的对象而将回收内存,因此可能无法回收与Connection
对象关联的资源(包括内存)。
在Connection
的{{1}}方法没有清理所有内容的情况下,实际上可能会发现与数据库服务器的物理连接将持续几个垃圾收集周期,直到数据库服务器最终会发现连接不存在(如果存在),应该关闭。
即使JDBC驱动程序要实现finalize
,也有可能在最终确定期间抛出异常。由此产生的行为是,与现在“休眠”对象关联的任何内存都不会被回收,因为finalize
只能被调用一次。
上述在对象终结期间遇到异常的情况与另一个可能导致内存泄漏的情况有关 - 对象复活。对象复活通常是通过从另一个对象创建对象的强引用来有意识地完成的。当对象复活被滥用时,它将导致内存泄漏以及其他内存泄漏源。
你可以想出更多的例子 - 比如
finalize
实例,您只添加到列表而不是从中删除(尽管您应该删除不再需要的元素),或者List
或Socket
,但在不再需要时关闭它们(类似于涉及File
类的上述示例)。答案 7 :(得分:113)
可能是潜在内存泄漏的最简单示例之一,以及如何避免它,是ArrayList.remove(int)的实现:
public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
elementData[--size] = null; // (!) Let gc do its work
return oldValue;
}
如果你自己实现它,你会想到清除不再使用的数组元素(elementData[--size] = null
)吗?那个引用可能会让一个巨大的物体活着......
答案 8 :(得分:66)
每当你保持对不再需要的对象的引用时,就会发生内存泄漏。有关内存泄漏如何在Java中表现出来以及您可以采取哪些措施的示例,请参阅Handling memory leaks in Java programs。
答案 9 :(得分:49)
您可以使用 sun.misc.Unsafe 类进行内存泄漏。实际上,此服务类用于不同的标准类(例如,在 java.nio 类中)。 您无法直接创建此类的实例,但您可以使用反射来执行此操作。
代码无法在Eclipse IDE中编译 - 使用命令javac
进行编译(在编译期间,您将收到警告)
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class TestUnsafe {
public static void main(String[] args) throws Exception{
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field f = unsafeClass.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
System.out.print("4..3..2..1...");
try
{
for(;;)
unsafe.allocateMemory(1024*1024);
} catch(Error e) {
System.out.println("Boom :)");
e.printStackTrace();
}
}
}
答案 10 :(得分:43)
我可以从这里复制我的答案: Easiest way to cause memory leak in Java?
“当计算机程序消耗内存但无法将其释放回操作系统时,会发生计算机科学(或泄漏,在此上下文中)的内存泄漏。” (维基百科)
简单的答案是:你做不到。 Java执行自动内存管理,并将释放您不需要的资源。你不能阻止这种情况发生。它总是能够释放资源。在具有手动内存管理的程序中,这是不同的。你可以使用malloc()在C中获得一些内存。要释放内存,您需要malloc返回的指针并在其上调用free()。但是如果你不再使用指针(覆盖或超过生命周期),那么很遗憾你无法释放这些内存,从而导致内存泄漏。
到目前为止,所有其他答案都在我的定义中并非真正的内存泄漏。它们都旨在快速填充无意义的内存。但是在任何时候你仍然可以取消引用你创建的对象,从而释放内存 - >没有泄漏。 acconrad's answer非常接近,但我必须承认,因为他的解决方案实际上是通过强制它在无限循环中“崩溃”垃圾收集器。
答案很长:您可以通过使用JNI编写Java库来获取内存泄漏,JNI可以进行手动内存管理,从而导致内存泄漏。如果你调用这个库,你的java进程将泄漏内存。或者,您可能在JVM中有错误,因此JVM会丢失内存。 JVM中可能存在bug,甚至可能有一些已知的bug,因为垃圾收集不是那么简单,但它仍然是一个bug。按设计这是不可能的。你可能会要求一些受这种bug影响的java代码。对不起,我不知道一个,不管怎样,在下一个Java版本中它可能不再是一个bug。
答案 11 :(得分:36)
GUI代码中的一个常见示例是创建窗口小部件/组件并向某个静态/应用程序范围对象添加侦听器,然后在窗口小部件被销毁时不删除侦听器。您不仅会遇到内存泄漏,而且还会受到性能影响,因为无论您何时正在收听火灾事件,您的所有旧听众都会被调用。
答案 12 :(得分:36)
这是一个简单/险恶的http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29。
public class StringLeaker
{
private final String muchSmallerString;
public StringLeaker()
{
// Imagine the whole Declaration of Independence here
String veryLongString = "We hold these truths to be self-evident...";
// The substring here maintains a reference to the internal char[]
// representation of the original string.
this.muchSmallerString = veryLongString.substring(0, 1);
}
}
因为子字符串引用原始字符串的内部表示,所以原始字符串保留在内存中。因此,只要你有一个StringLeaker在游戏中,你就可以在内存中拥有整个原始字符串,即使你可能认为你只是坚持使用单字符字符串。
避免将不需要的引用存储到原始字符串的方法是执行以下操作:
...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...
对于增加的不良,您可能还.intern()
子字符串:
...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...
即使在丢弃StringLeaker实例之后,这样做也会将原始长字符串和派生子字符串保留在内存中。
答案 13 :(得分:35)
在任何servlet容器中运行任何Web应用程序(Tomcat,Jetty,Glassfish,等等......)。连续重新部署应用程序10次(可能只需触摸服务器的autodeploy目录中的WAR即可。
除非有人实际测试了这个,否则很可能在经过几次重新部署后你会得到一个OutOfMemoryError,因为应用程序没有注意自己清理。您甚至可以通过此测试在服务器中找到错误。
问题是,容器的生命周期比应用程序的生命周期长。您必须确保容器可能对应用程序的对象或类的所有引用都是垃圾回收。
如果只有一个引用在您的Web应用程序取消部署后仍然存在,则相应的类加载器会因此无法进行垃圾回收。
您的应用程序启动的线程,ThreadLocal变量,日志记录appender是导致类加载器泄漏的一些常见嫌疑。
答案 14 :(得分:33)
也许通过JNI使用外部本机代码?
使用纯Java,几乎是不可能的。
但这是关于“标准”类型的内存泄漏,当你不能再访问内存时,它仍归应用程序所有。您可以保留对未使用对象的引用,或者在不关闭它们的情况下打开流。
答案 15 :(得分:30)
与PermGen和XML解析相比,我有一个很好的“内存泄漏”。 我们使用的XML解析器(我不记得它是哪一个)在标记名称上做了一个String.intern(),以便更快地进行比较。 我们的一位客户最好不要将数据值存储在XML属性或文本中,而是存储为标记名,因此我们有一个文档,如:
<data>
<1>bla</1>
<2>foo</>
...
</data>
事实上,他们并没有使用数字,而是使用更长的文本ID(大约20个字符),这些ID是独一无二的,并且以每天10-15百万的速度进行。这每天产生200 MB的垃圾,这是永远不需要的,而且从来没有GCed(因为它是在PermGen)。我们将permgen设置为512 MB,因此内存异常(OOME)需要大约两天才能到达......
答案 16 :(得分:23)
我最近遇到了log4j导致的内存泄漏情况。
Log4j有一种名为Nested Diagnostic Context(NDC)的机制,它是一种区分不同来源的交错日志输出的工具。 NDC工作的粒度是线程,因此它分别区分不同线程的日志输出。
为了存储特定于线程的标记,log4j的NDC类使用一个由Thread对象本身键入的Hashtable(而不是线程id),直到NDC标记在内存中保留所有挂起的对象线程对象的内容也留在内存中。在我们的Web应用程序中,我们使用NDC标记带有请求ID的logoutput,以区分日志和单个请求。将NDC标记与线程相关联的容器也会在从请求返回响应时将其删除。问题发生在处理请求的过程中,生成了一个子线程,类似于以下代码:
pubclic class RequestProcessor {
private static final Logger logger = Logger.getLogger(RequestProcessor.class);
public void doSomething() {
....
final List<String> hugeList = new ArrayList<String>(10000);
new Thread() {
public void run() {
logger.info("Child thread spawned")
for(String s:hugeList) {
....
}
}
}.start();
}
}
因此,NDC上下文与生成的内联线程相关联。作为此NDC上下文的键的线程对象是内联线程,其中挂起了hugeList对象。因此,即使在线程完成它正在做的事情之后,对NDC上下文Hastable仍然保持对hugeList的引用,从而导致内存泄漏。
答案 17 :(得分:22)
内存泄漏是什么:
典型示例:
对象缓存是弄乱事情的好起点。
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(仅在缓存中保留最近使用的对象)。
当然,你可以让事情变得更复杂:
经常发生的事情:
如果此Info对象具有对其他对象的引用,则其他对象也会引用其他对象。在某种程度上,你也可以认为这是某种内存泄漏(由糟糕的设计引起)。
答案 18 :(得分:21)
我觉得有趣的是没有人使用内部类示例。如果你有内部课程;它固有地维护对包含类的引用。当然,从技术上讲,它不是内存泄漏,因为Java最终会将其清理干净;但这可能会导致课程比预期的更长时间。
public class Example1 {
public Example2 getNewExample2() {
return this.new Example2();
}
public class Example2 {
public Example2() {}
}
}
现在,如果你调用Example1并获得一个丢弃Example1的Example2,你将固有地拥有一个指向Example1对象的链接。
public class Referencer {
public static Example2 GetAnExample2() {
Example1 ex = new Example1();
return ex.getNewExample2();
}
public static void main(String[] args) {
Example2 ex = Referencer.GetAnExample2();
// As long as ex is reachable; Example1 will always remain in memory.
}
}
我也听说过一个谣言,如果你的变量存在的时间超过特定的时间; Java假定它将永远存在,并且如果再也无法在代码中访问它,它实际上永远不会尝试清理它。但这完全未经证实。
答案 19 :(得分:19)
创建一个静态Map并继续添加对它的硬引用。那些永远不会是GC。
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); }
}
答案 20 :(得分:17)
面试官可能正在寻找一个循环引用,如下面的代码(顺便说一下,只有在使用引用计数的非常旧的JVM中泄漏内存,这不再是这种情况)。但这是一个非常模糊的问题,因此它是展示您对JVM内存管理的理解的绝佳机会。
class A {
B bRef;
}
class B {
A aRef;
}
public class Main {
public static void main(String args[]) {
A myA = new A();
B myB = new B();
myA.bRef = myB;
myB.aRef = myA;
myA=null;
myB=null;
/* at this point, there is no access to the myA and myB objects, */
/* even though both objects still have active references. */
} /* main */
}
然后你可以解释一下,通过引用计数,上面的代码会泄漏内存。但是大多数现代JVM不再使用引用计数,大多数使用扫描垃圾收集器,实际上会收集这些内存。
接下来,您可以解释创建一个具有底层本机资源的Object,如下所示:
public class Main {
public static void main(String args[]) {
Socket s = new Socket(InetAddress.getByName("google.com"),80);
s=null;
/* at this point, because you didn't close the socket properly, */
/* you have a leak of a native descriptor, which uses memory. */
}
}
然后你可以解释这在技术上是一个内存泄漏,但实际上泄漏是由JVM中的本机代码分配底层本机资源引起的,而这些资源并没有被你的Java代码释放。
在一天结束时,使用现代JVM,您需要编写一些Java代码,以便在JVM的正常范围之外分配本机资源。
答案 21 :(得分:16)
您可以通过在该类的finalize方法中创建类的新实例来创建移动内存泄漏。如果终结器创建多个实例,则奖励积分。这是一个简单的程序,它会在几秒到几分钟之间泄漏整个堆,具体取决于您的堆大小:
class Leakee {
public void check() {
if (depth > 2) {
Leaker.done();
}
}
private int depth;
public Leakee(int d) {
depth = d;
}
protected void finalize() {
new Leakee(depth + 1).check();
new Leakee(depth + 1).check();
}
}
public class Leaker {
private static boolean makeMore = true;
public static void done() {
makeMore = false;
}
public static void main(String[] args) throws InterruptedException {
// make a bunch of them until the garbage collector gets active
while (makeMore) {
new Leakee(0).check();
}
// sit back and watch the finalizers chew through memory
while (true) {
Thread.sleep(1000);
System.out.println("memory=" +
Runtime.getRuntime().freeMemory() + " / " +
Runtime.getRuntime().totalMemory());
}
}
}
答案 22 :(得分:16)
每个人都会忘记原生代码路由。这是泄漏的简单公式:
malloc
。请勿致电free
。请记住,本机代码中的内存分配来自JVM堆。
答案 23 :(得分:15)
最近我遇到了一种更为微妙的资源泄漏。 我们通过类加载器的getResourceAsStream打开资源,并且输入流句柄没有关闭。
嗯,你可能会说,多么愚蠢。那么,有趣的是:这样,你可以泄漏底层进程的堆内存,而不是来自JVM的堆。
您需要的只是一个jar文件,其中包含一个文件,该文件将从Java代码中引用。 jar文件越大,分配的内存越快。
您可以使用以下类轻松创建此类jar:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class BigJarCreator {
public static void main(String[] args) throws IOException {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
zos.putNextEntry(new ZipEntry("resource.txt"));
zos.write("not too much in here".getBytes());
zos.closeEntry();
zos.putNextEntry(new ZipEntry("largeFile.out"));
for (int i=0 ; i<10000000 ; i++) {
zos.write((int) (Math.round(Math.random()*100)+20));
}
zos.closeEntry();
zos.close();
}
}
只需粘贴到名为BigJarCreator.java的文件中,从命令行编译并运行它:
javac BigJarCreator.java
java -cp . BigJarCreator
Etvoilà:您在当前的工作目录中找到了一个包含两个文件的jar存档。
让我们创建第二个类:
public class MemLeak {
public static void main(String[] args) throws InterruptedException {
int ITERATIONS=100000;
for (int i=0 ; i<ITERATIONS ; i++) {
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
}
System.out.println("finished creation of streams, now waiting to be killed");
Thread.sleep(Long.MAX_VALUE);
}
}
这个类基本上什么都不做,但创建了未引用的InputStream对象。这些对象将立即被垃圾收集,因此不会对堆大小有所贡献。 对于我们的示例来说,从jar文件加载现有资源非常重要,大小在这里很重要!
如果您有疑问,请尝试编译并启动上面的类,但请确保选择合适的堆大小(2 MB):
javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak
这里不会遇到OOM错误,因为没有保留引用,无论您在上面的示例中选择了多大的ITERATIONS,应用程序都将继续运行。 除非应用程序进入wait命令,否则进程的内存消耗(在顶层(RES / RSS)或进程资源管理器中可见)会增长。在上面的设置中,它将在内存中分配大约150 MB。
如果您希望应用程序安全播放,请在创建它的位置关闭输入流:
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();
并且您的过程不会超过35 MB,与迭代次数无关。
非常简单和令人惊讶。
答案 24 :(得分:15)
我认为还没有人说过这个:你可以通过覆盖finalize()方法来复活一个对象,这样finalize()就会在某处存储一个引用。垃圾收集器只会在对象上调用一次,所以在此之后对象永远不会被销毁。
答案 25 :(得分:14)
正如很多人所说的那样,资源泄漏很容易引起 - 就像JDBC示例一样。实际的内存泄漏有点困难 - 特别是如果你不依赖于JVM的破碎位来为你做这件事......
创建具有非常大的占用空间然后无法访问它们的对象的想法也不是真正的内存泄漏。如果没有什么可以访问它那么它将被垃圾收集,如果有什么东西可以访问它,那么它不是泄漏......
使用工作的一种方式 - 我不知道它是否仍然存在 - 是有一个三深的循环链。如在对象A中引用了对象B,对象B引用了对象C,而对象C引用了对象A.GC非常聪明地知道两条深链 - 如A&lt; - &gt; ; B - 如果A和B无法通过其他任何方式访问,可以安全地收集,但无法处理三向链......
答案 26 :(得分:11)
线程在终止之前不会被收集。它们用作垃圾收集的roots。它们是为数不多的仅仅通过忘记它们或清除对它们的引用而无法回收的对象之一。
考虑:终止工作线程的基本模式是设置线程看到的一些条件变量。线程可以定期检查变量并将其用作终止信号。如果变量未声明volatile
,则线程可能看不到对变量的更改,因此它不会知道终止。或者想象一下,如果某些线程想要更新共享对象,但在尝试锁定它时会死锁。
如果您只有少数线程,这些错误可能会很明显,因为您的程序将无法正常工作。如果你有一个线程池可以根据需要创建更多的线程,那么过时/卡住的线程可能不会被注意到,并且会无限累积,导致内存泄漏。线程可能会在您的应用程序中使用其他数据,因此也会阻止他们直接引用的任何数据被收集。
作为一个玩具示例:
static void leakMe(final Object object) {
new Thread() {
public void run() {
Object o = object;
for (;;) {
try {
sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {}
}
}
}.start();
}
尽可能调用System.gc()
,但传递给leakMe
的对象永远不会消亡。
(*编辑*)
答案 27 :(得分:11)
有许多不同的情况,内存会泄漏。我遇到的一个,它暴露了一个不应该在其他地方暴露和使用的地图。
public class ServiceFactory {
private Map<String, Service> services;
private static ServiceFactory singleton;
private ServiceFactory() {
services = new HashMap<String, Service>();
}
public static synchronized ServiceFactory getDefault() {
if (singleton == null) {
singleton = new ServiceFactory();
}
return singleton;
}
public void addService(String name, Service serv) {
services.put(name, serv);
}
public void removeService(String name) {
services.remove(name);
}
public Service getService(String name, Service serv) {
return services.get(name);
}
// the problematic api, which expose the map.
//and user can do quite a lot of thing from this api.
//for example, create service reference and forget to dispose or set it null
//in all this is a dangerous api, and should not expose
public Map<String, Service> getAllServices() {
return services;
}
}
// resource class is a heavy class
class Service {
}
答案 28 :(得分:10)
另一种创建潜在巨大内存泄漏的方法是保留对Map.Entry<K,V>
的{{1}}的引用。
很难评估为什么这只适用于TreeMap
,但通过查看实现,原因可能是:TreeMap
存储对其兄弟的引用,因此如果{{1}准备好收集,但是其他一些类拥有对其任何TreeMap.Entry
的引用,然后整个地图将保留在内存中。
现实场景:
想象一下,db查询返回一个大的TreeMap
数据结构。人们通常使用Map.Entry
来保留元素插入顺序。
TreeMap
如果多次调用该查询,并且对于每个查询(因此,对于返回的每个TreeMap
),您在某处保存public static Map<String, Integer> pseudoQueryDatabase();
,内存将不断增长。
考虑以下包装类:
Map
应用:
Entry
每次class EntryHolder {
Map.Entry<String, Integer> entry;
EntryHolder(Map.Entry<String, Integer> entry) {
this.entry = entry;
}
}
调用后,public class LeakTest {
private final List<EntryHolder> holdersCache = new ArrayList<>();
private static final int MAP_SIZE = 100_000;
public void run() {
// create 500 entries each holding a reference to an Entry of a TreeMap
IntStream.range(0, 500).forEach(value -> {
// create map
final Map<String, Integer> map = pseudoQueryDatabase();
final int index = new Random().nextInt(MAP_SIZE);
// get random entry from map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue().equals(index)) {
holdersCache.add(new EntryHolder(entry));
break;
}
}
// to observe behavior in visualvm
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
public static Map<String, Integer> pseudoQueryDatabase() {
final Map<String, Integer> map = new TreeMap<>();
IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
return map;
}
public static void main(String[] args) throws Exception {
new LeakTest().run();
}
}
个实例应准备好进行收集,但不会发生,因为至少有一个pseudoQueryDatabase()
存储在其他位置。
根据您的map
设置,由于Entry
,应用程序可能会在早期阶段崩溃。
您可以从此jvm
图表中看到内存如何不断增长。
散列数据结构(OutOfMemoryError
)也不会发生这种情况。
这是使用visualvm
时的图表。
解决方案?只需直接保存键/值(您可能已经这样做),而不是保存HashMap
。
我写了更广泛的基准here。
答案 29 :(得分:10)
我认为一个有效的例子可能是在线程被池化的环境中使用ThreadLocal变量。
例如,使用Servlet中的ThreadLocal变量与其他Web组件进行通信,让容器创建线程并在池中维护空闲的线程。 ThreadLocal变量如果没有被正确清理,将会存在,直到可能相同的Web组件覆盖它们的值。
当然,一旦确定,问题就可以轻松解决。
答案 30 :(得分:9)
我最近修复的一个例子是创建新的GC和Image对象,但忘记调用dispose()方法。
GC javadoc片段:
应用程序代码必须显式调用GC.dispose()方法 释放每个实例管理的操作系统资源 不再需要这些实例。这一点尤为重要 在Windows95和Windows98上,操作系统有限 可用的设备上下文数量。
图片javadoc片段:
应用程序代码必须显式调用Image.dispose()方法 释放每个实例管理的操作系统资源 那些实例不再需要了。
答案 31 :(得分:9)
面试官可能正在寻找循环参考解决方案:
public static void main(String[] args) {
while (true) {
Element first = new Element();
first.next = new Element();
first.next.next = first;
}
}
这是引用计数垃圾收集器的典型问题。然后,您会礼貌地解释JVM使用更复杂的算法,但没有此限制。
-Wes Tarle
答案 32 :(得分:8)
理论上你不能。 Java内存模型可以防止它。但是,由于必须实现Java,因此您可以使用一些注意事项。取决于你可以使用什么:
如果你可以使用原生,你可以分配你以后不会放弃的记忆。
如果没有,那么关于java的一个肮脏的小秘密并没有人知道。您可以要求不由GC管理的直接访问阵列,因此可以很容易地用于使内存泄漏。这是由DirectByteBuffer(http://download.oracle.com/javase/1.5.0/docs/api/java/nio/ByteBuffer.html#allocateDirect(int))提供的。
如果你不能使用其中任何一个,你仍然可以通过欺骗GC来造成内存泄漏。 JVM使用Generational垃圾收集实现。这意味着堆被分为几个区域:年轻人,成年人和老年人。创建的对象从年轻区域开始。随着他的使用越来越多,他逐渐成长为成年人。到达老年人区域的物体很可能不会被收集。您不能确定物体是否泄漏,如果您要求停止并清洁GC,它可能会清洁它,但是很长一段时间它会被泄漏。更多信息,请访问(http://java.sun.com/docs/hotspot/gc1.4.2/faq.html)
此外,类对象不需要GC。我可以这样做。
答案 33 :(得分:8)
我想提供有关如何使用JVM中可用的工具监视应用程序内存泄漏的建议。它没有显示如何生成内存泄漏,而是说明了如何使用最少的可用工具来检测它。
您需要首先监视Java内存消耗。
最简单的方法是使用JVM附带的jstat实用程序。
jstat -gcutil <process_id> <timeout>
它将报告每一代(年轻人,老人和老人)的内存消耗和垃圾回收时间(年轻人和完整)。
一旦发现Full Garbage Collection执行得太频繁并且花费了太多时间,就可以认为应用程序正在泄漏内存。
然后,您需要使用jmap实用程序创建内存转储:
jmap -dump:live,format=b,file=heap.bin <process_id>
然后,您需要使用Memory Analyser(例如Eclipse Memory Analyzer(MAT))来分析heap.bin文件。
MAT将分析内存并为您提供有关内存泄漏的可疑信息。
答案 34 :(得分:8)
不终止的线程(比如在其run方法中无限期地休眠)。即使我们放弃了对它的引用,它也不会被垃圾收集。您可以添加字段以使线程对象变得很大。
目前最热门的答案列出了更多的技巧,但这些似乎是多余的。
答案 35 :(得分:7)
一些建议:
通过重新部署应用程序可以“改善”上述效果;)
最近偶然发现了这个:
阅读http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5072161并链接问题以进行深入讨论。
答案 36 :(得分:7)
关于如何在Java中创建内存泄漏有很多答案,但请注意访问期间提出的要点。
&#34;如何使用Java创建内存泄漏?&#34;是一个开放式问题,其目的是评估开发人员的经验程度。
如果我问你&#34;你是否有解决Java中内存泄漏问题的经验?&#34;,你的答案很简单&#34;是&#34;。 然后我会跟进&#34;你能举例说明你在哪里解决内存泄漏问题吗?&#34;你可以给我一两个例子。
然而,当面试官问及如何用Java创建内存泄漏时?&#34;预期答案应遵循这些方针:
当开发人员没有遵循这一思路时,我会尝试引导他/她提出问题&#34;你能给我一个例子,说明Java如何泄漏内存?&#34;,然后是&#34;您是否必须修复Java中的任何内存泄漏?&#34;
请注意,我不要求提供有关如何在Java中泄漏内存的示例。那太傻了。谁会对能够有效编写泄漏内存的代码的开发人员感兴趣?
答案 37 :(得分:7)
从finalize方法中抛出未处理的异常。
答案 38 :(得分:7)
如果最大堆大小是X. Y1 .... Yn没有实例所以,总内存=实例数X每个实例的字节数。如果X1 ...... Xn是每个实例的字节数。那么总内存( M)= Y1 * X1 + ..... + Yn * Xn。因此,如果M> X,则它超过堆空间。以下可能是代码中的问题 1.使用更多实例变量然后使用本地变量。 2.每次创建实例而不是汇集对象。 3.不按需创建对象。 4.在操作完成后使对象引用为null。再次,在程序中需要时重新创建。
答案 39 :(得分:7)
我在java中看到的大多数内存泄漏都与进程不同步有关。
进程A通过TCP与B进行通信,并告诉进程B创建一些东西。 B向资源发出一个ID,比如说432423,A存储在一个对象中并在与B交谈时使用。在某些时候,A中的对象被垃圾收集回收(可能是由于一个bug),但A从不告诉B(也许是另一个错误。)
现在A不再具有它在B的RAM中创建的对象的ID,并且B不知道A没有对该对象的更多引用。实际上,该对象已泄露。
答案 40 :(得分:6)
java中的内存泄漏不是典型的C / C ++内存泄漏。
要了解JVM的工作原理,请阅读Understanding Memory Management。
基本上,重要的部分是:
标记和扫描模型
JRockit JVM使用标记和清除垃圾收集模型 执行整个堆的垃圾收集。一个标记和扫描 垃圾收集包括两个阶段,标记阶段和 扫描阶段。
在标记阶段可以从Java访问的所有对象 线程,本机句柄和其他根源标记为活动,如 以及从这些对象可以访问的对象等等 向前。 此过程标识并标记所有静止的对象 使用,其余的可以被视为垃圾。
在扫描阶段,遍历堆以找到它们之间的间隙 活物。这些差距记录在免费清单中并制作完成 可用于新对象分配。
JRockit JVM使用标记和扫描的两个改进版本 模型。一个是大多数并发标记和扫描,另一个是 平行标记和扫描。你也可以混合两种策略,运行 例如,主要是并发标记和并行扫描。
因此,在Java中创建内存泄漏;最简单的方法是创建一个数据库连接,做一些工作,而不是Close()
它;然后在保持范围的同时生成新的数据库连接。例如,在循环中这并不难做到。如果您有一个从队列中拉出并推送到数据库的工作人员,您可以通过忘记Close()
连接或在不需要时打开它们来轻松创建内存泄漏,等等。
最终,您将通过忘记Close()
连接来使用已分配给JVM的堆。这将导致JVM垃圾收集像疯了一样;最终导致java.lang.OutOfMemoryError: Java heap space
错误。应该注意的是,错误可能并不意味着存在内存泄漏;它可能只是意味着你没有足够的记忆力;例如Cassandra和ElasticSearch等数据库可能会抛出该错误,因为它们没有足够的堆空间。
值得注意的是,所有GC语言都适用。下面是我作为SRE工作的一些例子:
json.Unmarshal
解析大型json文件,然后通过引用传递结果并保持打开状态。最终,这导致整个堆被意外引用消耗,我保持打开以解码json。答案 41 :(得分:6)
一种可能性是为ArrayList创建一个只提供一个方法的包装器:一个向ArrayList添加内容的方法。使ArrayList本身私有。现在,在全局范围内构建其中一个包装器对象(作为类中的静态对象),并使用 final 关键字(例如public static final ArrayListWrapper wrapperClass = new ArrayListWrapper()
)对其进行限定。所以现在引用不能改变。也就是说,wrapperClass = null
将不起作用,不能用于释放内存。但是除了向它添加对象之外,还没有办法对wrapperClass
做任何事情。因此,您添加到wrapperClass
的任何对象都无法回收。
答案 42 :(得分:5)
java 1.6中的String.substring方法创建内存泄漏。这篇博文解释了它。
http://javarevisited.blogspot.com/2011/10/how-substring-in-java-works.html
答案 43 :(得分:5)
使用对话框,Swing非常容易。创建一个JDialog,显示它,用户关闭它,泄漏!
您必须致电dispose()
或配置setDefaultCloseOperation(DISPOSE_ON_CLOSE)
答案 44 :(得分:5)
在Java中,“内存泄漏”主要是你使用了太多的内存,这与你不再使用内存而忘记返回(免费)的C不同。当面试官询问Java内存泄漏时,他们会询问JVM内存使用情况是否会继续上升,并且他们确定定期重新启动JVM是最佳解决方案。 (除非面试官非常精通技术)
所以回答这个问题好像他们问到了什么使JVM内存使用量随着时间的推移而增长。好的答案是在HttpSessions中存储过多的数据,超时时间过长或者内存缓存(Singleton)实现不好,从而不会刷新旧条目。另一个可能的答案是拥有大量JSP或动态生成的类。类被加载到一个名为PermGen的内存区域,通常很小,大多数JVM都没有实现类卸载。
答案 45 :(得分:4)
如果您不使用压缩垃圾收集器,则由于堆碎片可能会导致某种内存泄漏。
答案 46 :(得分:4)
Lapsed Listerners是内存泄漏的一个很好的例子:Object被添加为Listener。当不再需要该对象时,对该对象的所有引用都将被清零。但是,忘记从侦听器列表中删除对象会使对象保持活动状态,甚至会响应事件,从而浪费内存和CPU。见http://www.drdobbs.com/jvm/java-qa/184404011
答案 47 :(得分:4)
不小心在具有自己生命周期的类中使用非静态内部类。
在Java中,非静态内部和匿名类将隐式引用保存到其外部类。另一方面,静态内部类不。
以下是Android中存在内存泄漏的常见示例,但这并不明显:
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() { //non-static inner class, holds the reference to the SampleActivity outter class
@Override
public void handleMessage(Message msg) {
// ...
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for a long time.
mLeakyHandler.postDelayed(new Runnable() {//here, the anonymous inner class holds the reference to the SampleActivity class too
@Override
public void run() {
//....
}
}, SOME_TOME_TIME);
// Go back to the previous Activity.
finish();
}}
这将阻止活动上下文被垃圾回收。
答案 48 :(得分:2)
Java中有很多很好的内存泄漏示例,在此答案中我将提到其中两个。
示例1:
这是《有效的Java》第三版(项目7:消除过时的对象引用)中的一个内存泄漏的好例子:
// Can you spot the "memory leak"?
public class Stack {
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private Object[] elements;
private int size = 0;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) throw new EmptyStackException();
return elements[--size];
}
/*** Ensure space for at least one more element, roughly* doubling the capacity each time the array needs to grow.*/
private void ensureCapacity() {
if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
这是本书的段落,描述了为什么此实现会导致内存泄漏:
如果堆栈先增大然后缩小,则弹出的对象 即使程序使用 stack没有更多引用。这是因为 堆栈维护对这些对象的过时引用。过时的 引用只是一个永远不会被取消引用的引用 再次。在这种情况下,任何引用的“有效部分” 元素数组已过时。活动部分包括 索引小于大小的元素
这是本书中解决此内存泄漏的解决方案:
解决此类问题的方法很简单:将其清空 引用一旦过时。对于我们的Stack类, 弹出项目后,对该项目的引用就会过时 离开堆栈。正确的pop方法版本如下:
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
但是如何防止内存泄漏呢?这是本书的一个很好的警告:
通常来说,每当一个类管理自己的内存时, 程序员应该警惕内存泄漏。每当一个元素 被释放,元素中包含的任何对象引用都应为 无效。
示例2:
observer pattern也可能导致内存泄漏。您可以在以下链接中了解此模式:Observer pattern。
这是Observer
模式的一种实现:
class EventSource {
public interface Observer {
void update(String event);
}
private final List<Observer> observers = new ArrayList<>();
private void notifyObservers(String event) {
observers.forEach(observer -> observer.update(event)); //alternative lambda expression: observers.forEach(Observer::update);
}
public void addObserver(Observer observer) {
observers.add(observer);
}
public void scanSystemIn() {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
notifyObservers(line);
}
}
}
在此实现中,在EventSource
设计模式中可观察到的Observer
可以保存指向Obeserver
对象的链接,但是永远不会从observers
字段中删除此链接。在EventSource
中。因此,它们将永远不会被垃圾收集器收集。解决此问题的一种解决方案是为客户端提供另一种方法,以在他们不再需要observers
字段中的上述观察者时将其删除:
public void removeObserver(Observer observer) {
observers.remove(observer);
}
答案 49 :(得分:2)
import sun.misc.Unsafe;
import java.lang.reflect.Field;
class Main {
public static void main(String args[]) {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
((Unsafe) f.get(null)).allocateMemory(2000000000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
答案 50 :(得分:0)
对先前答案的一点改进(以更快地产生内存泄漏)是使用从大型 XML 文件加载的 DOM 文档实例。
答案 51 :(得分:0)
创建一个仅包含while-true循环的JNI函数,并使用另一个线程中的大对象对其进行调用。 GC不太喜欢JNI,它将把对象永远保存在内存中。
答案 52 :(得分:0)
其中一个Java内存泄漏示例是MySQLs memory leaking bug,当忘记调用ResultSets close方法时会产生该错误。例如:
while(true) {
ResultSet rs = database.select(query);
...
// going to next step of loop and leaving resultset without calling rs.close();
}
答案 53 :(得分:0)
内存泄漏是一种资源泄漏,它是当计算机程序错误地管理内存分配而导致不再释放不再需要的内存 => wiki definition时发生的。
这是一种相对基于上下文的主题,您可以根据自己的喜好创建一个主题,只要未使用的引用将永远不会被客户端使用,而是仍然存在。
第一个示例应该是一个自定义堆栈,而不会effective java item 6中的过时引用不删除 。
当然,只要您想要的话,还可以有更多,但是如果我们只看一下Java内置类,它可能就是
让我们检查一些 super傻代码以产生泄漏。
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来利用其查找过程导致内存泄漏。实际上有两种类型:
hashCode()
始终相同,但equals()
不同; hashCode()
和equals()
始终为真; 为什么?
hashCode()
-> bucket => equals()
to locate the pair
我本来要先提到substring()
,然后再提到subList()
,但似乎此问题已得到解决,因为其来源已在JDK 8中提出。
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);
}
答案 54 :(得分:0)
JDK 1.7之前内存泄漏的实时示例
假设您读取了1000行文本的文件并保留在String对象
中String fileText = 1000 characters from file
fileText = fileText.subString(900, fileText.length());
在上面的代码中,我最初读取了1000个字符,然后使用子字符串来获得最后100个字符。现在fileText应该只引用100个字符,所有其他字符应该得到垃圾收集,因为我丢失了引用但在JDK 1.7子字符串函数间接引用最后100个字符的原始字符串并防止整个字符串从垃圾收集和整个1000字符将在那里在内存中,直到松开子串的引用。
您可以创建内存泄漏示例,如上所述
答案 55 :(得分:-1)
实现内存泄漏样本最简单的方法之一就是使用静态方法。
例如
static List<int> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
list.add(i);
}
system.sleep();
在这种情况下,jvm无法释放列表的内存,因为它是静态类,并且始终保留在堆上。
答案 56 :(得分:-4)
就像这样!
public static void main(String[] args) {
List<Object> objects = new ArrayList<>();
while(true) {
objects.add(new Object());
}
}
答案 57 :(得分:-4)
从有效的java书籍
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
/**
* Ensure space for at least one more element, roughly doubling the capacity
* each time the array needs to grow.
*/
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
你能发现内存泄漏吗? 那么内存泄漏在哪里?如果堆栈增长然后收缩,则对象 即使是程序,从堆栈弹出的也不会被垃圾收集 使用堆栈没有更多的引用。这是因为堆栈维护 对这些对象的过时引用。过时的参考只是一个参考 永远不会再被取消引用。在这种情况下,任何引用之外 元素数组的“活动部分”已过时。活动部分包括 索引小于大小的元素。
答案 58 :(得分:-5)
Java中没有内存泄漏这样的东西。内存泄漏是从C等人那里借来的一句话。 Java在GC的帮助下内部处理内存分配。 内存浪费(即留下搁浅的对象),但内存泄漏。
答案 59 :(得分:-6)
这是一个非常简单的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));
}
}
}