用Java创建内存泄漏。

时间:2023-01-20 17:36:02

I just had an interview, and I was asked to create a memory leak with Java. Needless to say I felt pretty dumb having no clue on how to even start creating one.

我刚刚接受了一次采访,被要求用Java创建一个内存泄漏。不用说,我对如何开始创建一个网站一无所知。

What would an example be?

一个例子是什么?

51 个解决方案

#1


1867  

Here's a good way to create a true memory leak (objects inaccessible by running code but still stored in memory) in pure Java:

这里有一个很好的方法来创建一个真正的内存泄漏(运行代码无法访问的对象,但仍然存储在内存中):

  1. The application creates a long-running thread (or use a thread pool to leak even faster).
  2. 应用程序创建一个长时间运行的线程(或者使用线程池来更快地泄漏)。
  3. The thread loads a class via an (optionally custom) ClassLoader.
  4. 线程通过一个(可选自定义的)类装入器装入一个类。
  5. The class allocates a large chunk of memory (e.g. new byte[1000000]), stores a strong reference to it in a static field, and then stores a reference to itself in a ThreadLocal. Allocating the extra memory is optional (leaking the Class instance is enough), but it will make the leak work that much faster.
  6. 类分配了大量内存(例如,new byte[1000000]),在静态字段中存储强引用,然后在ThreadLocal中存储对自身的引用。分配额外内存是可选的(泄漏类实例就足够了),但是它会使泄漏工作更快。
  7. The thread clears all references to the custom class or the ClassLoader it was loaded from.
  8. 该线程清除所有对自定义类或其加载的类加载器的引用。
  9. Repeat.
  10. 重复。

This works because the ThreadLocal keeps a reference to the object, which keeps a reference to its Class, which in turn keeps a reference to its ClassLoader. The ClassLoader, in turn, keeps a reference to all the Classes it has loaded.

这可以工作,因为ThreadLocal保存对对象的引用,该对象保存对它的类的引用,而该对象又保留对它的类加载器的引用。类加载器依次保存对它所装载的所有类的引用。

(It was worse in many JVM implementations, especially prior to Java 7, because Classes and ClassLoaders were allocated straight into permgen and were never GC'd at all. However, regardless of how the JVM handles class unloading, a ThreadLocal will still prevent a Class object from being reclaimed.)

(在许多JVM实现中,尤其是在Java 7之前,情况更糟,因为类和类加载器被直接分配到permgen中,而从来没有GC。然而,不管JVM如何处理类卸载,ThreadLocal仍然会阻止类对象被回收。

A variation on this pattern is why application containers (like Tomcat) can leak memory like a sieve if you frequently redeploy applications that happen to use ThreadLocals in any way. (Since the application container uses Threads as described, and each time you redeploy the application a new ClassLoader is used.)

这种模式的变体就是为什么应用程序容器(如Tomcat)可以像筛子一样泄漏内存,如果您频繁地重新部署使用threadlocal的应用程序的话。(因为应用程序容器使用了描述的线程,并且每次重新部署应用程序时都会使用新的类加载器。)

Update: Since lots of people keep asking for it, here's some example code that shows this behavior in action.

更新:由于许多人不断地要求它,这里有一些示例代码显示了这种行为。

#2


1024  

Static field holding object reference [esp final field]

静态字段保存对象引用[esp final字段]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

Calling String.intern() on lengthy String

在长字符串中调用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();

(Unclosed) open streams ( file , network etc... )

(未关闭)开放流(文件、网络等)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Unclosed connections

打开连接

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Areas that are unreachable from JVM's garbage collector, such as memory allocated through native methods

从JVM的垃圾收集器中无法访问的区域,例如通过本机方法分配的内存。

In web applications, some objects are stored in application scope until the application is explicitly stopped or removed.

在web应用程序中,有些对象存储在应用程序范围中,直到应用程序被显式地停止或删除。

getServletContext().setAttribute("SOME_MAP", map);

Incorrect or inappropriate JVM options, such as the noclassgc option on IBM JDK that prevents unused class garbage collection

不正确或不适当的JVM选项,例如IBM JDK上的noclassgc选项,可以防止未使用的类垃圾收集。

See IBM jdk settings.

看到IBM jdk设置。

#3


382  

A simple thing to do is to use a HashSet with an incorrect (or non-existent) hashCode() or equals(), and then keep adding "duplicates". Instead of ignoring duplicates as it should, the set will only ever grow and you won't be able to remove them.

简单的做法是使用一个带有不正确(或不存在)hashCode()或equals()的HashSet,然后继续添加“重复”。这个集合不会像它应该那样忽略重复,而是只会增长,而你将无法删除它们。

If you want these bad keys/elements to hang around you can use a static field like

如果您希望这些坏键/元素挂在您的周围,可以使用静态字段。

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.

#4


220  

Below there will be a non-obvious case where Java leaks, besides the standard case of forgotten listeners, static references, bogus/modifiable keys in hashmaps, or just threads stuck without any chance to end their life-cycle.

下面将会有一个不明显的例子,Java泄漏,除了被遗忘的侦听器的标准案例,静态引用,hashmap中的伪/可修改键,或者只是线程被卡住,没有任何机会结束它们的生命周期。

  • File.deleteOnExit() - always leaks the string, if the string is a substring, the leak is even worse (the underlying char[] is also leaked) - in Java 7 substring also copies the char[], so the later doesn't apply; @Daniel, no needs for votes, though.
  • File.deleteOnExit()—总是泄漏字符串,如果字符串是子字符串,则泄漏更严重(底层的char[]也被泄漏)——在Java 7子字符串中也会复制char[],因此后面不应用;但是,丹尼尔,不需要投票。

I'll concentrate on threads to show the danger of unmanaged threads mostly, don't wish to even touch swing.

我将重点关注线程,以显示非托管线程的危险,甚至不希望触摸swing。

  • Runtime.addShutdownHook and not remove... and then even with removeShutdownHook due to a bug in ThreadGroup class regarding unstarted threads it may not get collected, effectively leak the ThreadGroup. JGroup has the leak in GossipRouter.

    运行时。addShutdownHook而不是删除…然后,即使是removeShutdownHook,由于ThreadGroup类中关于未启动线程的bug,它可能不会被收集,从而有效地泄漏ThreadGroup。JGroup泄露了流言蜚语。

  • Creating, but not starting, a Thread goes into the same category as above.

    创建,但不启动,一个线程进入与上面相同的类别。

  • Creating a thread inherits the ContextClassLoader and AccessControlContext, plus the ThreadGroup and any InheritedThreadLocal, all those references are potential leaks, along with the entire classes loaded by the classloader and all static references, and ja-ja. The effect is especially visible with the entire j.u.c.Executor framework that features a super simple ThreadFactory interface, yet most developers have no clue of the lurking danger. Also a lot of libraries do start threads upon request (way too many industry popular libraries).

    创建一个线程继承ContextClassLoader和AccessControlContext,加上ThreadGroup和任何继承threadlocal,所有这些引用都是潜在的泄漏,以及类加载器和所有静态引用和ja-ja加载的整个类。这种效果在整个j.u.c中尤其明显。具有超级简单的ThreadFactory接口的Executor框架,但大多数开发人员都不知道潜在的危险。此外,许多库在请求时也会启动线程(这是太多业界流行的库)。

  • ThreadLocal caches; those are evil in many cases. I am sure everyone has seen quite a bit of simple caches based on ThreadLocal, well the bad news: if the thread keeps going more than expected the life the context ClassLoader, it is a pure nice little leak. Do not use ThreadLocal caches unless really needed.

    ThreadLocal缓存;在很多情况下,这些都是邪恶的。我相信每个人都已经看到了一些基于ThreadLocal的简单缓存,但是坏消息是:如果线程的运行比预期的要多,那么上下文类加载器就是一个纯粹的小漏洞。除非确实需要,否则不要使用ThreadLocal缓存。

  • Calling ThreadGroup.destroy() when the ThreadGroup has no threads itself, but it still keeps child ThreadGroups. A bad leak that will prevent the ThreadGroup to remove from its parent, but all the children become un-enumerateable.

    当ThreadGroup本身没有线程时,调用ThreadGroup.destroy(),但它仍然保留子线程组。一个糟糕的泄漏将阻止ThreadGroup从其父节点中删除,但是所有的子节点都无法枚举。

  • Using WeakHashMap and the value (in)directly references the key. This is a hard one to find without a heap dump. That applies to all extended Weak/SoftReference that might keep a hard reference back to the guarded object.

    使用WeakHashMap和值(in)直接引用该键。如果没有堆转储,这是很难找到的。这适用于所有扩展的弱/SoftReference,它可能会对被保护对象保持一个严格的引用。

  • Using java.net.URL with the HTTP(S) protocol and loading the resource from(!). This one is special, the KeepAliveCache creates a new thread in the system ThreadGroup which leaks the current thread's context classloader. The thread is created upon the first request when no alive thread exists, so either you may get lucky or just leak. The leak is already fixed in Java 7 and the code that creates thread properly removes the context classloader. There are few more cases (like ImageFetcher, also fixed) of creating similar threads.

    使用java.net.URL与HTTP(S)协议并从(!)加载资源。这个是特殊的,KeepAliveCache在系统ThreadGroup中创建一个新线程,该线程会泄漏当前线程的上下文类加载器。线程是在没有生命线程存在时创建的,因此您可能会幸运或只是泄漏。泄漏已经在Java 7中修复了,而创建线程的代码将正确地删除上下文类加载器。在创建类似的线程时,很少有其他的情况(像ImageFetcher,同样是固定的)。

  • Using InflaterInputStream passing new java.util.zip.Inflater() in the constructor (PNGImageDecoder for instance) and not calling end() of the inflater. Well, if you pass in the constructor with just new, no chance... And yes, calling close() on the stream does not close the inflater if it's manually passed as constructor parameter. This is not a true leak since it'd be released by the finalizer... when it deems it necessary. Till that moment it eats native memory so badly it can cause Linux oom_killer to kill the process with impunity. The main issue is that finalization in Java is very unreliable and G1 made it worse till 7.0.2. Moral of the story: release native resources as soon as you can; the finalizer is just too poor.

    使用InflaterInputStream在构造函数(例如PNGImageDecoder)中传递新的java.util.zip.Inflater(),而不是调用膨胀器的end()。好吧,如果你在构造函数中使用新的,没有机会……是的,如果手动传递为构造函数参数,那么在流上调用close()不会关闭该扩展器。这不是一个真正的泄漏,因为它将由终结器发布……当它认为有必要时。直到那一刻,它严重地吞噬了本机内存,它会导致Linux oom_killer在不受惩罚的情况下杀死进程。主要的问题是,在Java中终结是非常不可靠的,G1使它变得更糟,直到7.0.2。故事的寓意:尽可能快地释放本地资源;终结器太穷了。

  • The same case with java.util.zip.Deflater. This one is far worse since Deflater is memory hungry in Java, i.e. always uses 15 bits (max) and 8 memory levels (9 is max) allocating several hundreds KB of native memory. Fortunately, Deflater is not widely used and to my knowledge JDK contains no misuses. Always call end() if you manually create a Deflater or Inflater. The best part of the last two: you can't find them via normal profiling tools available.

    与java.util.zip.Deflater相同的情况。这是一个非常糟糕的情况,因为在Java中,平减器是内存不足的,它总是使用15个字节(max)和8个内存级别(9是最大的),分配了几百KB的本机内存。幸运的是,Deflater并没有被广泛使用,而且在我的知识中,JDK不包含任何错误。如果您手动创建了一个偏转器或增压器,请始终调用end()。最后两个中的最好的部分:您不能通过正常的分析工具找到它们。

(I can add some more time wasters I have encountered upon request.)

(我可以增加一些我在请求时遇到的浪费时间。)

Good luck and stay safe; leaks are evil!

祝你好运,并保持安全;泄漏是邪恶的!

#5


139  

Most examples here are "too complex". They are edge cases. With these examples, the programmer made a mistake (like don't redefining equals/hashcode), or has been bitten by a corner case of the JVM/JAVA (load of class with static...). I think that's not the type of example an interviewer want or even the most common case.

这里的大多数例子都“太复杂”。他们是边界情况。在这些例子中,程序员犯了一个错误(比如不要重新定义equals/hashcode),或者被JVM/JAVA的一个角例(带有静态的类的负载)所咬。我认为这不是面试官想要的类型,也不是最常见的情况。

But there are really simpler cases for memory leaks. The garbage collector only frees what is no longer referenced. We as Java developers don't care about memory. We allocate it when needed and let it be freed automatically. Fine.

但是对于内存泄漏,有一些非常简单的例子。垃圾收集器只释放不再引用的内容。我们作为Java开发人员并不关心内存。我们在需要时分配它,并让它自动释放。很好。

But any long-lived application tend to have shared state. It can be anything, statics, singletons... Often non-trivial applications tend to make complex objects graphs. Just forgetting to set a reference to null or more often forgetting to remove one object from a collection is enough to make a memory leak.

但是任何长时间使用的应用程序都倾向于共享状态。它可以是任何东西,静态的,单身的…通常,非平凡的应用程序往往会生成复杂的对象图。忘记设置引用为null或更经常忘记从集合中删除一个对象就足以造成内存泄漏。

Of course all sort of listeners (like UI listeners), caches, or any long-lived shared state tend to produce memory leak if not properly handled. What shall be understood is that this is not a Java corner case, or a problem with the garbage collector. It is a design problem. We design that we add a listener to a long-lived object, but we don't remove the listener when no longer needed. We cache objects, but we have no strategy to remove them from the cache.

当然,所有类型的侦听器(如UI侦听器)、缓存或任何长期存在的共享状态都倾向于产生内存泄漏,如果处理不当。应该理解的是,这不是一个Java角的情况,也不是垃圾收集器的问题。这是一个设计问题。我们设计的是将一个监听器添加到一个长期存在的对象中,但是当不再需要时,我们不会删除侦听器。我们缓存对象,但是我们没有策略将它们从缓存中删除。

We maybe have a complex graph that store the previous state that is needed by a computation. But the previous state is itself linked to the state before and so on.

我们可能有一个复杂的图,它存储了计算所需的前一个状态。但是之前的状态本身与状态之前,等等。

Like we have to close SQL connections or files. We need to set proper references to null and remove elements from the collection. We shall have proper caching strategies (maximum memory size, number of elements, or timers). All objects that allow a listener to be notified must provide both a addListener and removeListener method. And when these notifiers are no longer used, they must clear their listener list.

比如我们必须关闭SQL连接或文件。我们需要为null设置适当的引用,并从集合中删除元素。我们将有适当的缓存策略(最大内存大小、元素数量或计时器)。允许通知侦听器的所有对象必须同时提供一个addListener和removeListener方法。当这些通知不再使用时,它们必须清除侦听器列表。

A memory leak is indeed truly possible and is perfectly predictable. No need for special language features or corner cases. Memory leaks are either an indicator that something is maybe missing or even of design problems.

内存泄漏确实是可能的,而且是完全可以预测的。不需要特殊语言功能或角落案例。内存泄漏可能是某个东西可能丢失或者甚至是设计问题的指示器。

#6


130  

The answer depends entirely on what the interviewer thought they were asking.

答案完全取决于面试官认为他们在问什么。

Is it possible in practice to make Java leak? Of course it is, and there are plenty of examples in the other answers.

在实践中是否可能导致Java泄漏?当然,在其他答案中也有很多例子。

But there are multiple meta-questions that may have been being asked?

但是有很多的元问题可能会被问到?

  • Is a theoretically "perfect" Java implementation vulnerable to leaks?
  • 从理论上说,“完美”的Java实现容易被泄露吗?
  • Does the candidate understand the difference between theory and reality?
  • 候选人是否了解理论与现实的区别?
  • Does the candidate understand how garbage collection works?
  • 候选人是否了解垃圾收集的工作原理?
  • Or how garbage collection is supposed to work in an ideal case?
  • 或者垃圾收集在理想情况下应该如何工作?
  • Do they know they can call other languages through native interfaces?
  • 他们知道他们可以通过本地接口调用其他语言吗?
  • Do they know to leak memory in those other languages?
  • 他们知道用其他语言泄露内存吗?
  • Does the candidate even know what memory management is, and what is going on behind the scene in Java?
  • 这位候选人是否知道什么是内存管理,以及在Java的幕后发生了什么?

I'm reading your meta-question as "What's an answer I could have used in this interview situation". And hence, I'm going to focus on interview skills instead of Java. I believe your more likely to repeat the situation of not knowing the answer to a question in an interview than you are to be in a place of needing to know how to make Java leak. So, hopefully, this will help.

我正在读你的元问题,因为“我在面试中可能会用到什么答案”。因此,我将专注于面试技巧而不是Java。我相信,在面试中,你更有可能重复不知道答案的情况,而不是在一个需要知道如何制造Java漏洞的地方。希望这能帮上忙。

One of the most important skills you can develop for interviewing is learning to actively listen to the questions and working with the interviewer to extract their intent. Not only does this let you answer their question the way they want, but also shows that you have some vital communication skills. And when it comes down to a choice between many equally talented developers, I'll hire the one who listens, thinks, and understands before they respond every time.

面试中最重要的技能之一就是学会积极地倾听问题,和面试官一起努力发掘他们的意图。这不仅让你以他们想要的方式回答他们的问题,而且还表明你有一些重要的沟通技巧。在许多同样有才华的开发人员之间做出选择的时候,我会雇佣一个在每次回应之前都会倾听、思考和理解的人。

#7


109  

The following is a pretty pointless example, if you do not understand JDBC. Or at least how JDBC expects a developer to close Connection, Statement and ResultSet instances before discarding them or losing references to them, instead of relying on the implementation of finalize.

如果您不理解JDBC,下面是一个非常没有意义的示例。或者至少JDBC期望开发人员在丢弃它们或丢失对它们的引用之前关闭连接、语句和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);
   }
}

