Mat*_* B. 3076 java memory memory-leaks
我刚接受采访,并被要求用Java 创建内存泄漏.
毋庸置疑,我觉得自己很傻,甚至不知道如何开始创建一个.
一个例子是什么?
Dan*_*den 2225
这是在纯Java中创建真正的内存泄漏(通过运行代码但仍然存储在内存中无法访问的对象)的好方法:
ClassLoader),在静态字段中存储对它的强引用,然后在ThreadLocal中存储对自身的引用.分配额外的内存是可选的(泄漏Class实例就足够了),但它会使泄漏工作更快.这是有效的,因为ThreadLocal保留对该对象的引用,该对象保持对其Class的引用,而Class又保持对其ClassLoader的引用.反过来,ClassLoader保持对它已加载的所有类的引用.
(在许多JVM实现中,特别是在Java 7之前,情况更糟,因为Classes和ClassLoader直接分配到permgen并且根本就没有GC.但是,无论JVM如何处理类卸载,ThreadLocal仍然会阻止被回收的类对象.)
此模式的一个变体是,如果您经常重新部署碰巧以任何方式使用ThreadLocals的应用程序,那么应用程序容器(如Tomcat)可能会像筛子那样泄漏内存.(由于应用程序容器使用了所描述的线程,并且每次重新部署应用程序时都会使用新的ClassLoader.)
更新:由于很多人不断要求它,这里有一些示例代码显示了这种行为.
Pra*_*ate 1177
静态字段保持对象引用[esp final field]
class MemorableClass {
static final ArrayList list = new ArrayList(100);
}
Run Code Online (Sandbox Code Playgroud)
调用String.intern()冗长的String
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();
Run Code Online (Sandbox Code Playgroud)
(未封闭)开放流(文件,网络等......)
try {
BufferedReader br = new BufferedReader(new FileReader(inputFile));
...
...
} catch (Exception e) {
e.printStacktrace();
}
Run Code Online (Sandbox Code Playgroud)
未封闭的连接
try {
Connection conn = ConnectionFactory.getConnection();
...
...
} catch (Exception e) {
e.printStacktrace();
}
Run Code Online (Sandbox Code Playgroud)
JVM的垃圾收集器无法访问的区域,例如通过本机方法分配的内存
在Web应用程序中,某些对象存储在应用程序范围中,直到明确停止或删除应用程序.
getServletContext().setAttribute("SOME_MAP", map);
Run Code Online (Sandbox Code Playgroud)
不正确或不适当的JVM选项,例如noclassgcIBM JDK上防止未使用的类垃圾回收的选项
请参阅IBM jdk设置.
Pet*_*rey 448
一件简单的事情做的是使用与不正确的(或不存在)一个HashSet hashCode()或者equals(),然后不断增加"复制".而不是忽略重复,它只会增长,你将无法删除它们.
如果你想让这些坏键/元素闲逛,你可以使用静态字段
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.
Run Code Online (Sandbox Code Playgroud)
bes*_*sss 266
下面将有一个非显而易见的案例,其中Java泄漏,除了被遗忘的侦听器的标准情况,静态引用,哈希映射中的虚假/可修改键,或者只是线程卡住而没有任何机会结束其生命周期.
File.deleteOnExit() - 总是泄漏字符串, char[],所以后者不适用 ; @Daniel,不需要投票.我将专注于线程,以显示大多数非托管线程的危险,不希望甚至触摸挥杆.
Runtime.addShutdownHook而不是甚至removeShutdownHook由于ThreadGroup类有关它可能不会收集尚未启动的线程中的错误删除......然后,有效地泄漏线程组.JGroup在GossipRouter中有漏洞.
创建(但不是开始)a Thread与上面的类别相同.
创建一个线程继承了ContextClassLoaderand AccessControlContext,加上ThreadGroup和InheritedThreadLocal所有这些引用都是潜在的泄漏,以及类加载器和所有静态引用加载的整个类,以及ja-ja.整个jucExecutor框架具有超级简单的ThreadFactory界面,但效果特别明显,但大多数开发人员都不知道潜伏的危险.此外,许多库都会根据请求启动线程(太多行业流行的库).
ThreadLocal高速缓存; 在许多情况下这些都是邪恶的.我相信每个人都已经看到了很多基于ThreadLocal的简单缓存,这也是坏消息:如果线程持续超过预期生命的上下文ClassLoader,它是一个纯粹的小泄漏.除非确实需要,否则不要使用ThreadLocal缓存.
ThreadGroup.destroy()当ThreadGroup本身没有线程时调用,但它仍然保留子ThreadGroups.一个错误的泄漏将阻止ThreadGroup从其父级中删除,但所有子级都变为不可枚举.
使用WeakHashMap和值(in)直接引用键.没有堆转储,这是一个很难找到的.这适用于所有Weak/SoftReference可能将硬引用保留回受保护对象的扩展.
使用java.net.URLHTTP(S)协议并从(!)加载资源.这个是特殊的,KeepAliveCache在系统ThreadGroup中创建一个新线程,它泄漏当前线程的上下文类加载器.当没有活动线程存在时,线程是在第一个请求时创建的,所以你可能会幸运或只是泄漏.泄漏已在Java 7中修复,并且创建线程的代码正确删除了上下文类加载器.还有更多的案例(像ImageFetcher,也修复了创建类似线程的问题.
使用InflaterInputStream传入new java.util.zip.Inflater()构造函数(PNGImageDecoder例如)而不调用end()inflater.好吧,如果你只用构造传递new,没有机会了......是的,要求close()在该流不关闭充气,如果是手动的构造函数参数传递.这不是真正的泄漏,因为它会被终结者释放......当它认为有必要时.直到那一刻它吃掉本机内存如此糟糕,它可能导致Linux oom_killer肆无忌惮地杀死进程.主要问题是Java的最终确定非常不可靠,G1在7.0.2之前变得更糟.故事的道德:尽快释放本地资源; 终结者太穷了.
同样的情况java.util.zip.Deflater.这个更糟糕,因为Deflater在Java中占用了大量内存,即总是使用15位(最大值)和8个内存级别(最多9个)来分配几百KB的本机内存.幸运的是,Deflater并没有广泛使用,据我所知JDK不包含任何错误.end()如果手动创建Deflater或,请始终呼叫Inflater.最后两个中最好的部分:您无法通过常规的分析工具找到它们.
(我可以根据要求添加更多浪费时间.)
祝你好运,保持安全; 泄漏是邪恶的!
Nic*_*uet 196
这里的大多数例子都"过于复杂".他们是边缘案件.使用这些示例,程序员犯了一个错误(比如不重新定义equals/hashcode),或者被JVM/JAVA(带静态类的类加载)的角落情况所困扰.我认为这不是面试官想要的例子,甚至是最常见的案例.
但是内存泄漏的情况确实比较简单.垃圾收集器只释放不再引用的内容.我们作为Java开发人员并不关心内存.我们在需要时分配它,并让它自动释放.精细.
但任何长期存在的应用程序都倾向于共享状态.它可以是任何东西,静力学,单身......通常非平凡的应用程序往往会制作复杂的对象图.只是忘记设置null或更多的引用经常忘记从集合中删除一个对象足以导致内存泄漏.
当然,如果处理不当,所有类型的侦听器(如UI侦听器),缓存或任何长期共享状态都会产生内存泄漏.应该理解的是,这不是Java角落案例,也不是垃圾收集器的问题.这是一个设计问题.我们设计我们为一个长期存在的对象添加一个监听器,但是当不再需要时我们不会删除监听器.我们缓存对象,但我们没有策略将它们从缓存中删除.
我们可能有一个复杂的图形来存储计算所需的先前状态.但是之前的状态本身与之前的状态有关,依此类推.
就像我们必须关闭SQL连接或文件一样.我们需要设置对null的正确引用并从集合中删除元素.我们将有适当的缓存策略(最大内存大小,元素数量或计时器).允许侦听器得到通知的所有对象都必须同时提供addListener和removeListener方法.当这些通知符不再使用时,他们必须清除他们的听众列表.
内存泄漏确实是可能的,并且是完全可预测的.无需特殊语言功能或角落案例.内存泄漏可能是某些事情可能缺失甚至是设计问题的指标.
Pla*_*ank 155
答案完全取决于面试官的想法.
在实践中是否有可能使Java泄漏?当然是,并且在其他答案中有很多例子.
但是有多个元问题可能会被问到?
我正在读你的元问题:"在这次采访中我能用到什么答案".因此,我将专注于面试技巧而不是Java.我相信你更有可能重复在面试中不知道问题答案的情况,而不是在需要知道如何使Java泄漏的地方.所以,希望这会有所帮助.
您可以为面试开发的最重要技能之一是学习积极倾听问题并与面试官合作以提取他们的意图.这不仅可以让你以他们想要的方式回答他们的问题,而且还表明你有一些重要的沟通技巧.当谈到许多同样有才华的开发人员之间的选择时,我会聘请那些在每次回复之前倾听,思考和理解的人.
Vin*_*lds 128
如果您不了解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);
}
}
Run Code Online (Sandbox Code Playgroud)
上面的问题是Connection对象没有关闭,因此物理连接将保持打开状态,直到垃圾收集器出现并看到它无法访问.GC将调用该finalize方法,但是有些JDBC驱动程序没有实现finalize,至少与Connection.close实现的方式不同.由此产生的行为是,由于收集了无法访问的对象而将回收内存,因此Connection可能无法回收与该对象关联的资源(包括内存).
在这种情况下,其中Connection的finalize方法不清理一切,一个可能真正发现到数据库服务器的物理连接将持续几个垃圾回收周期,直到该数据库服务器最终计算出该连接是不是还活着(如果它并且应该关闭.
即使要实现JDBC驱动程序finalize,也可能在最终确定期间抛出异常.由此产生的行为是,与现在"休眠"对象关联的任何内存都不会被回收,因为finalize保证只调用一次.
在对象最终化期间遇到异常的上述情况与另一个可能导致内存泄漏的情况有关 - 对象复活.对象复活通常是通过从另一个对象创建对象的强引用来有意识地完成的.当对象复活被滥用时,它将导致内存泄漏以及其他内存泄漏源.
你可以想出更多的例子 - 比如
List您只添加到列表而不是从中删除的实例(尽管您应该删除不再需要的元素),或者Sockets或Files,但不再需要它们时关闭它们(类似于涉及Connection类的上述示例).mer*_*ike 118
可能是潜在内存泄漏的最简单示例之一,以及如何避免它,是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;
}
Run Code Online (Sandbox Code Playgroud)
如果你自己实现它,你会想到清除不再使用的数组元素(elementData[--size] = null)吗?那个引用可能会让一个巨大的物体活着......
Bil*_*ard 65
每当你保持对不再需要的对象的引用时,就会发生内存泄漏.有关内存泄漏如何在Java中表现出来以及您可以采取哪些措施的示例,请参阅Java程序中的处理内存泄漏.
ste*_*emm 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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
yan*_*kee 43
我可以从这里复制我的答案: 在Java中导致内存泄漏的最简单方法?
"当计算机程序消耗内存但无法将其释放回操作系统时,会发生计算机科学(或泄漏,在此上下文中)的内存泄漏." (维基百科)
简单的答案是:你做不到.Java执行自动内存管理,并将释放您不需要的资源.你不能阻止这种情况发生.它总是能够释放资源.在具有手动内存管理的程序中,这是不同的.你可以使用malloc()在C中获得一些内存.要释放内存,您需要malloc返回的指针并在其上调用free().但是如果你不再使用指针(覆盖或超过生命周期),那么很遗憾你无法释放这个内存,因此你有内存泄漏.
到目前为止,所有其他答案都在我的定义中并非真正的内存泄漏.它们都旨在快速填充无意义的内存.但是在任何时候你仍然可以取消引用你创建的对象,从而释放内存 - >没有泄漏.acconrad的答案非常接近,但我不得不承认,因为他的解决方案实际上是通过强制它在无限循环中"崩溃"垃圾收集器.
答案很长:您可以通过使用JNI编写Java库来获取内存泄漏,JNI可能具有手动内存管理功能,因此存在内存泄漏.如果你调用这个库,你的java进程将泄漏内存.或者,您可能在JVM中有错误,因此JVM会丢失内存.JVM中可能存在bug,甚至可能有一些已知的bug,因为垃圾收集不是那么简单,但它仍然是一个bug.按设计这是不可能的.你可能会要求一些受这种bug影响的java代码.对不起,我不知道一个,无论如何它在下一个Java版本中可能不再是一个bug.
Jon*_*ers 37
这是一个简单/险恶的,来自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);
}
}
Run Code Online (Sandbox Code Playgroud)
因为子串指的是原始字符串的内部表示,所以原始字符串保留在内存中.因此,只要你有一个StringLeaker,你就可以在内存中拥有整个原始字符串,即使你可能认为你只是坚持使用单字符字符串.
避免存储对原始字符串的不需要的引用的方法是执行以下操作:
...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...
Run Code Online (Sandbox Code Playgroud)
对于增加的不良,您可能还有.intern()子字符串:
...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...
Run Code Online (Sandbox Code Playgroud)
即使在丢弃StringLeaker实例之后,这样做也会将原始长字符串和派生子字符串保留在内存中.
Har*_*ann 36
获取在任何servlet容器中运行的任何Web应用程序(Tomcat,Jetty,Glassfish,等等......).连续重新部署应用程序10次或者20次(可能只需触摸服务器的autodeploy目录中的WAR即可.
除非有人实际对此进行了测试,否则在进行几次重新部署后你会得到一个OutOfMemoryError的可能性很高,因为应用程序没有注意自己清理.您甚至可以通过此测试在服务器中找到错误.
问题是,容器的生命周期比应用程序的生命周期长.您必须确保容器可能对应用程序的对象或类的所有引用都可以进行垃圾回收.
如果只有一个引用在您的Web应用程序取消部署后仍然存在,则相应的类加载器将导致您的Web应用程序的所有类都无法进行垃圾回收.
您的应用程序启动的线程,ThreadLocal变量,日志记录appender是导致类加载器泄漏的一些常见嫌疑.
pil*_*rth 35
GUI代码中的一个常见示例是创建窗口小部件/组件并将侦听器添加到某个静态/应用程序范围对象,然后在窗口小部件被销毁时不删除侦听器.您不仅会遇到内存泄漏,而且还会受到性能影响,因为无论您何时正在收听点火事件,您的所有旧听众都会被调用.
Rog*_*ach 33
也许通过JNI使用外部本机代码?
使用纯Java,几乎是不可能的.
但这是关于"标准"类型的内存泄漏,当您无法再访问内存时,它仍然由应用程序拥有.您可以保留对未使用对象的引用,或者在不关闭它们的情况下打开流.
Ron*_*Ron 29
我曾经有过一次与PermGen和XML解析有关的"内存泄漏".我们使用的XML解析器(我不记得它是哪一个)在标记名称上做了一个String.intern(),以便更快地进行比较.我们的一位客户最好不要将数据值存储在XML属性或文本中,而是存储为标记名,因此我们有一个文档,如:
<data>
<1>bla</1>
<2>foo</>
...
</data>
Run Code Online (Sandbox Code Playgroud)
实际上,他们并没有使用数字,而是使用更长的文本ID(大约20个字符),这些ID是独一无二的,并且以每天1000-1500万的速度进行.这每天产生200 MB的垃圾,这是永远不需要的,而且从来没有GCed(因为它是在PermGen).我们将permgen设置为512 MB,因此内存异常(OOME)需要大约两天才能到达......
bvd*_*vdb 23
什么是内存泄漏:
典型例子:
对象的缓存是弄乱事物的一个很好的起点.
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;
}
Run Code Online (Sandbox Code Playgroud)
您的缓存增长和增长.很快整个数据库就被吸进了内存.更好的设计使用LRUMap(仅在缓存中保留最近使用的对象).
当然,你可以让事情变得更复杂:
经常发生的事情:
如果此Info对象具有对其他对象的引用,则其他对象也会引用其他对象.在某种程度上,你也可以认为这是某种内存泄漏(由糟糕的设计引起).
Sur*_*oot 22
我觉得有趣的是没有人使用内部类的例子.如果你有内部课程; 它固有地维护对包含类的引用.当然,从技术上讲,它不是内存泄漏,因为Java最终会将其清理干净; 但这可能导致课程比预期更长时间.
public class Example1 {
public Example2 getNewExample2() {
return this.new Example2();
}
public class Example2 {
public Example2() {}
}
}
Run Code Online (Sandbox Code Playgroud)
现在,如果您调用Example1并获取Example2丢弃Example1,您将固有地仍然拥有指向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.
}
}
Run Code Online (Sandbox Code Playgroud)
我也听说过一个谣言,如果你的变量存在时间超过特定的时间; Java假定它将永远存在,并且如果再也无法在代码中访问它,它实际上永远不会尝试清理它.但这完全未经证实.
Pun*_*eet 22
我最近遇到了log4j造成的内存泄漏情况.
Log4j具有这种称为嵌套诊断上下文(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();
}
}
Run Code Online (Sandbox Code Playgroud)
因此,NDC上下文与生成的内联线程相关联.作为此NDC上下文的键的线程对象是内联线程,其中挂起了hugeList对象.因此,即使在线程完成它正在做的事情之后,对NDC上下文Hastable仍然保持对hugeList的引用,从而导致内存泄漏.
del*_*106 20
面试官可能正在寻找一个循环引用,如下面的代码(顺便说一下,在使用引用计数的非常旧的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 */
}
Run Code Online (Sandbox Code Playgroud)
然后你可以用引用计数来解释,上面的代码会泄漏内存.但是大多数现代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. */
}
}
Run Code Online (Sandbox Code Playgroud)
然后你可以解释这在技术上是一个内存泄漏,但实际上泄漏是由JVM中的本机代码分配底层本机资源引起的,这些资源没有被Java代码释放.
在一天结束时,使用现代JVM,您需要编写一些Java代码,以便在JVM意识的正常范围之外分配本机资源.
duf*_*ymo 18
创建一个静态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); }
}
Run Code Online (Sandbox Code Playgroud)
Pau*_*rie 17
每个人总是忘记原生代码路由.这是泄漏的简单公式:
malloc.不要打电话free.请记住,本机代码中的内存分配来自JVM堆.
set*_*ien 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());
}
}
}
Run Code Online (Sandbox Code Playgroud)
Ben*_*Ben 15
我认为还没有人说过这个:你可以通过覆盖finalize()方法来复活一个对象,以便finalize()在某处存储这个引用.垃圾收集器只会在对象上调用一次,所以在此之后对象永远不会被销毁.
Jay*_*Jay 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();
}
}
Run Code Online (Sandbox Code Playgroud)
只需粘贴到名为BigJarCreator.java的文件中,从命令行编译并运行它:
javac BigJarCreator.java
java -cp . BigJarCreator
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这个类基本上什么都不做,但创建了未引用的InputStream对象.这些对象将立即被垃圾收集,因此不会对堆大小有所贡献.对于我们的示例来说,从jar文件加载现有资源非常重要,大小在这里很重要!
如果您有疑问,请尝试编译并启动上面的类,但请确保选择合适的堆大小(2 MB):
javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak
Run Code Online (Sandbox Code Playgroud)
这里不会遇到OOM错误,因为没有保留引用,无论您在上面的示例中选择了多大的ITERATIONS,应用程序都将继续运行.除非应用程序进入wait命令,否则进程的内存消耗(在顶层(RES/RSS)或进程资源管理器中可见)会增长.在上面的设置中,它将在内存中分配大约150 MB.
如果您希望应用程序安全播放,请在创建它的位置关闭输入流:
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();
Run Code Online (Sandbox Code Playgroud)
并且您的过程不会超过35 MB,与迭代次数无关.
非常简单和令人惊讶.
Gra*_*ham 14
正如许多人所建议的那样,资源泄漏很容易引起 - 就像JDBC示例一样.实际内存泄漏有点困难 - 特别是如果你不依赖于JVM的破碎位来为你做这件事......
创建具有非常大的占用空间然后无法访问它们的对象的想法也不是真正的内存泄漏.如果没有什么可以访问它那么它将被垃圾收集,如果有什么东西可以访问它,那么它不是泄漏......
过去工作的一种方式- 我不知道它是否仍然存在 - 是有一个三深的圆形链.如在对象A中引用了对象B,对象B引用了对象C,对象C引用了对象A.GC非常聪明,知道两条深链 - 如A < - > B - 如果A和B无法通过其他任何方式访问,可以安全地收集,但无法处理三向链......
Ben*_* Xu 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 {
}
Run Code Online (Sandbox Code Playgroud)
Boa*_*ann 11
线程在终止之前不会被收集.它们是垃圾收集的根源.它们是为数不多的仅通过忘记它们或清除对它们的引用而无法回收的对象之一.
考虑:终止工作线程的基本模式是设置线程看到的一些条件变量.线程可以定期检查变量并将其用作终止信号.如果未声明变量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();
}
Run Code Online (Sandbox Code Playgroud)
打电话给System.gc()你喜欢,但传递给的对象leakMe永远不会死.
(*编辑*)
msc*_*ker 10
我认为一个有效的例子可能是在线程被池化的环境中使用ThreadLocal变量.
例如,使用Servlet中的ThreadLocal变量与其他Web组件进行通信,让容器创建线程并在池中维护空闲的线程.ThreadLocal变量如果没有被正确清理,将会存在,直到可能相同的Web组件覆盖它们的值.
当然,一旦确定,问题就可以轻松解决.
小智 10
面试官可能正在寻找循环参考解决方案:
public static void main(String[] args) {
while (true) {
Element first = new Element();
first.next = new Element();
first.next.next = first;
}
}
Run Code Online (Sandbox Code Playgroud)
这是引用计数垃圾收集器的典型问题.然后,您可以礼貌地解释JVM使用更复杂的算法,但没有此限制.
-Wes Tarle
Mar*_*cak 10
另一种创建潜在巨大内存泄漏的方法是保存对Map.Entry<K,V>a的引用TreeMap.
很难评估为什么这只适用于TreeMaps,但是通过查看实现,原因可能是:a TreeMap.Entry存储对其兄弟节点的引用,因此如果a TreeMap已准备好被收集,但是其他一些类持有对任何它Map.Entry,然后整个地图将保留在内存中.
现实场景:
想象一下,有一个返回大TreeMap数据结构的db查询.人们通常使用TreeMaps作为元素插入顺序被保留.
public static Map<String, Integer> pseudoQueryDatabase();
Run Code Online (Sandbox Code Playgroud)
如果查询被多次调用,并且对于每个查询(因此,对于每个Map返回的),您保存在Entry某处,内存将不断增长.
考虑以下包装类:
class EntryHolder {
Map.Entry<String, Integer> entry;
EntryHolder(Map.Entry<String, Integer> entry) {
this.entry = entry;
}
}
Run Code Online (Sandbox Code Playgroud)
应用:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
在每次pseudoQueryDatabase()调用之后,map实例应该准备好进行收集,但是不会发生,因为至少有一个Entry存储在其他地方.
根据您的jvm设置,应用程序可能会因为a而在早期阶段崩溃OutOfMemoryError.
您可以从此visualvm图中看到内存如何不断增长.
散列数据结构(HashMap)不会发生同样的情况.
这是使用a时的图表HashMap.
解决方案?只需直接保存键/值(就像你可能已经做过的那样)而不是保存Map.Entry.
我在这里写了一个更广泛的基准.
小智 9
我最近修复的一个例子是创建新的GC和Image对象,但忘记调用dispose()方法.
GC javadoc片段:
应用程序代码必须显式调用GC.dispose()方法,以便在不再需要这些实例时释放由每个实例管理的操作系统资源.这在Windows95和Windows98上尤其重要,其中操作系统具有有限数量的设备上下文.
图片javadoc片段:
应用程序代码必须显式调用Image.dispose()方法,以便在不再需要这些实例时释放由每个实例管理的操作系统资源.
小智 8
理论上你不能.Java内存模型可以防止它.但是,由于必须实现Java,因此您可以使用一些注意事项.取决于你可以使用什么:
如果你可以使用native,你可以分配你以后不会放弃的内存.
如果没有,那么关于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.我可以这样做.
一个不终止的线程(比如在其run方法中无限期地休眠).即使我们放弃了对它的引用,它也不会被垃圾收集.您可以添加字段以使线程对象变得很大.
目前最热门的答案列出了更多的技巧,但这些似乎是多余的.
我想就如何使用JVM中提供的工具监视内存泄漏的应用程序提出建议.它没有说明如何生成内存泄漏,但解释了如何使用最少的工具检测它.
您需要首先监视Java内存消耗.
最简单的方法是使用JVM附带的jstat实用程序.
jstat -gcutil <process_id> <timeout>
Run Code Online (Sandbox Code Playgroud)
它将报告每一代(Young,Eldery和Old)的内存消耗和垃圾收集时间(Young和Full).
一旦发现Full Garbage Collection执行得太频繁并且花费太多时间,您就可以假设应用程序正在泄漏内存.
然后,您需要使用jmap实用程序创建内存转储:
jmap -dump:live,format=b,file=heap.bin <process_id>
Run Code Online (Sandbox Code Playgroud)
然后,您需要使用Memory Analyzer,Eclipse Memory Analyzer(MAT)分析heap.bin文件.
MAT将分析内存并为您提供有关内存泄漏的可疑信息.
我在java中看到的大多数内存泄漏都与进程不同步有关.
进程A通过TCP与B通信,并告诉进程B创建一些东西.B向资源发出一个ID,比如说432423,A存储在一个对象中并在与B交谈时使用.在某些时候,A中的对象被垃圾收集回收(可能是由于一个bug),但A从不告诉B(也许是另一个bug).
现在A不再具有它在B的RAM中创建的对象的ID,并且B不知道A没有对该对象的更多引用.实际上,该对象被泄露.
一些建议:
通过重新部署应用程序可以"改善"上述效果;)
最近偶然发现了这个:
阅读http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5072161并链接问题以进行深入讨论.
如果最大堆大小是X. Y1 .... Yn没有实例所以,总内存=实例数X每个实例的字节数.如果X1 ...... Xn是每个实例的字节数.那么总内存(M)= Y1*X1 + ..... + Yn*Xn.所以,如果M> X它超过了堆空间.以下可以是代码1中的问题.使用更多实例变量然后是本地变量.2.每次创建实例而不是汇集对象.3.不按需创建对象.4.在操作完成后使对象引用为null.再次,在程序中需要时重新创建.
关于如何在Java中创建内存泄漏有很多答案,但请注意在访谈期间提出的要点.
"如何使用Java创建内存泄漏?" 是一个开放式问题,其目的是评估开发人员的经验程度.
如果我问你"你有解决Java中内存泄漏问题的经验吗?",你的回答将是一个简单的"是".然后我会跟进"你能举例说明你在哪里解决内存泄漏问题吗?",你会给我一两个例子.
然而,当面试官问"如何用Java创建内存泄漏?" 预期答案应遵循以下方针:
当开发人员不遵循这一思路时,我会尝试引导他/她问"你能给我一个Java泄漏内存的例子吗?",然后是"你有没有必要修复Java中的任何内存泄漏?"
请注意,我不是要求一个关于如何在Java中泄漏内存的示例.那太傻了.谁会对能够有效编写泄漏内存的代码的开发人员感兴趣?
一种可能性是为ArrayList创建一个包装器,它只提供一个方法:一个将事物添加到ArrayList.使ArrayList本身私有.现在,在全局范围内构造其中一个包装器对象(作为类中的静态对象),并使用final关键字(例如public static final ArrayListWrapper wrapperClass = new ArrayListWrapper())对其进行限定.所以现在引用不能改变.也就是说,wrapperClass = null不会工作,不能用来释放内存.但wrapperClass除了添加对象之外,还没有办法做任何事情.因此,您添加的任何对象wrapperClass都无法回收.
小智 6
java中的内存泄漏不是典型的C/C++内存泄漏.
要了解JVM的工作原理,请阅读" 了解内存管理".
基本上,重要的是:
标记和扫描模型
JRockit JVM使用标记和清除垃圾收集模型来执行整个堆的垃圾收集.标记和扫描垃圾收集包括两个阶段,标记阶段和扫描阶段.
在标记阶段,可以从Java线程,本机句柄和其他根源可访问的所有对象都标记为活动,以及可从这些对象访问的对象,等等.此过程标识并标记仍在使用的所有对象,其余对象可视为垃圾.
在扫描阶段,遍历堆以找到活动对象之间的间隙.这些间隙记录在空闲列表中,可用于新对象分配.
JRockit JVM使用标记和扫描模型的两个改进版本.一个是并发标记和扫描,另一个是并行标记和扫描.您还可以混合使用两种策略,例如主要是并发标记和并行扫描.
所以,在Java中创建内存泄漏; 最简单的方法是创建一个数据库连接,做一些工作,而不是Close()它; 然后在保持范围的同时生成新的数据库连接.例如,这在环路中并不难做到.如果您有一个从队列中拉出并推送到数据库的工作人员,则可以通过忘记Close()连接或在不需要时打开它们来轻松创建内存泄漏,等等.
最终,您将通过忘记Close()连接来使用已分配给JVM的堆.这将导致JVM垃圾收集像疯了一样; 最终导致java.lang.OutOfMemoryError: Java heap space错误.应该注意的是,错误可能并不意味着存在内存泄漏; 它可能只是意味着你没有足够的记忆; 例如Cassandra和ElasticSearch等数据库可能会抛出该错误,因为它们没有足够的堆空间.
值得注意的是,所有GC语言都适用.下面,我看到一些作为SRE工作的例子:
json.Unmarshal然后通过引用传递结果并保持打开状态.最终,这导致整个堆被意外引用消耗,我保持打开以解码json.在Java中,"内存泄漏"主要是你使用了太多的内存,这与你不再使用内存而忘记返回(免费)的C不同.当面试官询问Java内存泄漏时,他们会询问JVM内存使用情况是否会继续上升,并且他们确定定期重新启动JVM是最佳解决方案.(除非是面试官是非常精通技术)
所以回答这个问题,就好像他们询问是什么让JVM内存使用量随着时间而增长.好的答案是在HttpSessions中存储过多的数据,超时时间过长或内存缓存(Singleton)实现不好,从而不会刷新旧条目.另一个可能的答案是拥有大量JSP或动态生成的类.类被加载到一个名为PermGen的内存区域,该区域通常很小,大多数JVM都不实现类卸载.
使用对话框,Swing非常容易.创建一个JDialog,显示它,用户关闭它,泄漏!你必须打电话dispose()或配置setDefaultCloseOperation(DISPOSE_ON_CLOSE)
java 1.6中的String.substring方法创建内存泄漏.这篇博文解释了它.
http://javarevisited.blogspot.com/2011/10/how-substring-in-java-works.html
Java内存泄漏有很多很好的例子,我将在这个答案中提到其中两个。
示例 1:
这是Effective 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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是书中描述为什么这种实现会导致内存泄漏的段落:
如果堆栈增长然后收缩,从堆栈中弹出的对象将不会被垃圾回收,即使使用堆栈的程序没有更多对它们的引用。这是因为堆栈维护对这些对象的过时引用。一个过时的引用只是一个永远不会被取消引用的引用。在这种情况下,元素数组“活动部分”之外的任何引用都已过时。活动部分由索引小于 size 的元素组成
这是本书解决此内存泄漏的解决方案:
解决这类问题的方法很简单:一旦引用过时,就将其归零。在我们的 Stack 类的情况下,一旦它从堆栈中弹出,对项目的引用就变得过时了。pop 方法的修正版本如下所示:
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
Run Code Online (Sandbox Code Playgroud)
但是我们如何才能防止内存泄漏的发生呢?这是书中的一个很好的警告:
一般来说,每当一个类管理自己的内存时,程序员都应该警惕内存泄漏。每当一个元素被释放时,该元素中包含的任何对象引用都应该被清除。
示例 2:
在观察者模式也能引起了内存泄漏。您可以在以下链接中阅读有关此模式的信息:观察者模式。
这是观察者模式的一种实现:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
在这个实现中,EventSource,在观察者设计模式中是可观察的,可以保存到Obeserver对象的链接,但是这个链接永远不会从 中的observers字段中删除EventSource。所以它们永远不会被垃圾收集器收集。解决这个问题的一个解决方案是向客户端提供另一种方法,observers当他们不再需要这些观察者时,将上述观察者从现场中移除:
public void removeObserver(Observer observer) {
observers.remove(observer);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
615453 次 |
| 最近记录: |