The problem with the above is that the Connection object is not closed, and hence the physical connection will remain open, until the garbage collector comes around and sees that it is unreachable. GC will invoke the finalize method, but there are JDBC drivers that do not implement the finalize, at least not in the same way that Connection.close is implemented. The resulting behavior is that while memory will be reclaimed due to unreachable objects being collected, resources (including memory) associated with the Connection object might simply not be reclaimed.

上面的问题是连接对象没有关闭,因此物理连接将保持打开状态,直到垃圾收集器出现并看到它是不可访问的。GC将调用finalize方法,但是有一些JDBC驱动程序没有实现最终的实现,至少与连接的方式不同。接近实现。由此产生的行为是,虽然内存将被回收,但是与连接对象关联的资源(包括内存)可能不会被回收。

In such an event where the Connection's finalize method does not clean up everything, one might actually find that the physical connection to the database server will last several garbage collection cycles, until the database server eventually figures out that the connection is not alive (if it does), and should be closed.

在这样一个事件连接的确定方法不清理一切,你可能会发现物理连接到数据库服务器将持续几个垃圾收集周期,直到最终数据库服务器数据的连接不是活着(如果是这样),并且应该被关闭。

Even if the JDBC driver were to implement finalize, it is possible for exceptions to be thrown during finalization. The resulting behavior is that any memory associated with the now "dormant" object will not be reclaimed, as finalize is guaranteed to be invoked only once.

即使JDBC驱动程序实现了finalize,也有可能在终结过程中抛出异常。由此产生的行为是,与现在“休眠”对象关联的任何内存都不会被回收,因为finalize保证只被调用一次。

The above scenario of encountering exceptions during object finalization is related to another other scenario that could possibly lead to a memory leak - object resurrection. Object resurrection is often done intentionally by creating a strong reference to the object from being finalized, from another object. When object resurrection is misused it will lead to a memory leak in combination with other sources of memory leaks.

上述在对象终结过程中遇到异常的场景与另一个可能导致内存泄漏-对象复活的场景有关。对象的复活通常是有意的,通过对被最终定稿的对象,从另一个对象中创建一个强烈的引用。当对象复活被误用时,它会与其他内存泄漏源一起导致内存泄漏。

There are plenty more examples that you can conjure up - like

还有很多你可以想象的例子。

  • Managing a List instance where you are only adding to the list and not deleting from it (although you should be getting rid of elements you no longer need), or
  • 管理一个列表实例,其中您只添加到列表中,而不是从它中删除(尽管您应该删除不再需要的元素),或者。
  • Opening Sockets or Files, but not closing them when they are no longer needed (similar to the above example involving the Connection class).
  • 打开套接字或文件,但在不再需要时关闭它们(类似于上面的连接类示例)。
  • Not unloading Singletons when bringing down a Java EE application. Apparently, the Classloader that loaded the singleton class will retain a reference to the class, and hence the singleton instance will never be collected. When a new instance of the application is deployed, a new class loader is usually created, and the former class loader will continue to exist due to the singleton.
  • 在引入Java EE应用程序时,不卸载单例。显然,加载单例类的类加载器将保留对类的引用,因此不会收集singleton实例。当应用程序的新实例被部署时,通常会创建一个新的类装入器,而以前的类装入器将继续存在,因为单例。

#8


98  

Probably one of the simplest examples of a potential memory leak, and how to avoid it, is the implementation of ArrayList.remove(int):

可能是内存泄漏的最简单的例子之一,以及如何避免它,是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;
}

If you were implementing it yourself, would you have thought to clear the array element that is no longer used (elementData[--size] = null)? That reference might keep a huge object alive ...

如果您自己正在实现它,您是否想要清除已不再使用的数组元素(elementData[- size] = null)?这个引用可能会让一个巨大的对象存活……

#9


62  

Any time you keep references around to objects that you no longer need you have a memory leak. See Handling memory leaks in Java programs for examples of how memory leaks manifest themselves in Java and what you can do about it.

任何时候,只要将引用保存到不再需要的对象,就会出现内存泄漏。请参阅Java程序中处理内存泄漏的示例,以了解内存泄漏如何在Java中表现出来,以及您可以对此做些什么。

#10


42  

You are able to make memory leak with sun.misc.Unsafe class. In fact this service class is used in different standard classes (for example in java.nio classes). You can't create instance of this class directly, but you may use reflection to do that.

你可以用sun.misc进行内存泄漏。不安全的类。实际上,这个服务类被用于不同的标准类(例如在java中)。nio类)。您不能直接创建该类的实例,但是您可以使用反射来做到这一点。

Code doesn't compile in Eclipse IDE - compile it using command javac (during compilation you'll get warnings)

代码不能在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();
        }
    }

}

#11


35  

I can copy my answer from here: Easiest way to cause memory leak in Java?

我可以从这里复制我的答案:在Java中导致内存泄漏的最简单方法?

"A memory leak, in computer science (or leakage, in this context), occurs when a computer program consumes memory but is unable to release it back to the operating system." (Wikipedia)

“内存泄漏,在计算机科学(或泄漏,在这种情况下),发生在计算机程序消耗内存但无法将其释放回操作系统。”(*)

The easy answer is: You can't. Java does automatic memory management and will free resources that are not needed for you. You can't stop this from happening. It will ALWAYS be able to release the resources. In programs with manual memory management, this is different. You cann get some memory in C using malloc(). To free the memory, you need the pointer that malloc returned and call free() on it. But if you don't have the pointer anymore (overwritten, or lifetime exceeded), then you are unfortunately incapable of freeing this memory and thus you have a memory leak.

简单的回答是:你不能。Java实现了自动内存管理,并将免费提供不需要的资源。你不能阻止这一切的发生。它总是能够释放资源。在使用手动内存管理的程序中,这是不同的。使用malloc()可以在C中获得一些内存。要释放内存,需要malloc返回的指针,并在其上调用free()。但是,如果您不再拥有指针(覆盖了,或者超过了生命周期),那么不幸的是,您无法释放内存,从而导致内存泄漏。

All the other answers so far are in my definition not really memory leaks. They all aim at filling the memory with pointless stuff real fast. But at any time you could still dereference the objects you created and thus freeing the memory --> NO LEAK. acconrad's answer comes pretty close though as I have to admit since his solution is effectively to just "crash" the garbage collector by forcing it in an endless loop).

到目前为止,所有其他的答案都在我的定义中,而不是真正的内存泄漏。他们的目标都是快速地把那些无意义的东西填满记忆。但是,在任何时候,您仍然可以取消您创建的对象,从而释放内存—>没有泄漏。acconrad的回答非常接近,尽管我不得不承认,因为他的解决方案实际上是通过将垃圾收集器强制循环来“崩溃”。

The long answer is: You can get a memory leak by writing a library for Java using the JNI, which can have manual memory management and thus have memory leaks. If you call this library, your java process will leak memory. Or, you can have bugs in the JVM, so that the JVM looses memory. There are probably bugs in the JVM, there may even be some known ones since garbage collection is not that trivial, but then it's still a bug. By design this is not possible. You may be asking for some java code that is effected by such a bug. Sorry I don't know one and it might well not be a bug anymore in the next Java version anyway.

长期的答案是:您可以通过使用JNI编写Java库来获得内存泄漏,JNI可以进行手动内存管理,从而导致内存泄漏。如果您调用这个库,您的java进程将会泄漏内存。或者,您可以在JVM中有bug,这样JVM就会丢失内存。在JVM中可能存在bug,甚至可能有一些已知的bug,因为垃圾收集并不是那么简单,但是它仍然是一个bug。通过设计,这是不可能的。您可能需要一些java代码,这些代码是由这样的bug影响的。不好意思,我不知道,下一个Java版本可能也不会是bug了。

#12


31  

Here's a simple/sinister one via http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29.

下面是一个简单/邪恶的例子: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);
    }
}

Because the substring refers to the internal representation of the original, much longer string, the original stays in memory. Thus, as long as you have a StringLeaker in play, you have the whole original string in memory, too, even though you might think you're just holding on to a single-character string.

因为子字符串表示原始的、更长的字符串的内部表示,而原来的字符串保留在内存中。因此,只要在游戏中有一个StringLeaker,在内存中也有完整的原始字符串,尽管您可能认为您只是在坚持一个单字字符串。

The way to avoid storing an unwanted reference to the original string is to do something like this:

避免将不想要的引用存储到原始字符串的方法是这样做:

...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...

For added badness, you might also .intern() the substring:

对于添加的badness,您还可以.intern()子字符串:

...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...

Doing so will keep both the original long string and the derived substring in memory even after the StringLeaker instance has been discarded.

这样做将保留原来的长字符串和在内存中派生的子字符串,即使在StringLeaker实例被丢弃之后。

#13


30  

Take any web application running in any servlet container (Tomcat, Jetty, Glassfish, whatever...). Redeploy the app 10 or 20 times in a row (it may be enough to simply touch the WAR in the server's autodeploy directory.

在任何servlet容器中运行任何web应用程序(Tomcat、Jetty、Glassfish,等等)。将应用程序重新部署10次或20次(它可能足以简单地在服务器的自动部署目录中触发战争)。

Unless anybody has actually tested this, chances are high that you'll get an OutOfMemoryError after a couple of redeployments, because the application did not take care to clean up after itself. You may even find a bug in your server with this test.

除非有人实际测试过这种情况,否则在两次重新部署之后,您将获得OutOfMemoryError错误的几率非常高,因为应用程序本身并没有进行清理。您甚至可能在您的服务器中发现一个错误。

The problem is, the lifetime of the container is longer than the lifetime of your application. You have to make sure that all references the container might have to objects or classes of your application can be garbage collected.

问题是,容器的生命周期比应用程序的生命周期长。您必须确保容器可能对您的应用程序的对象或类的所有引用都是垃圾收集的。

If there is just one reference surviving the undeployment of your web app, the corresponding classloader and by consequence all classes of your web app cannot be garbage collected.

如果你的web应用程序没有部署,那么相应的类加载器和你的web应用程序的所有类都不能被垃圾收集。

Threads started by your application, ThreadLocal variables, logging appenders are some of the usual suspects to cause classloader leaks.

应用程序启动的线程、ThreadLocal变量、日志应用程序都是导致类加载器泄漏的常见疑点。

#14


29  

A common example of this in GUI code is when creating a widget/component and adding a listener to some static/application scoped object and then not removing the listener when the widget is destroyed. Not only do you get a memory leak, but also a performance hit as when whatever you are listening to fires events, all your old listeners are called too.

在GUI代码中,一个常见的例子是创建一个小部件/组件,并将侦听器添加到某个静态/应用程序范围的对象,然后在小部件被销毁时不删除侦听器。不仅会出现内存泄漏,而且还会出现性能问题,就像您正在监听的任何事件一样,您的老侦听器也会被调用。

#15


27  

Maybe by using external native code through JNI?

也许通过JNI使用外部原生代码?

With pure Java, it is almost impossible.

使用纯Java,这几乎是不可能的。

But that is about a "standard" type of memory leak, when you cannot access the memory anymore, but it is still owned by the application. You can instead keep references to unused objects, or open streams without closing them afterwards.

但是,这是一个“标准”类型的内存泄漏,当您无法访问内存时,它仍然属于应用程序。相反,您可以保留对未使用对象的引用,或者在不关闭的情况下打开流。

#16


25  

I have had a nice "memory leak" in relation to PermGen and XML parsing once. The XML parser we used (I can't remember which one it was) did a String.intern() on tag names, to make comparison faster. One of our customers had the great idea to store data values not in XML attributes or text, but as tagnames, so we had a document like:

对于PermGen和XML解析,我有过一次很好的“内存泄漏”。我们使用的XML解析器(我不记得是哪一个)在标记名上做了一个String.intern(),以便更快地进行比较。我们的一个客户有一个很棒的主意,可以将数据值存储在非XML属性或文本中,但是作为tagname,我们有这样的文档:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

In fact, they did not use numbers but longer textual IDs (around 20 characters), which were unique and came in at a rate of 10-15 million a day. That makes 200 MB of rubbish a day, which is never needed again, and never GCed (since it is in PermGen). We had permgen set to 512 MB, so it took around two days for the out-of-memory exception (OOME) to arrive...

事实上,他们没有使用数字,而是使用了更长的文本id(大约20个字符),这是唯一的,并且以每天10-15万的速度出现。这使得每天有200 MB的垃圾,再也不需要了,而且从来没有GCed(因为它在PermGen中)。我们将permgen设置为512 MB,因此内存溢出异常(OOME)在大约2天内到达……

#17


20  

I recently encountered a memory leak situation caused in a way by log4j.

我最近遇到了一个由log4j引起的内存泄漏情况。

Log4j has this mechanism called Nested Diagnostic Context(NDC) which is an instrument to distinguish interleaved log output from different sources. The granularity at which NDC works is threads, so it distinguishes log outputs from different threads separately.

Log4j有一个称为嵌套诊断上下文(NDC)的机制,它是一种区分不同来源的交叉日志输出的工具。NDC工作的粒度是线程,因此它区分不同线程的日志输出。

In order to store thread specific tags, log4j's NDC class uses a Hashtable which is keyed by the Thread object itself (as opposed to say the thread id), and thus till the NDC tag stays in memory all the objects that hang off of the thread object also stay in memory. In our web application we use NDC to tag logoutputs with a request id to distinguish logs from a single request separately. The container that associates the NDC tag with a thread, also removes it while returning the response from a request. The problem occurred when during the course of processing a request, a child thread was spawned, something like the following code:

为了存储线程特定标记,log4j NDC类使用一个哈希表的键控的线程对象本身(而不是说线程id),因此直到NDC标签保持在内存中所有挂起的线程对象的对象也保持在内存中。在我们的web应用程序中,我们使用NDC来标记带有请求id的日志输出,以分别从单个请求中区分日志。将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();
    }
}    

So an NDC context was associated with inline thread that was spawned. The thread object that was the key for this NDC context, is the inline thread which has the hugeList object hanging off of it. Hence even after the thread finished doing what it was doing, the reference to the hugeList was kept alive by the NDC context Hastable, thus causing a memory leak.

因此,NDC上下文与派生的内联线程相关联。线程对象是这个NDC上下文的关键,它是一个包含hugeList对象的内联线程。因此,即使在线程完成了它所做的工作之后,对hugeList的引用仍然被NDC上下文保存,从而导致了内存泄漏。

#18


19  

I thought it was interesting that no one used the internal class examples. If you have an internal class; it inherently maintains a reference to the containing class. Of course it is not technically a memory leak because Java WILL eventually clean it up; but this can cause classes to hang around longer than anticipated.

我觉得有趣的是没有人使用内部类的例子。如果你有一个内部类;它本质上维护对包含类的引用。当然,这在技术上不是一个内存泄漏,因为Java最终会清理它;但这可能会导致类的延迟时间比预期的长。

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

Now if you call Example1 and get an Example2 discarding Example1, you will inherently still have a link to an Example1 object.

现在,如果您调用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.
  }
}

I've also heard a rumor that if you have a variable that exists for longer than a specific amount of time; Java assumes that it will always exist and will actually never try to clean it up if cannot be reached in code anymore. But that is completely unverified.

我也听过这样的传言,如果你有一个变量,它存在的时间比特定的时间长;Java假定它将永远存在,而且如果不能在代码中找到它,它将永远不会尝试去清理它。但这是完全未经证实的。

#19


16  

What's a memory leak:

什么是内存泄漏:

  • It's caused by a bug or bad design.
  • 它是由错误或错误的设计引起的。
  • It's a waste of memory.
  • 这是在浪费记忆。
  • It gets worse over time.
  • 随着时间的推移,情况会变得更糟。
  • The garbage collector cannot clean it.
  • 垃圾收集器无法清理它。

Typical example:

典型的例子:

A cache of objects is a good starting point to mess things up.

缓存对象是把事情搞糟的好起点。

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;
}

Your cache grows and grows. And pretty soon the entire database gets sucked into memory. A better design uses an LRUMap (Only keeps recently used objects in cache).

缓存增长和增长。很快整个数据库就被内存占用了。更好的设计使用LRUMap(只在缓存中保存最近使用的对象)。

Sure, you can make things a lot more complicated:

当然,你可以让事情变得复杂得多:

  • using ThreadLocal constructions.
  • 使用ThreadLocal结构。
  • adding more complex reference trees.
  • 添加更复杂的参考树。
  • or leaks caused by 3rd party libraries.
  • 或由第三方图书馆造成的泄露。

What often happens:

经常发生:

If this Info object has references to other objects, which again have references to other objects. In a way you could also consider this to be some kind of memory leak, (caused by bad design).

如果这个信息对象引用了其他对象,那么它也会引用其他对象。在某种程度上,您还可以认为这是某种内存泄漏(由糟糕的设计引起)。

#20


15  

Create a static Map and keep adding hard references to it. Those will never be GC'd.

创建一个静态映射,并不断添加对它的硬引用。那些永远不会是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); }
}

#21


14  

You can create a moving memory leak by creating a new instance of a class in that class's finalize method. Bonus points if the finalizer creates multiple instances. Here's a simple program that leaks the entire heap in sometime between a few seconds and a few minutes depending on your heap size:

您可以通过在该类的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


13  

As a lot of people have suggested, Resource Leaks are fairly easy to cause - like the JDBC examples. Actual Memory leaks are a bit harder - especially if you aren't relying on broken bits of the JVM to do it for you...

正如许多人所建议的那样,资源泄漏是相当容易引起的——就像JDBC示例一样。实际内存泄漏有点困难,特别是如果您不依赖JVM的碎片来为您做这些事情……

The ideas of creating objects that have a very large footprint and then not being able to access them aren't real memory leaks either. If nothing can access it then it will be garbage collected, and if something can access it then it's not a leak...

创建具有很大内存空间的对象,然后不能访问它们的想法也不是真正的内存泄漏。如果没有任何东西可以访问它,那么它将被垃圾收集,如果有什么东西可以访问它,那么它就不是一个漏洞……

One way that used to work though - and I don't know if it still does - is to have a three-deep circular chain. As in Object A has a reference to Object B, Object B has a reference to Object C and Object C has a reference to Object A. The GC was clever enough to know that a two deep chain - as in A <--> B - can safely be collected if A and B aren't accessible by anything else, but couldn't handle the three-way chain...

有一种方法,虽然我不知道它是否仍然有效,就是有一个三深的循环链。在对象有一个引用对象B,B的引用对象C和C对象的引用对象A GC是够聪明,知道两个深链——< - - > B - A和B可以安全地收集如果不是由其他访问,但不能处理三方链…

#23


13  

I came across a more subtle kind of resource leak recently. We open resources via class loader's getResourceAsStream and it happened that the input stream handles were not closed.

我最近遇到了一种更微妙的资源泄漏。我们通过类装入器的getresour流来打开资源,并发生了输入流句柄未关闭的情况。

Uhm, you might say, what an idiot.

嗯,你可能会说,真是个白痴。

Well, what makes this interesting is: this way, you can leak heap memory of the underlying process, rather than from JVM's heap.

让这个有趣的是:通过这种方式,您可以泄漏底层进程的堆内存,而不是从JVM堆中。

All you need is a jar file with a file inside which will be referenced from Java code. The bigger the jar file, the quicker memory gets allocated.

您所需要的是一个jar文件,其中包含一个从Java代码中引用的文件。jar文件越大,分配的内存就越快。

You can easily create such a jar with the following class:

您可以轻松地在以下类中创建这样一个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();
    }
}

Just paste into a file named BigJarCreator.java, compile and run it from command line:

直接粘贴到一个名为BigJarCreator的文件中。java,编译并运行命令行:

javac BigJarCreator.java
java -cp . BigJarCreator

Et voilà: you find a jar archive in your current working directory with two files inside.

在当前工作目录中找到一个jar存档,其中包含两个文件。

Let's create a second class:

让我们创建第二个类:

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);
    }

}

This class basically does nothing, but create unreferenced InputStream objects. Those objects will be garbage collected immediately and thus, do not contribute to heap size. It is important for our example to load an existing resource from a jar file, and size does matter here!

这个类基本上什么都不做,但是创建未引用的InputStream对象。这些对象将立即被垃圾收集,因此不会导致堆大小。对于我们的示例来说,从jar文件加载现有资源是很重要的,而且大小在这里很重要!

If you're doubtful, try to compile and start the class above, but make sure to chose a decent heap size (2 MB):

如果您有疑问,请尝试编译并启动上面的类,但是一定要选择适当的堆大小(2 MB):

javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak

You will not encounter an OOM error here, as no references are kept, the application will keep running no matter how large you chose ITERATIONS in the above example. The memory consumption of your process (visible in top (RES/RSS) or process explorer) grows unless the application gets to the wait command. In the setup above, it will allocate around 150 MB in memory.

在这里,您将不会遇到OOM错误,因为没有保存任何引用,应用程序将继续运行,无论您在上面的示例中选择了多大的迭代。除非应用程序到达等待命令,否则进程的内存消耗(在top (RES/RSS)或进程浏览器中可见)增长。在上面的设置中,它将在内存中分配大约150 MB。

If you want the application to play safe, close the input stream right where it's created:

如果您希望应用程序安全运行,请关闭它所创建的输入流:

MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();

and your process will not exceed 35 MB, independent of the iteration count.

并且您的进程不会超过35 MB,独立于迭代计数。

Quite simple and surprising.

非常简单和令人惊讶的。

#24


12  

I don't think anyone has said this yet: you can resurrect an object by overriding the finalize() method such that finalize() stores a reference of this somewhere. The garbage collector will only be called once on the object so after that the object will never destroyed.

我认为还没有人这样说过:您可以通过重写finalize()方法来恢复对象,这样finalize()就可以在某个地方存储这个引用。垃圾回收器只会在对象上被调用一次,因此之后对象将不会被销毁。

#25


12  

Everyone always forgets the native code route. Here's a simple formula for a leak:

每个人总是忘记本地代码路由。这里有一个简单的泄漏公式:

  1. Declare native method.
  2. 声明本机方法。
  3. In native method, call malloc. Don't call free.
  4. 在本机方法中,调用malloc。别叫*。
  5. Call the native method.
  6. 调用本机方法。

Remember, memory allocations in native code come from the JVM heap.

请记住,本机代码中的内存分配来自JVM堆。

#26


12  

Threads are not collected until they terminate. They serve as roots of garbage collection. They are one of the few objects that won't be reclaimed simply by forgetting about them or clearing references to them.

线程在终止之前不会被收集。它们是垃圾收集的根。它们是少数几个不能简单地通过忘记它们或清除对它们的引用而被回收的对象之一。

Consider: the basic pattern to terminate a worker thread is to set some condition variable seen by the thread. The thread can check the variable periodically and use that as a signal to terminate. If the variable is not declared volatile, then the change to the variable might not be seen by the thread, so it won't know to terminate. Or imagine if some threads want to update a shared object, but deadlock while trying to lock on it.

考虑:终止一个工作线程的基本模式是设置线程所看到的某个条件变量。线程可以周期性地检查变量,并使用它作为终止的信号。如果变量没有被声明为不稳定,那么对变量的更改可能不会被线程看到,所以它不知道要终止。或者想象一下,如果某些线程想要更新共享对象,但是在试图锁定它时却陷入了死锁。

If you only have a handful of threads these bugs will probably be obvious because your program will stop working properly. If you have a thread pool that creates more threads as needed, then the obsolete/stuck threads might not be noticed, and will accumulate indefinitely, causing a memory leak. Threads are likely to use other data in your application, so will also prevent anything they directly reference from ever being collected.

如果您只有少量的线程,这些错误可能会很明显,因为您的程序将停止正常工作。如果您有一个线程池,它可以根据需要创建更多的线程,那么过时的/阻塞的线程可能不会被注意到,并且会无限累积,导致内存泄漏。线程可能会在您的应用程序中使用其他数据,因此也将防止任何它们直接引用的数据被收集。

As a toy example:

作为一个玩具的例子:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

Call System.gc() all you like, but the object passed to leakMe will never die.

调用System.gc()您所喜欢的,但是传递给leakMe的对象将不会死。

(*edited*)

(* *)编辑

#27


11  

there are many different situations memory will leak. One i encountered, which expose a map that should not be exposed and used in other place.

内存泄漏有很多不同的情况。我遇到的一个,它暴露了一个不应该暴露在其他地方使用的地图。

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  

The interviewer was probably looking for a circular reference like the code below (which incidentally only leak memory in very old JVMs that used reference counting, which isn't the case any more). But it's a pretty vague question, so it's a prime opportunity to show off your understanding of JVM memory management.

面试官可能在寻找一种循环引用,比如下面的代码(顺便说一下,它只是在使用引用计数的旧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 */
}

Then you can explain that with reference counting, the above code would leak memory. But most modern JVMs don't use reference counting any longer, most use a sweep garbage collector, which will in fact collect this memory.

然后您可以用引用计数来解释,上面的代码会泄漏内存。但是大多数现代jvm不再使用引用计数,大多数使用的是清除垃圾收集器,实际上它将收集这些内存。

Next you might explain creating an Object that has an underlying native resource, like this:

接下来,您可能会解释创建一个具有底层原生资源的对象:

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. */
    }
}

Then you can explain this is technically a memory leak, but really the leak is caused by native code in the JVM allocating underlying native resources, which weren't freed by your Java code.

然后您可以解释这在技术上是一个内存泄漏,但是真正的泄漏是由JVM分配底层本地资源的本地代码引起的,而这些本地资源并不是由Java代码释放出来的。

At the end of the day, with a modern JVM, you need to write some Java code that allocates a native resource outside the normal scope of the JVM's awareness.

在一天结束时,使用现代JVM,您需要编写一些Java代码,在JVM意识的正常范围之外分配本机资源。

#29


9  

I think that a valid example could be using ThreadLocal variables in an environment where threads are pooled.

我认为一个有效的示例可以在线程池的环境中使用ThreadLocal变量。

For instance, using ThreadLocal variables in Servlets to communicate with other web components, having the threads being created by the container and maintaining the idle ones in a pool. ThreadLocal variables, if not correctly cleaned up, will live there until, possibly, the same web component overwrites their values.

例如,在servlet中使用ThreadLocal变量与其他web组件通信,使线程由容器创建并维护池中的空闲线程。ThreadLocal变量(如果没有正确清理)将在那里生存,直到可能,相同的web组件重写它们的值。

Of course, once identified, the problem can be solved easily.

当然,一旦确定,问题就可以很容易地解决。

#30


9  

The interviewer might have be looking for a circular reference solution:

面试官可能在寻找一个循环的参考答案:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

This is a classic problem with reference counting garbage collectors. You would then politely explain that JVMs use a much more sophisticated algorithm that doesn't have this limitation.

这是一个引用计数垃圾收集器的经典问题。然后,您将礼貌地解释,jvm使用了一种更复杂的算法,它没有这种限制。

-Wes Tarle

韦斯Tarle

#1


1867  

Here's a good way to create a true memory leak (objects inaccessible by running code but still stored in memory) in pure Java:

这里有一个很好的方法来创建一个真正的内存泄漏(运行代码无法访问的对象,但仍然存储在内存中):

  1. The application creates a long-running thread (or use a thread pool to leak even faster).
  2. 应用程序创建一个长时间运行的线程(或者使用线程池来更快地泄漏)。
  3. The thread loads a class via an (optionally custom) ClassLoader.
  4. 线程通过一个(可选自定义的)类装入器装入一个类。
  5. The class allocates a large chunk of memory (e.g. new byte[1000000]), stores a strong reference to it in a static field, and then stores a reference to itself in a ThreadLocal. Allocating the extra memory is optional (leaking the Class instance is enough), but it will make the leak work that much faster.
  6. 类分配了大量内存(例如,new byte[1000000]),在静态字段中存储强引用,然后在ThreadLocal中存储对自身的引用。分配额外内存是可选的(泄漏类实例就足够了),但是它会使泄漏工作更快。
  7. The thread clears all references to the custom class or the ClassLoader it was loaded from.
  8. 该线程清除所有对自定义类或其加载的类加载器的引用。
  9. Repeat.
  10. 重复。

This works because the ThreadLocal keeps a reference to the object, which keeps a reference to its Class, which in turn keeps a reference to its ClassLoader. The ClassLoader, in turn, keeps a reference to all the Classes it has loaded.

这可以工作,因为ThreadLocal保存对对象的引用,该对象保存对它的类的引用,而该对象又保留对它的类加载器的引用。类加载器依次保存对它所装载的所有类的引用。

(It was worse in many JVM implementations, especially prior to Java 7, because Classes and ClassLoaders were allocated straight into permgen and were never GC'd at all. However, regardless of how the JVM handles class unloading, a ThreadLocal will still prevent a Class object from being reclaimed.)

(在许多JVM实现中,尤其是在Java 7之前,情况更糟,因为类和类加载器被直接分配到permgen中,而从来没有GC。然而,不管JVM如何处理类卸载,ThreadLocal仍然会阻止类对象被回收。

A variation on this pattern is why application containers (like Tomcat) can leak memory like a sieve if you frequently redeploy applications that happen to use ThreadLocals in any way. (Since the application container uses Threads as described, and each time you redeploy the application a new ClassLoader is used.)

这种模式的变体就是为什么应用程序容器(如Tomcat)可以像筛子一样泄漏内存,如果您频繁地重新部署使用threadlocal的应用程序的话。(因为应用程序容器使用了描述的线程,并且每次重新部署应用程序时都会使用新的类加载器。)

Update: Since lots of people keep asking for it, here's some example code that shows this behavior in action.

更新:由于许多人不断地要求它,这里有一些示例代码显示了这种行为。

#2


1024  

Static field holding object reference [esp final field]

静态字段保存对象引用[esp final字段]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

Calling String.intern() on lengthy String

在长字符串中调用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();

(Unclosed) open streams ( file , network etc... )

(未关闭)开放流(文件、网络等)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Unclosed connections

打开连接

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Areas that are unreachable from JVM's garbage collector, such as memory allocated through native methods

从JVM的垃圾收集器中无法访问的区域,例如通过本机方法分配的内存。

In web applications, some objects are stored in application scope until the application is explicitly stopped or removed.

在web应用程序中,有些对象存储在应用程序范围中,直到应用程序被显式地停止或删除。

getServletContext().setAttribute("SOME_MAP", map);

Incorrect or inappropriate JVM options, such as the noclassgc option on IBM JDK that prevents unused class garbage collection

不正确或不适当的JVM选项,例如IBM JDK上的noclassgc选项,可以防止未使用的类垃圾收集。

See IBM jdk settings.

看到IBM jdk设置。

#3


382  

A simple thing to do is to use a HashSet with an incorrect (or non-existent) hashCode() or equals(), and then keep adding "duplicates". Instead of ignoring duplicates as it should, the set will only ever grow and you won't be able to remove them.

简单的做法是使用一个带有不正确(或不存在)hashCode()或equals()的HashSet,然后继续添加“重复”。这个集合不会像它应该那样忽略重复,而是只会增长,而你将无法删除它们。

If you want these bad keys/elements to hang around you can use a static field like

如果您希望这些坏键/元素挂在您的周围,可以使用静态字段。

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.

#4


220  

Below there will be a non-obvious case where Java leaks, besides the standard case of forgotten listeners, static references, bogus/modifiable keys in hashmaps, or just threads stuck without any chance to end their life-cycle.

下面将会有一个不明显的例子,Java泄漏,除了被遗忘的侦听器的标准案例,静态引用,hashmap中的伪/可修改键,或者只是线程被卡住,没有任何机会结束它们的生命周期。

  • File.deleteOnExit() - always leaks the string, if the string is a substring, the leak is even worse (the underlying char[] is also leaked) - in Java 7 substring also copies the char[], so the later doesn't apply; @Daniel, no needs for votes, though.
  • File.deleteOnExit()—总是泄漏字符串,如果字符串是子字符串,则泄漏更严重(底层的char[]也被泄漏)——在Java 7子字符串中也会复制char[],因此后面不应用;但是,丹尼尔,不需要投票。

I'll concentrate on threads to show the danger of unmanaged threads mostly, don't wish to even touch swing.

我将重点关注线程,以显示非托管线程的危险,甚至不希望触摸swing。

  • Runtime.addShutdownHook and not remove... and then even with removeShutdownHook due to a bug in ThreadGroup class regarding unstarted threads it may not get collected, effectively leak the ThreadGroup. JGroup has the leak in GossipRouter.

    运行时。addShutdownHook而不是删除…然后,即使是removeShutdownHook,由于ThreadGroup类中关于未启动线程的bug,它可能不会被收集,从而有效地泄漏ThreadGroup。JGroup泄露了流言蜚语。

  • Creating, but not starting, a Thread goes into the same category as above.

    创建,但不启动,一个线程进入与上面相同的类别。

  • Creating a thread inherits the ContextClassLoader and AccessControlContext, plus the ThreadGroup and any InheritedThreadLocal, all those references are potential leaks, along with the entire classes loaded by the classloader and all static references, and ja-ja. The effect is especially visible with the entire j.u.c.Executor framework that features a super simple ThreadFactory interface, yet most developers have no clue of the lurking danger. Also a lot of libraries do start threads upon request (way too many industry popular libraries).

    创建一个线程继承ContextClassLoader和AccessControlContext,加上ThreadGroup和任何继承threadlocal,所有这些引用都是潜在的泄漏,以及类加载器和所有静态引用和ja-ja加载的整个类。这种效果在整个j.u.c中尤其明显。具有超级简单的ThreadFactory接口的Executor框架,但大多数开发人员都不知道潜在的危险。此外,许多库在请求时也会启动线程(这是太多业界流行的库)。

  • ThreadLocal caches; those are evil in many cases. I am sure everyone has seen quite a bit of simple caches based on ThreadLocal, well the bad news: if the thread keeps going more than expected the life the context ClassLoader, it is a pure nice little leak. Do not use ThreadLocal caches unless really needed.

    ThreadLocal缓存;在很多情况下,这些都是邪恶的。我相信每个人都已经看到了一些基于ThreadLocal的简单缓存,但是坏消息是:如果线程的运行比预期的要多,那么上下文类加载器就是一个纯粹的小漏洞。除非确实需要,否则不要使用ThreadLocal缓存。

  • Calling ThreadGroup.destroy() when the ThreadGroup has no threads itself, but it still keeps child ThreadGroups. A bad leak that will prevent the ThreadGroup to remove from its parent, but all the children become un-enumerateable.

    当ThreadGroup本身没有线程时,调用ThreadGroup.destroy(),但它仍然保留子线程组。一个糟糕的泄漏将阻止ThreadGroup从其父节点中删除,但是所有的子节点都无法枚举。

  • Using WeakHashMap and the value (in)directly references the key. This is a hard one to find without a heap dump. That applies to all extended Weak/SoftReference that might keep a hard reference back to the guarded object.

    使用WeakHashMap和值(in)直接引用该键。如果没有堆转储,这是很难找到的。这适用于所有扩展的弱/SoftReference,它可能会对被保护对象保持一个严格的引用。

  • Using java.net.URL with the HTTP(S) protocol and loading the resource from(!). This one is special, the KeepAliveCache creates a new thread in the system ThreadGroup which leaks the current thread's context classloader. The thread is created upon the first request when no alive thread exists, so either you may get lucky or just leak. The leak is already fixed in Java 7 and the code that creates thread properly removes the context classloader. There are few more cases (like ImageFetcher, also fixed) of creating similar threads.

    使用java.net.URL与HTTP(S)协议并从(!)加载资源。这个是特殊的,KeepAliveCache在系统ThreadGroup中创建一个新线程,该线程会泄漏当前线程的上下文类加载器。线程是在没有生命线程存在时创建的,因此您可能会幸运或只是泄漏。泄漏已经在Java 7中修复了,而创建线程的代码将正确地删除上下文类加载器。在创建类似的线程时,很少有其他的情况(像ImageFetcher,同样是固定的)。

  • Using InflaterInputStream passing new java.util.zip.Inflater() in the constructor (PNGImageDecoder for instance) and not calling end() of the inflater. Well, if you pass in the constructor with just new, no chance... And yes, calling close() on the stream does not close the inflater if it's manually passed as constructor parameter. This is not a true leak since it'd be released by the finalizer... when it deems it necessary. Till that moment it eats native memory so badly it can cause Linux oom_killer to kill the process with impunity. The main issue is that finalization in Java is very unreliable and G1 made it worse till 7.0.2. Moral of the story: release native resources as soon as you can; the finalizer is just too poor.

    使用InflaterInputStream在构造函数(例如PNGImageDecoder)中传递新的java.util.zip.Inflater(),而不是调用膨胀器的end()。好吧,如果你在构造函数中使用新的,没有机会……是的,如果手动传递为构造函数参数,那么在流上调用close()不会关闭该扩展器。这不是一个真正的泄漏,因为它将由终结器发布……当它认为有必要时。直到那一刻,它严重地吞噬了本机内存,它会导致Linux oom_killer在不受惩罚的情况下杀死进程。主要的问题是,在Java中终结是非常不可靠的,G1使它变得更糟,直到7.0.2。故事的寓意:尽可能快地释放本地资源;终结器太穷了。

  • The same case with java.util.zip.Deflater. This one is far worse since Deflater is memory hungry in Java, i.e. always uses 15 bits (max) and 8 memory levels (9 is max) allocating several hundreds KB of native memory. Fortunately, Deflater is not widely used and to my knowledge JDK contains no misuses. Always call end() if you manually create a Deflater or Inflater. The best part of the last two: you can't find them via normal profiling tools available.

    与java.util.zip.Deflater相同的情况。这是一个非常糟糕的情况,因为在Java中,平减器是内存不足的,它总是使用15个字节(max)和8个内存级别(9是最大的),分配了几百KB的本机内存。幸运的是,Deflater并没有被广泛使用,而且在我的知识中,JDK不包含任何错误。如果您手动创建了一个偏转器或增压器,请始终调用end()。最后两个中的最好的部分:您不能通过正常的分析工具找到它们。

(I can add some more time wasters I have encountered upon request.)

(我可以增加一些我在请求时遇到的浪费时间。)

Good luck and stay safe; leaks are evil!

祝你好运,并保持安全;泄漏是邪恶的!

#5


139  

Most examples here are "too complex". They are edge cases. With these examples, the programmer made a mistake (like don't redefining equals/hashcode), or has been bitten by a corner case of the JVM/JAVA (load of class with static...). I think that's not the type of example an interviewer want or even the most common case.

这里的大多数例子都“太复杂”。他们是边界情况。在这些例子中,程序员犯了一个错误(比如不要重新定义equals/hashcode),或者被JVM/JAVA的一个角例(带有静态的类的负载)所咬。我认为这不是面试官想要的类型,也不是最常见的情况。

But there are really simpler cases for memory leaks. The garbage collector only frees what is no longer referenced. We as Java developers don't care about memory. We allocate it when needed and let it be freed automatically. Fine.

但是对于内存泄漏,有一些非常简单的例子。垃圾收集器只释放不再引用的内容。我们作为Java开发人员并不关心内存。我们在需要时分配它,并让它自动释放。很好。

But any long-lived application tend to have shared state. It can be anything, statics, singletons... Often non-trivial applications tend to make complex objects graphs. Just forgetting to set a reference to null or more often forgetting to remove one object from a collection is enough to make a memory leak.

但是任何长时间使用的应用程序都倾向于共享状态。它可以是任何东西,静态的,单身的…通常,非平凡的应用程序往往会生成复杂的对象图。忘记设置引用为null或更经常忘记从集合中删除一个对象就足以造成内存泄漏。

Of course all sort of listeners (like UI listeners), caches, or any long-lived shared state tend to produce memory leak if not properly handled. What shall be understood is that this is not a Java corner case, or a problem with the garbage collector. It is a design problem. We design that we add a listener to a long-lived object, but we don't remove the listener when no longer needed. We cache objects, but we have no strategy to remove them from the cache.

当然,所有类型的侦听器(如UI侦听器)、缓存或任何长期存在的共享状态都倾向于产生内存泄漏,如果处理不当。应该理解的是,这不是一个Java角的情况,也不是垃圾收集器的问题。这是一个设计问题。我们设计的是将一个监听器添加到一个长期存在的对象中,但是当不再需要时,我们不会删除侦听器。我们缓存对象,但是我们没有策略将它们从缓存中删除。

We maybe have a complex graph that store the previous state that is needed by a computation. But the previous state is itself linked to the state before and so on.

我们可能有一个复杂的图,它存储了计算所需的前一个状态。但是之前的状态本身与状态之前,等等。

Like we have to close SQL connections or files. We need to set proper references to null and remove elements from the collection. We shall have proper caching strategies (maximum memory size, number of elements, or timers). All objects that allow a listener to be notified must provide both a addListener and removeListener method. And when these notifiers are no longer used, they must clear their listener list.

比如我们必须关闭SQL连接或文件。我们需要为null设置适当的引用,并从集合中删除元素。我们将有适当的缓存策略(最大内存大小、元素数量或计时器)。允许通知侦听器的所有对象必须同时提供一个addListener和removeListener方法。当这些通知不再使用时,它们必须清除侦听器列表。

A memory leak is indeed truly possible and is perfectly predictable. No need for special language features or corner cases. Memory leaks are either an indicator that something is maybe missing or even of design problems.

内存泄漏确实是可能的,而且是完全可以预测的。不需要特殊语言功能或角落案例。内存泄漏可能是某个东西可能丢失或者甚至是设计问题的指示器。

#6


130  

The answer depends entirely on what the interviewer thought they were asking.

答案完全取决于面试官认为他们在问什么。

Is it possible in practice to make Java leak? Of course it is, and there are plenty of examples in the other answers.

在实践中是否可能导致Java泄漏?当然,在其他答案中也有很多例子。

But there are multiple meta-questions that may have been being asked?

但是有很多的元问题可能会被问到?

  • Is a theoretically "perfect" Java implementation vulnerable to leaks?
  • 从理论上说,“完美”的Java实现容易被泄露吗?
  • Does the candidate understand the difference between theory and reality?
  • 候选人是否了解理论与现实的区别?
  • Does the candidate understand how garbage collection works?
  • 候选人是否了解垃圾收集的工作原理?
  • Or how garbage collection is supposed to work in an ideal case?
  • 或者垃圾收集在理想情况下应该如何工作?
  • Do they know they can call other languages through native interfaces?
  • 他们知道他们可以通过本地接口调用其他语言吗?
  • Do they know to leak memory in those other languages?
  • 他们知道用其他语言泄露内存吗?
  • Does the candidate even know what memory management is, and what is going on behind the scene in Java?
  • 这位候选人是否知道什么是内存管理,以及在Java的幕后发生了什么?

I'm reading your meta-question as "What's an answer I could have used in this interview situation". And hence, I'm going to focus on interview skills instead of Java. I believe your more likely to repeat the situation of not knowing the answer to a question in an interview than you are to be in a place of needing to know how to make Java leak. So, hopefully, this will help.

我正在读你的元问题,因为“我在面试中可能会用到什么答案”。因此,我将专注于面试技巧而不是Java。我相信,在面试中,你更有可能重复不知道答案的情况,而不是在一个需要知道如何制造Java漏洞的地方。希望这能帮上忙。

One of the most important skills you can develop for interviewing is learning to actively listen to the questions and working with the interviewer to extract their intent. Not only does this let you answer their question the way they want, but also shows that you have some vital communication skills. And when it comes down to a choice between many equally talented developers, I'll hire the one who listens, thinks, and understands before they respond every time.

面试中最重要的技能之一就是学会积极地倾听问题,和面试官一起努力发掘他们的意图。这不仅让你以他们想要的方式回答他们的问题,而且还表明你有一些重要的沟通技巧。在许多同样有才华的开发人员之间做出选择的时候,我会雇佣一个在每次回应之前都会倾听、思考和理解的人。

#7


109  

The following is a pretty pointless example, if you do not understand JDBC. Or at least how JDBC expects a developer to close Connection, Statement and ResultSet instances before discarding them or losing references to them, instead of relying on the implementation of finalize.

如果您不理解JDBC,下面是一个非常没有意义的示例。或者至少JDBC期望开发人员在丢弃它们或丢失对它们的引用之前关闭连接、语句和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);
   }
}

The problem with the above is that the Connection object is not closed, and hence the physical connection will remain open, until the garbage collector comes around and sees that it is unreachable. GC will invoke the finalize method, but there are JDBC drivers that do not implement the finalize, at least not in the same way that Connection.close is implemented. The resulting behavior is that while memory will be reclaimed due to unreachable objects being collected, resources (including memory) associated with the Connection object might simply not be reclaimed.

上面的问题是连接对象没有关闭,因此物理连接将保持打开状态,直到垃圾收集器出现并看到它是不可访问的。GC将调用finalize方法,但是有一些JDBC驱动程序没有实现最终的实现,至少与连接的方式不同。接近实现。由此产生的行为是,虽然内存将被回收,但是与连接对象关联的资源(包括内存)可能不会被回收。

In such an event where the Connection's finalize method does not clean up everything, one might actually find that the physical connection to the database server will last several garbage collection cycles, until the database server eventually figures out that the connection is not alive (if it does), and should be closed.

在这样一个事件连接的确定方法不清理一切,你可能会发现物理连接到数据库服务器将持续几个垃圾收集周期,直到最终数据库服务器数据的连接不是活着(如果是这样),并且应该被关闭。

Even if the JDBC driver were to implement finalize, it is possible for exceptions to be thrown during finalization. The resulting behavior is that any memory associated with the now "dormant" object will not be reclaimed, as finalize is guaranteed to be invoked only once.

即使JDBC驱动程序实现了finalize,也有可能在终结过程中抛出异常。由此产生的行为是,与现在“休眠”对象关联的任何内存都不会被回收,因为finalize保证只被调用一次。

The above scenario of encountering exceptions during object finalization is related to another other scenario that could possibly lead to a memory leak - object resurrection. Object resurrection is often done intentionally by creating a strong reference to the object from being finalized, from another object. When object resurrection is misused it will lead to a memory leak in combination with other sources of memory leaks.

上述在对象终结过程中遇到异常的场景与另一个可能导致内存泄漏-对象复活的场景有关。对象的复活通常是有意的,通过对被最终定稿的对象,从另一个对象中创建一个强烈的引用。当对象复活被误用时,它会与其他内存泄漏源一起导致内存泄漏。

There are plenty more examples that you can conjure up - like

还有很多你可以想象的例子。

  • Managing a List instance where you are only adding to the list and not deleting from it (although you should be getting rid of elements you no longer need), or
  • 管理一个列表实例,其中您只添加到列表中,而不是从它中删除(尽管您应该删除不再需要的元素),或者。
  • Opening Sockets or Files, but not closing them when they are no longer needed (similar to the above example involving the Connection class).
  • 打开套接字或文件,但在不再需要时关闭它们(类似于上面的连接类示例)。
  • Not unloading Singletons when bringing down a Java EE application. Apparently, the Classloader that loaded the singleton class will retain a reference to the class, and hence the singleton instance will never be collected. When a new instance of the application is deployed, a new class loader is usually created, and the former class loader will continue to exist due to the singleton.
  • 在引入Java EE应用程序时,不卸载单例。显然,加载单例类的类加载器将保留对类的引用,因此不会收集singleton实例。当应用程序的新实例被部署时,通常会创建一个新的类装入器,而以前的类装入器将继续存在,因为单例。

#8


98  

Probably one of the simplest examples of a potential memory leak, and how to avoid it, is the implementation of ArrayList.remove(int):

可能是内存泄漏的最简单的例子之一,以及如何避免它,是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;
}

If you were implementing it yourself, would you have thought to clear the array element that is no longer used (elementData[--size] = null)? That reference might keep a huge object alive ...

如果您自己正在实现它,您是否想要清除已不再使用的数组元素(elementData[- size] = null)?这个引用可能会让一个巨大的对象存活……

#9


62  

Any time you keep references around to objects that you no longer need you have a memory leak. See Handling memory leaks in Java programs for examples of how memory leaks manifest themselves in Java and what you can do about it.

任何时候,只要将引用保存到不再需要的对象,就会出现内存泄漏。请参阅Java程序中处理内存泄漏的示例,以了解内存泄漏如何在Java中表现出来,以及您可以对此做些什么。

#10


42  

You are able to make memory leak with sun.misc.Unsafe class. In fact this service class is used in different standard classes (for example in java.nio classes). You can't create instance of this class directly, but you may use reflection to do that.

你可以用sun.misc进行内存泄漏。不安全的类。实际上,这个服务类被用于不同的标准类(例如在java中)。nio类)。您不能直接创建该类的实例,但是您可以使用反射来做到这一点。

Code doesn't compile in Eclipse IDE - compile it using command javac (during compilation you'll get warnings)

代码不能在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();
        }
    }

}

#11


35  

I can copy my answer from here: Easiest way to cause memory leak in Java?

我可以从这里复制我的答案:在Java中导致内存泄漏的最简单方法?

"A memory leak, in computer science (or leakage, in this context), occurs when a computer program consumes memory but is unable to release it back to the operating system." (Wikipedia)

“内存泄漏,在计算机科学(或泄漏,在这种情况下),发生在计算机程序消耗内存但无法将其释放回操作系统。”(*)

The easy answer is: You can't. Java does automatic memory management and will free resources that are not needed for you. You can't stop this from happening. It will ALWAYS be able to release the resources. In programs with manual memory management, this is different. You cann get some memory in C using malloc(). To free the memory, you need the pointer that malloc returned and call free() on it. But if you don't have the pointer anymore (overwritten, or lifetime exceeded), then you are unfortunately incapable of freeing this memory and thus you have a memory leak.

简单的回答是:你不能。Java实现了自动内存管理,并将免费提供不需要的资源。你不能阻止这一切的发生。它总是能够释放资源。在使用手动内存管理的程序中,这是不同的。使用malloc()可以在C中获得一些内存。要释放内存,需要malloc返回的指针,并在其上调用free()。但是,如果您不再拥有指针(覆盖了,或者超过了生命周期),那么不幸的是,您无法释放内存,从而导致内存泄漏。

All the other answers so far are in my definition not really memory leaks. They all aim at filling the memory with pointless stuff real fast. But at any time you could still dereference the objects you created and thus freeing the memory --> NO LEAK. acconrad's answer comes pretty close though as I have to admit since his solution is effectively to just "crash" the garbage collector by forcing it in an endless loop).

到目前为止,所有其他的答案都在我的定义中,而不是真正的内存泄漏。他们的目标都是快速地把那些无意义的东西填满记忆。但是,在任何时候,您仍然可以取消您创建的对象,从而释放内存—>没有泄漏。acconrad的回答非常接近,尽管我不得不承认,因为他的解决方案实际上是通过将垃圾收集器强制循环来“崩溃”。

The long answer is: You can get a memory leak by writing a library for Java using the JNI, which can have manual memory management and thus have memory leaks. If you call this library, your java process will leak memory. Or, you can have bugs in the JVM, so that the JVM looses memory. There are probably bugs in the JVM, there may even be some known ones since garbage collection is not that trivial, but then it's still a bug. By design this is not possible. You may be asking for some java code that is effected by such a bug. Sorry I don't know one and it might well not be a bug anymore in the next Java version anyway.

长期的答案是:您可以通过使用JNI编写Java库来获得内存泄漏,JNI可以进行手动内存管理,从而导致内存泄漏。如果您调用这个库,您的java进程将会泄漏内存。或者,您可以在JVM中有bug,这样JVM就会丢失内存。在JVM中可能存在bug,甚至可能有一些已知的bug,因为垃圾收集并不是那么简单,但是它仍然是一个bug。通过设计,这是不可能的。您可能需要一些java代码,这些代码是由这样的bug影响的。不好意思,我不知道,下一个Java版本可能也不会是bug了。

#12


31  

Here's a simple/sinister one via http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29.

下面是一个简单/邪恶的例子: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);
    }
}

Because the substring refers to the internal representation of the original, much longer string, the original stays in memory. Thus, as long as you have a StringLeaker in play, you have the whole original string in memory, too, even though you might think you're just holding on to a single-character string.

因为子字符串表示原始的、更长的字符串的内部表示,而原来的字符串保留在内存中。因此,只要在游戏中有一个StringLeaker,在内存中也有完整的原始字符串,尽管您可能认为您只是在坚持一个单字字符串。

The way to avoid storing an unwanted reference to the original string is to do something like this:

避免将不想要的引用存储到原始字符串的方法是这样做:

...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...

For added badness, you might also .intern() the substring:

对于添加的badness,您还可以.intern()子字符串:

...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...

Doing so will keep both the original long string and the derived substring in memory even after the StringLeaker instance has been discarded.

这样做将保留原来的长字符串和在内存中派生的子字符串,即使在StringLeaker实例被丢弃之后。

#13


30  

Take any web application running in any servlet container (Tomcat, Jetty, Glassfish, whatever...). Redeploy the app 10 or 20 times in a row (it may be enough to simply touch the WAR in the server's autodeploy directory.

在任何servlet容器中运行任何web应用程序(Tomcat、Jetty、Glassfish,等等)。将应用程序重新部署10次或20次(它可能足以简单地在服务器的自动部署目录中触发战争)。

Unless anybody has actually tested this, chances are high that you'll get an OutOfMemoryError after a couple of redeployments, because the application did not take care to clean up after itself. You may even find a bug in your server with this test.

除非有人实际测试过这种情况,否则在两次重新部署之后,您将获得OutOfMemoryError错误的几率非常高,因为应用程序本身并没有进行清理。您甚至可能在您的服务器中发现一个错误。

The problem is, the lifetime of the container is longer than the lifetime of your application. You have to make sure that all references the container might have to objects or classes of your application can be garbage collected.

问题是,容器的生命周期比应用程序的生命周期长。您必须确保容器可能对您的应用程序的对象或类的所有引用都是垃圾收集的。

If there is just one reference surviving the undeployment of your web app, the corresponding classloader and by consequence all classes of your web app cannot be garbage collected.

如果你的web应用程序没有部署,那么相应的类加载器和你的web应用程序的所有类都不能被垃圾收集。

Threads started by your application, ThreadLocal variables, logging appenders are some of the usual suspects to cause classloader leaks.

应用程序启动的线程、ThreadLocal变量、日志应用程序都是导致类加载器泄漏的常见疑点。

#14


29  

A common example of this in GUI code is when creating a widget/component and adding a listener to some static/application scoped object and then not removing the listener when the widget is destroyed. Not only do you get a memory leak, but also a performance hit as when whatever you are listening to fires events, all your old listeners are called too.

在GUI代码中,一个常见的例子是创建一个小部件/组件,并将侦听器添加到某个静态/应用程序范围的对象,然后在小部件被销毁时不删除侦听器。不仅会出现内存泄漏,而且还会出现性能问题,就像您正在监听的任何事件一样,您的老侦听器也会被调用。

#15


27  

Maybe by using external native code through JNI?

也许通过JNI使用外部原生代码?

With pure Java, it is almost impossible.

使用纯Java,这几乎是不可能的。

But that is about a "standard" type of memory leak, when you cannot access the memory anymore, but it is still owned by the application. You can instead keep references to unused objects, or open streams without closing them afterwards.

但是,这是一个“标准”类型的内存泄漏,当您无法访问内存时,它仍然属于应用程序。相反,您可以保留对未使用对象的引用,或者在不关闭的情况下打开流。

#16


25  

I have had a nice "memory leak" in relation to PermGen and XML parsing once. The XML parser we used (I can't remember which one it was) did a String.intern() on tag names, to make comparison faster. One of our customers had the great idea to store data values not in XML attributes or text, but as tagnames, so we had a document like:

对于PermGen和XML解析,我有过一次很好的“内存泄漏”。我们使用的XML解析器(我不记得是哪一个)在标记名上做了一个String.intern(),以便更快地进行比较。我们的一个客户有一个很棒的主意,可以将数据值存储在非XML属性或文本中,但是作为tagname,我们有这样的文档:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

In fact, they did not use numbers but longer textual IDs (around 20 characters), which were unique and came in at a rate of 10-15 million a day. That makes 200 MB of rubbish a day, which is never needed again, and never GCed (since it is in PermGen). We had permgen set to 512 MB, so it took around two days for the out-of-memory exception (OOME) to arrive...

事实上,他们没有使用数字,而是使用了更长的文本id(大约20个字符),这是唯一的,并且以每天10-15万的速度出现。这使得每天有200 MB的垃圾,再也不需要了,而且从来没有GCed(因为它在PermGen中)。我们将permgen设置为512 MB,因此内存溢出异常(OOME)在大约2天内到达……

#17


20  

I recently encountered a memory leak situation caused in a way by log4j.

我最近遇到了一个由log4j引起的内存泄漏情况。

Log4j has this mechanism called Nested Diagnostic Context(NDC) which is an instrument to distinguish interleaved log output from different sources. The granularity at which NDC works is threads, so it distinguishes log outputs from different threads separately.

Log4j有一个称为嵌套诊断上下文(NDC)的机制,它是一种区分不同来源的交叉日志输出的工具。NDC工作的粒度是线程,因此它区分不同线程的日志输出。

In order to store thread specific tags, log4j's NDC class uses a Hashtable which is keyed by the Thread object itself (as opposed to say the thread id), and thus till the NDC tag stays in memory all the objects that hang off of the thread object also stay in memory. In our web application we use NDC to tag logoutputs with a request id to distinguish logs from a single request separately. The container that associates the NDC tag with a thread, also removes it while returning the response from a request. The problem occurred when during the course of processing a request, a child thread was spawned, something like the following code:

为了存储线程特定标记,log4j NDC类使用一个哈希表的键控的线程对象本身(而不是说线程id),因此直到NDC标签保持在内存中所有挂起的线程对象的对象也保持在内存中。在我们的web应用程序中,我们使用NDC来标记带有请求id的日志输出,以分别从单个请求中区分日志。将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();
    }
}    

So an NDC context was associated with inline thread that was spawned. The thread object that was the key for this NDC context, is the inline thread which has the hugeList object hanging off of it. Hence even after the thread finished doing what it was doing, the reference to the hugeList was kept alive by the NDC context Hastable, thus causing a memory leak.

因此,NDC上下文与派生的内联线程相关联。线程对象是这个NDC上下文的关键,它是一个包含hugeList对象的内联线程。因此,即使在线程完成了它所做的工作之后,对hugeList的引用仍然被NDC上下文保存,从而导致了内存泄漏。

#18


19  

I thought it was interesting that no one used the internal class examples. If you have an internal class; it inherently maintains a reference to the containing class. Of course it is not technically a memory leak because Java WILL eventually clean it up; but this can cause classes to hang around longer than anticipated.

我觉得有趣的是没有人使用内部类的例子。如果你有一个内部类;它本质上维护对包含类的引用。当然,这在技术上不是一个内存泄漏,因为Java最终会清理它;但这可能会导致类的延迟时间比预期的长。

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

Now if you call Example1 and get an Example2 discarding Example1, you will inherently still have a link to an Example1 object.

现在,如果您调用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.
  }
}

I've also heard a rumor that if you have a variable that exists for longer than a specific amount of time; Java assumes that it will always exist and will actually never try to clean it up if cannot be reached in code anymore. But that is completely unverified.

我也听过这样的传言,如果你有一个变量,它存在的时间比特定的时间长;Java假定它将永远存在,而且如果不能在代码中找到它,它将永远不会尝试去清理它。但这是完全未经证实的。

#19


16  

What's a memory leak:

什么是内存泄漏:

  • It's caused by a bug or bad design.
  • 它是由错误或错误的设计引起的。
  • It's a waste of memory.
  • 这是在浪费记忆。
  • It gets worse over time.
  • 随着时间的推移,情况会变得更糟。
  • The garbage collector cannot clean it.
  • 垃圾收集器无法清理它。

Typical example:

典型的例子:

A cache of objects is a good starting point to mess things up.

缓存对象是把事情搞糟的好起点。

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;
}

Your cache grows and grows. And pretty soon the entire database gets sucked into memory. A better design uses an LRUMap (Only keeps recently used objects in cache).

缓存增长和增长。很快整个数据库就被内存占用了。更好的设计使用LRUMap(只在缓存中保存最近使用的对象)。

Sure, you can make things a lot more complicated:

当然,你可以让事情变得复杂得多:

  • using ThreadLocal constructions.
  • 使用ThreadLocal结构。
  • adding more complex reference trees.
  • 添加更复杂的参考树。
  • or leaks caused by 3rd party libraries.
  • 或由第三方图书馆造成的泄露。

What often happens:

经常发生:

If this Info object has references to other objects, which again have references to other objects. In a way you could also consider this to be some kind of memory leak, (caused by bad design).

如果这个信息对象引用了其他对象,那么它也会引用其他对象。在某种程度上,您还可以认为这是某种内存泄漏(由糟糕的设计引起)。

#20


15  

Create a static Map and keep adding hard references to it. Those will never be GC'd.

创建一个静态映射,并不断添加对它的硬引用。那些永远不会是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); }
}

#21


14  

You can create a moving memory leak by creating a new instance of a class in that class's finalize method. Bonus points if the finalizer creates multiple instances. Here's a simple program that leaks the entire heap in sometime between a few seconds and a few minutes depending on your heap size:

您可以通过在该类的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


13  

As a lot of people have suggested, Resource Leaks are fairly easy to cause - like the JDBC examples. Actual Memory leaks are a bit harder - especially if you aren't relying on broken bits of the JVM to do it for you...

正如许多人所建议的那样,资源泄漏是相当容易引起的——就像JDBC示例一样。实际内存泄漏有点困难,特别是如果您不依赖JVM的碎片来为您做这些事情……

The ideas of creating objects that have a very large footprint and then not being able to access them aren't real memory leaks either. If nothing can access it then it will be garbage collected, and if something can access it then it's not a leak...

创建具有很大内存空间的对象,然后不能访问它们的想法也不是真正的内存泄漏。如果没有任何东西可以访问它,那么它将被垃圾收集,如果有什么东西可以访问它,那么它就不是一个漏洞……

One way that used to work though - and I don't know if it still does - is to have a three-deep circular chain. As in Object A has a reference to Object B, Object B has a reference to Object C and Object C has a reference to Object A. The GC was clever enough to know that a two deep chain - as in A <--> B - can safely be collected if A and B aren't accessible by anything else, but couldn't handle the three-way chain...

有一种方法,虽然我不知道它是否仍然有效,就是有一个三深的循环链。在对象有一个引用对象B,B的引用对象C和C对象的引用对象A GC是够聪明,知道两个深链——< - - > B - A和B可以安全地收集如果不是由其他访问,但不能处理三方链…

#23


13  

I came across a more subtle kind of resource leak recently. We open resources via class loader's getResourceAsStream and it happened that the input stream handles were not closed.

我最近遇到了一种更微妙的资源泄漏。我们通过类装入器的getresour流来打开资源,并发生了输入流句柄未关闭的情况。

Uhm, you might say, what an idiot.

嗯,你可能会说,真是个白痴。

Well, what makes this interesting is: this way, you can leak heap memory of the underlying process, rather than from JVM's heap.

让这个有趣的是:通过这种方式,您可以泄漏底层进程的堆内存,而不是从JVM堆中。

All you need is a jar file with a file inside which will be referenced from Java code. The bigger the jar file, the quicker memory gets allocated.

您所需要的是一个jar文件,其中包含一个从Java代码中引用的文件。jar文件越大,分配的内存就越快。

You can easily create such a jar with the following class:

您可以轻松地在以下类中创建这样一个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();
    }
}

Just paste into a file named BigJarCreator.java, compile and run it from command line:

直接粘贴到一个名为BigJarCreator的文件中。java,编译并运行命令行:

javac BigJarCreator.java
java -cp . BigJarCreator

Et voilà: you find a jar archive in your current working directory with two files inside.

在当前工作目录中找到一个jar存档,其中包含两个文件。

Let's create a second class:

让我们创建第二个类:

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);
    }

}

This class basically does nothing, but create unreferenced InputStream objects. Those objects will be garbage collected immediately and thus, do not contribute to heap size. It is important for our example to load an existing resource from a jar file, and size does matter here!

这个类基本上什么都不做,但是创建未引用的InputStream对象。这些对象将立即被垃圾收集,因此不会导致堆大小。对于我们的示例来说,从jar文件加载现有资源是很重要的,而且大小在这里很重要!

If you're doubtful, try to compile and start the class above, but make sure to chose a decent heap size (2 MB):

如果您有疑问,请尝试编译并启动上面的类,但是一定要选择适当的堆大小(2 MB):

javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak

You will not encounter an OOM error here, as no references are kept, the application will keep running no matter how large you chose ITERATIONS in the above example. The memory consumption of your process (visible in top (RES/RSS) or process explorer) grows unless the application gets to the wait command. In the setup above, it will allocate around 150 MB in memory.

在这里,您将不会遇到OOM错误,因为没有保存任何引用,应用程序将继续运行,无论您在上面的示例中选择了多大的迭代。除非应用程序到达等待命令,否则进程的内存消耗(在top (RES/RSS)或进程浏览器中可见)增长。在上面的设置中,它将在内存中分配大约150 MB。

If you want the application to play safe, close the input stream right where it's created:

如果您希望应用程序安全运行,请关闭它所创建的输入流:

MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();

and your process will not exceed 35 MB, independent of the iteration count.

并且您的进程不会超过35 MB,独立于迭代计数。

Quite simple and surprising.

非常简单和令人惊讶的。

#24


12  

I don't think anyone has said this yet: you can resurrect an object by overriding the finalize() method such that finalize() stores a reference of this somewhere. The garbage collector will only be called once on the object so after that the object will never destroyed.

我认为还没有人这样说过:您可以通过重写finalize()方法来恢复对象,这样finalize()就可以在某个地方存储这个引用。垃圾回收器只会在对象上被调用一次,因此之后对象将不会被销毁。

#25


12  

Everyone always forgets the native code route. Here's a simple formula for a leak:

每个人总是忘记本地代码路由。这里有一个简单的泄漏公式:

  1. Declare native method.
  2. 声明本机方法。
  3. In native method, call malloc. Don't call free.
  4. 在本机方法中,调用malloc。别叫*。
  5. Call the native method.
  6. 调用本机方法。

Remember, memory allocations in native code come from the JVM heap.

请记住,本机代码中的内存分配来自JVM堆。

#26


12  

Threads are not collected until they terminate. They serve as roots of garbage collection. They are one of the few objects that won't be reclaimed simply by forgetting about them or clearing references to them.

线程在终止之前不会被收集。它们是垃圾收集的根。它们是少数几个不能简单地通过忘记它们或清除对它们的引用而被回收的对象之一。

Consider: the basic pattern to terminate a worker thread is to set some condition variable seen by the thread. The thread can check the variable periodically and use that as a signal to terminate. If the variable is not declared volatile, then the change to the variable might not be seen by the thread, so it won't know to terminate. Or imagine if some threads want to update a shared object, but deadlock while trying to lock on it.

考虑:终止一个工作线程的基本模式是设置线程所看到的某个条件变量。线程可以周期性地检查变量,并使用它作为终止的信号。如果变量没有被声明为不稳定,那么对变量的更改可能不会被线程看到,所以它不知道要终止。或者想象一下,如果某些线程想要更新共享对象,但是在试图锁定它时却陷入了死锁。

If you only have a handful of threads these bugs will probably be obvious because your program will stop working properly. If you have a thread pool that creates more threads as needed, then the obsolete/stuck threads might not be noticed, and will accumulate indefinitely, causing a memory leak. Threads are likely to use other data in your application, so will also prevent anything they directly reference from ever being collected.

如果您只有少量的线程,这些错误可能会很明显,因为您的程序将停止正常工作。如果您有一个线程池,它可以根据需要创建更多的线程,那么过时的/阻塞的线程可能不会被注意到,并且会无限累积,导致内存泄漏。线程可能会在您的应用程序中使用其他数据,因此也将防止任何它们直接引用的数据被收集。

As a toy example:

作为一个玩具的例子:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

Call System.gc() all you like, but the object passed to leakMe will never die.

调用System.gc()您所喜欢的,但是传递给leakMe的对象将不会死。

(*edited*)

(* *)编辑

#27


11  

there are many different situations memory will leak. One i encountered, which expose a map that should not be exposed and used in other place.

内存泄漏有很多不同的情况。我遇到的一个,它暴露了一个不应该暴露在其他地方使用的地图。

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  

The interviewer was probably looking for a circular reference like the code below (which incidentally only leak memory in very old JVMs that used reference counting, which isn't the case any more). But it's a pretty vague question, so it's a prime opportunity to show off your understanding of JVM memory management.

面试官可能在寻找一种循环引用,比如下面的代码(顺便说一下,它只是在使用引用计数的旧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 */
}

Then you can explain that with reference counting, the above code would leak memory. But most modern JVMs don't use reference counting any longer, most use a sweep garbage collector, which will in fact collect this memory.

然后您可以用引用计数来解释,上面的代码会泄漏内存。但是大多数现代jvm不再使用引用计数,大多数使用的是清除垃圾收集器,实际上它将收集这些内存。

Next you might explain creating an Object that has an underlying native resource, like this:

接下来,您可能会解释创建一个具有底层原生资源的对象:

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. */
    }
}

Then you can explain this is technically a memory leak, but really the leak is caused by native code in the JVM allocating underlying native resources, which weren't freed by your Java code.

然后您可以解释这在技术上是一个内存泄漏,但是真正的泄漏是由JVM分配底层本地资源的本地代码引起的,而这些本地资源并不是由Java代码释放出来的。

At the end of the day, with a modern JVM, you need to write some Java code that allocates a native resource outside the normal scope of the JVM's awareness.

在一天结束时,使用现代JVM,您需要编写一些Java代码,在JVM意识的正常范围之外分配本机资源。

#29


9  

I think that a valid example could be using ThreadLocal variables in an environment where threads are pooled.

我认为一个有效的示例可以在线程池的环境中使用ThreadLocal变量。

For instance, using ThreadLocal variables in Servlets to communicate with other web components, having the threads being created by the container and maintaining the idle ones in a pool. ThreadLocal variables, if not correctly cleaned up, will live there until, possibly, the same web component overwrites their values.

例如,在servlet中使用ThreadLocal变量与其他web组件通信,使线程由容器创建并维护池中的空闲线程。ThreadLocal变量(如果没有正确清理)将在那里生存,直到可能,相同的web组件重写它们的值。

Of course, once identified, the problem can be solved easily.

当然,一旦确定,问题就可以很容易地解决。

#30


9  

The interviewer might have be looking for a circular reference solution:

面试官可能在寻找一个循环的参考答案:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

This is a classic problem with reference counting garbage collectors. You would then politely explain that JVMs use a much more sophisticated algorithm that doesn't have this limitation.

这是一个引用计数垃圾收集器的经典问题。然后,您将礼貌地解释,jvm使用了一种更复杂的算法,它没有这种限制。

-Wes Tarle

韦斯Tarle