用Java创建内存泄漏

Creating a memory leak with Java

我刚刚接受了一次采访,我被要求用Java创建内存泄漏。不用说,我觉得自己很傻,根本不知道如何开始创作。

一个例子是什么?


这里有一个很好的方法来创建一个真正的内存泄漏(在代码中不可访问的代码,但仍然存储在内存中)在纯Java中:

  • 应用程序创建一个长时间运行的线程(或使用线程池更快地泄漏)。
  • 线程通过(可选的自定义)类加载器加载类。
  • 类分配一大块内存(例如new byte[1000000]),在静态字段中存储对它的强引用,然后在threadlocal中存储对自身的引用。分配额外的内存是可选的(泄漏类实例就足够了),但它会使泄漏工作更快。
  • 线程清除对自定义类或从中加载该类的类加载器的所有引用。
  • 重复。
  • 这是因为ThreadLocal保持对对象的引用,该对象保持对其类的引用,而该类又保持对其类加载器的引用。然后,类加载器保存对它所加载的所有类的引用。

    (在许多JVM实现中,尤其是在Java 7之前,这是更糟糕的,因为类和类加载器直接分配到PimMGEN,根本没有GC。但是,不管JVM如何处理类卸载,线程本地仍然会阻止类对象被回收。)

    这种模式的一个变化是,如果您经常重新部署碰巧以任何方式使用线程局部变量的应用程序,那么应用程序容器(如Tomcat)可能会像筛子一样泄漏内存。(因为应用程序容器按照描述使用线程,并且每次重新部署应用程序时都使用新的类加载器。)

    更新:由于很多人一直在要求它,这里有一些示例代码显示了这种行为的实际情况。


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

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

    在长字符串上调用String.intern()

    1
    2
    3
    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();

    (未关闭)打开的流(文件、网络等…)

    1
    2
    3
    4
    5
    6
    7
    try {
        BufferedReader br = new BufferedReader(new FileReader(inputFile));
        ...
        ...
    } catch (Exception e) {
        e.printStacktrace();
    }

    未闭合的连接

    1
    2
    3
    4
    5
    6
    7
    try {
        Connection conn = ConnectionFactory.getConnection();
        ...
        ...
    } catch (Exception e) {
        e.printStacktrace();
    }

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

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

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

    不正确或不适当的JVM选项,如IBMJDK上的noclassgc选项,用于防止未使用的类垃圾收集

    请参阅IBM JDK设置。


    要做的一件简单的事情就是使用一个哈希集,其中包含一个不正确(或不存在)的hashCode()equals(),然后继续添加"duplicates"。该集将只会增长,并且您将无法删除它们,而不是忽略它应该忽略的重复项。

    如果希望这些坏键/元素挂起,可以使用静态字段,如

    1
    2
    3
    4
    5
    6
    7
    8
    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.


    下面将有一个明显的情况下,非标准的Java漏洞,此外《被遗忘的听众的情况下,静态引用,在hashmaps假/ modifiable键没有任何线程在一起,或只是对他们的生命周期结束的机会。

    • 我总是File.deleteOnExit()泄漏删除字符串,字符串是一个字符串的子串,如果泄漏,甚至更糟的是,(在底层的char是[ ] [ ]也leaked)<删除>在Java 7  ;复制子的char[]也不是后来的操作系统中的应用需求,"丹尼尔,投票的,虽然。

    I’ll精矿在线自由线程到线程是unmanaged大学演出,甚至不想触摸的摆动。

    • Runtime.addShutdownHook和不删除。然后,在与removeshutdownhook虫类,由于它对threadgroup unstarted线程不可能得到有效的收集,threadgroup泄漏。在gossiprouter jgroup SAH的泄漏。

    • 创建,但不启动,为该类Thread上面去的。

    • 创建一个线程的ContextClassLoader继承和AccessControlContext,加上在任何InheritedThreadLocalThreadGroup和参考电位,所有这些都是沿与泄漏,整个类的类加载器加载的静态和所有引用和JA -是的。效果是可见的,特别是与整个框架是j.u.c.executor超级简单的ThreadFactory接口功能,但大多数开发商都在lurking线索的危险。还有很多图书馆在启动线程的请求(太多的行业流行的库)。

    • 这些都是危机的ThreadLocal缓存;在许多情况下。我相信每个人都有保本点处看到的简单的基于threadlocal,好的坏的消息:如果要使多线程上下文类加载器的生命预期的好,这是纯小泄漏。不使用,除非真的需要threadlocal缓存。

    • 当threadgroup蛛网膜下腔出血(SAH)ThreadGroup.destroy()调用线程本身,但它仍然保持threadgroups儿童。这将防止坏的threadgroup泄漏到从母体中,但是,所有的儿童enumerateable成为联合国。

    • 利用weakhashmap和直接的参考值(中)的关键。这是一个不难发现堆转储。这是适用于所有的扩展是可能Weak/SoftReference把硬背的guarded参考对象。

    • 利用java.net.URL协议与HTTP(S)和加载资源从(!)这一个是特别的。KeepAliveCache创建新的线程,而在系统中泄漏的threadgroup当前线程的上下文类加载器。该线程是由在第一次请求时,在操作系统上存在的任何线程,你可能会得到幸运的或只是泄漏。Java中的泄漏是已经固定 ;7和代码,创建线程的上下文类加载器removes恰当。有一些更多的案件(imagefetcher <删除>删除样,也产生类似的螺纹固定)。

    • 通过使用InflaterInputStreamnew java.util.zip.Inflater()在构造函数(例如呼叫PNGImageDecoder)和不end()的打气筒。好的,如果你只是new带通在构造函数中的机会……和是的,电话close()在线流,如果不接近它的手动打气筒中的构造函数的参数。这不是一个真正的泄漏,因为它是由finalizer释…………………当它认为必要的。到那时刻它eats本地存储器操作系统它可以引起严重的OOM杀手杀死_ Linux与惩罚的过程。主要的问题是,Java是非常不可靠的和部分的G1期,直到成功7.0.2更糟。道德的故事:尽快释放本地资源,你可以在finalizer太可怜;一只。

    • java.util.zip.Deflater表壳。更糟的是,因为这是一个遥远的记忆deflater Java总是饥饿,即用15  ;位(max)和8(9) ;内存分配是最大的几个英皇直属领地奇尔特恩诸邑kb的)本地存储。fortunately Deflater广泛使用,是不包含在JDK和misuses到我的知识。如果你总是end()呼叫或InflaterDeflater手动创建。最后的两个最好的部分:你不能找到他们的正常路径分析工具可用。

    (我可以添加一些更多的时间浪费在一个遇到的请求)。

    好运气和保持安全;漏洞是邪恶的!


    这里的大多数例子"太复杂了"。它们是边缘案例。通过这些例子,程序员犯了一个错误(比如不重新定义等值/哈希代码),或者被JVM/Java的一个角情况(用静态的类加载)被咬了。我认为这不是面试官想要的例子,甚至不是最常见的例子。

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

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

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

    我们可能有一个复杂的图来存储计算所需的先前状态。但前一个状态本身与前一个状态相关,以此类推。

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

    内存泄漏确实是可能的,并且是完全可预测的。无需特殊语言功能或角盒。内存泄漏或者是某种东西丢失的指示器,或者甚至是设计问题的指示器。


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

    在实践中有可能使Java泄漏吗?当然,其他答案中有很多例子。

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

    • 理论上"完美"的Java实现是否容易泄漏?
    • 候选人了解理论和现实之间的区别吗?
    • 候选人是否了解垃圾收集的工作原理?
    • 或者,在理想情况下,垃圾收集应该如何工作?
    • 他们知道他们可以通过本机接口调用其他语言吗?
    • 他们知道用其他语言泄漏内存吗?
    • 候选人是否知道什么是内存管理,以及在Java背后的场景是什么?

    我把你的元问题解读为"在这种面试情况下,我可以用什么回答"。因此,我将专注于面试技巧而不是Java。我相信你更有可能重复不知道面试中的问题的答案,而不是你需要知道如何让Java泄露。希望这能有所帮助。

    对于面试来说,你能培养的最重要的技能之一就是学会主动倾听问题,并与面试官合作以提取他们的意图。这不仅能让你以他们想要的方式回答他们的问题,而且也表明你有一些至关重要的沟通技巧。当涉及到许多同样有才华的开发人员之间的选择时,我会雇佣一个在他们每次响应之前倾听、思考和理解的开发人员。


    如果您不理解JDBC,下面是一个非常没有意义的例子。或者至少JDBC希望开发人员在放弃或丢失引用之前关闭ConnectionStatementResultSet实例,而不是依赖于finalize的实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    void doWork()
    {
       try
       {
           Connection conn = ConnectionFactory.getConnection();
           PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
           ResultSet rs = stmt.executeQuery();
           while(rs.hasNext())
           {
              ... process the result set
           }
       }
       catch(SQLException sqlEx)
       {
           log(sqlEx);
       }
    }

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

    在这种情况下,如果Connectionfinalize方法不能清除所有内容,那么实际上可能会发现到数据库服务器的物理连接将持续几个垃圾收集周期,直到数据库服务器最终发现连接不存在(如果存在),并且应该关闭。

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

    上述在对象终结过程中遇到异常的场景与另一个可能导致内存泄漏的场景(对象复活)相关。对象复活通常是有意地通过创建对对象的强引用来完成的,从另一个对象开始。当对象复活被误用时,它将导致与其他内存泄漏源结合的内存泄漏。

    还有很多例子你可以像变戏法一样

    • 管理仅添加到列表中而不从列表中删除的List实例(尽管您应该除去不再需要的元素),或者
    • 打开Sockets或Files,但不再需要时不关闭(类似于上面涉及Connection类的例子)。
    • 在下载JavaEE应用程序时不卸载单体。显然,加载singleton类的类加载器将保留对该类的引用,因此永远不会收集singleton实例。当部署应用程序的新实例时,通常会创建一个新的类加载器,由于是单例的,前一个类加载器将继续存在。


    arraylist.remove(int)的实现可能是潜在内存泄漏的最简单示例之一,以及如何避免它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public E remove(int index) {
        RangeCheck(index);

        modCount++;
        E oldValue = (E) elementData[index];

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index + 1, elementData, index,
                    numMoved);
        elementData[--size] = null; // (!) Let gc do its work

        return oldValue;
    }

    如果您自己实现它,您会考虑清除不再使用的数组元素吗(elementData[--size] = null?那个参考可能会让一个巨大的物体活着…


    任何时候当您将引用放在不再需要的对象周围时,都会出现内存泄漏。请参阅Java程序中内存泄漏的处理,举例说明内存泄漏是如何在Java中表现出来的,以及您可以对此做些什么。


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

    代码不能在Eclipse IDE中编译-使用命令javac编译它(在编译过程中会收到警告)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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();
            }
        }

    }


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

    "在计算机科学中,当一个计算机程序消耗内存,但无法将其释放回操作系统时,就会发生内存泄漏(或在此上下文中的泄漏)。(维基百科)

    简单的答案是:你不能。Java做自动内存管理,并将免费为你不需要的资源。你不能阻止这一切的发生。它将始终能够释放资源。在使用手动内存管理的程序中,这是不同的。您可以使用malloc()在c中获得一些内存。要释放内存,您需要malloc返回的指针并对其调用free()。但是,如果您不再拥有指针(被覆盖或超过了生存期),那么很遗憾,您无法释放此内存,因此出现了内存泄漏。

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

    长期的答案是:通过使用JNI编写Java库,可以获得内存泄漏,JNI可以进行手动内存管理,从而导致内存泄漏。如果调用这个库,Java进程将泄漏内存。或者,您可以在JVM中有bug,以便JVM释放内存。在JVM中可能存在一些bug,甚至可能存在一些已知的bug,因为垃圾收集不是那么简单,但它仍然是一个bug。按设计,这是不可能的。您可能需要一些受这种bug影响的Java代码。对不起,我不知道其中一个,它可能在下一个Java版本中不再是一个bug。


    下面是一个简单/险恶的例子,通过http://wiki.eclipse.org/performance-blooppers-string.substring.28.29。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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);
        }
    }

    因为子字符串指的是原始字符串的内部表示,所以原始字符串保留在内存中。因此,只要你有一个字符串泄漏者在玩,你也有整个原始字符串在记忆中,即使你可能认为你只是持有一个字符串。

    避免存储对原始字符串的不需要的引用的方法是执行如下操作:

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

    为了增加坏处,您还可以使用cx1〔0〕子字符串:

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

    这样做会将原始的长字符串和派生的子字符串保存在内存中,即使在StringLeaker实例被丢弃之后也是如此。


    在任何servlet容器(tomcat、jetty、glassfish等)中运行任何Web应用程序。连续10或20次重新部署应用程序(这可能足以在服务器的autodeploy目录中直接接触战争)。

    除非有人真的测试过这个,否则在重新部署两次之后,您很可能会遇到内存不足的错误,因为应用程序本身并不关心清理。通过这个测试,您甚至可能在服务器中发现一个bug。

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

    如果只有一个引用在未部署Web应用程序的情况下仍然存在,那么相应的类加载器和Web应用程序的所有类就不能被垃圾收集。

    由应用程序启动的线程、线程局部变量、日志附加器是导致类加载器泄漏的常见怀疑因素之一。


    在GUI代码中,一个常见的例子是当创建一个小部件/组件并向某个静态/应用程序范围的对象添加一个侦听器,然后在小部件被破坏时不删除该侦听器。不仅会出现内存泄漏,还会影响性能,因为无论您在听什么,都会调用所有旧的侦听器。


    也许通过JNI使用外部本机代码?

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

    但这是一种"标准"类型的内存泄漏,当您无法再访问内存时,它仍然属于应用程序。相反,您可以保留对未使用对象的引用,或者打开流,而不随后关闭它们。


    我曾经有过一次与PermGen和XML解析相关的"内存泄漏"。我们使用的XML解析器(我记不清它是哪一个)对标记名执行了string.intern(),以便更快地进行比较。我们的一个客户有一个很好的想法,不是将数据值存储在XML属性或文本中,而是将其存储为标记名,因此我们有一个类似以下的文档:

    1
    2
    3
    4
    5
    <data>
       <1>bla</1>
       <2>foo</>
       ...
    </data>

    事实上,他们不使用数字,而是使用更长的文本ID(大约20个字符),这些ID是唯一的,以每天1000-1500万的速度传入。这使得每天产生200 MB的垃圾,不再需要,也不再需要GCED(因为它是永久的)。我们将permgen设置为512 MB,因此大约需要两天时间,内存不足异常(oome)才会到达…


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

    log4j有一种称为嵌套诊断上下文(ndc)的机制,它是一种工具,用于区分交错的日志输出与不同的源。NDC工作的粒度是线程,因此它分别区分不同线程的日志输出。

    为了存储线程特定的标记,log4j的ndc类使用一个哈希表,该哈希表由线程对象本身(而不是说线程ID)键控,因此,直到ndc标记留在内存中,挂起线程对象的所有对象也都留在内存中。在我们的Web应用程序中,我们使用ndc标记带有请求ID的logoutputs,以分别区分日志和单个请求。将ndc标记与线程关联的容器,也会在从请求返回响应时将其移除。在处理请求的过程中,生成了一个子线程(如以下代码)时出现问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    pubclic class RequestProcessor {
        private static final Logger logger = Logger.getLogger(RequestProcessor.class);
        public void doSomething()  {
            ....
            final List<String> hugeList = new ArrayList<String>(10000);
            new Thread() {
               public void run() {
                   logger.info("Child thread spawned")
                   for(String s:hugeList) {
                       ....
                   }
               }
            }.start();
        }
    }

    因此,一个ndc上下文与生成的内联线程相关联。线程对象是这个ndc上下文的关键,它是一个内联线程,其中挂起了HugeList对象。因此,即使在线程完成了它正在做的事情之后,ndc上下文hastable仍然保持对hugelist的引用,从而导致内存泄漏。


    什么是内存泄漏:

    • 它是由一个错误或糟糕的设计引起的。
    • 这是浪费记忆。
    • 随着时间的推移,情况变得更糟。
    • 垃圾收集器无法清理它。

    典型示例:

    一个对象缓存是把事情搞砸的一个好起点。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private static final Map<String, Info> myCache = new HashMap<>();

    public void getInfo(String key)
    {
        // uses cache
        Info info = myCache.get(key);
        if (info != null) return info;

        // if it's not in cache, then fetch it from the database
        info = Database.fetch(key);
        if (info == null) return null;

        // and store it in the cache
        myCache.put(key, info);
        return info;
    }

    您的缓存会不断增长。很快整个数据库就被吸进了内存。更好的设计使用lrumap(仅将最近使用的对象保存在缓存中)。

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

    • 采用螺纹局部结构。
    • 添加更复杂的参考树。
    • 或第三方图书馆造成的泄漏。

    经常发生的事情:

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


    我认为没有人使用内部类示例很有趣。如果您有一个内部类,它就内在地维护对包含类的引用。当然,从技术上来说,这并不是内存泄漏,因为Java最终会清理它,但这可能会导致类的逗留时间比预期的长。

    1
    2
    3
    4
    5
    6
    7
    8
    public class Example1 {
      public Example2 getNewExample2() {
        return this.new Example2();
      }
      public class Example2 {
        public Example2() {}
      }
    }

    现在,如果调用example1并获取example2丢弃example1,那么本质上仍然有指向example1对象的链接。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Referencer {
      public static Example2 GetAnExample2() {
        Example1 ex = new Example1();
        return ex.getNewExample2();
      }

      public static void main(String[] args) {
        Example2 ex = Referencer.GetAnExample2();
        // As long as ex is reachable; Example1 will always remain in memory.
      }
    }

    我还听到一个谣言,如果你有一个变量存在的时间比一个特定的时间长;Java假定它将永远存在,实际上将永远不会试图清理它,如果不能在代码中达到。但这完全没有得到证实。


    创建静态映射并不断向其添加硬引用。这些永远不会是GC的。

    1
    2
    3
    4
    5
    6
    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); }
    }


    面试官可能在寻找类似下面代码的循环引用(顺便说一下,它只泄漏使用引用计数的非常老的JVM中的内存,现在不再是这样了)。但这是一个相当模糊的问题,所以这是展示您对JVM内存管理理解的最佳机会。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class A {
        B bRef;
    }

    class B {
        A aRef;
    }

    public class Main {
        public static void main(String args[]) {
            A myA = new A();
            B myB = new B();
            myA.bRef = myB;
            myB.aRef = myA;
            myA=null;
            myB=null;
            /* at this point, there is no access to the myA and myB objects, */
            /* even though both objects still have active references. */
        } /* main */
    }

    然后,您可以解释通过引用计数,上述代码将泄漏内存。但是大多数现代的JVM不再使用引用计数,大多数使用一个清理垃圾收集器,实际上它将收集这个内存。

    接下来,您可能会解释如何创建具有底层本机资源的对象,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    public class Main {
        public static void main(String args[]) {
            Socket s = new Socket(InetAddress.getByName("google.com"),80);
            s=null;
            /* at this point, because you didn't close the socket properly, */
            /* you have a leak of a native descriptor, which uses memory. */
        }
    }

    然后,您可以解释这在技术上是内存泄漏,但真正的泄漏是由JVM中的本地代码分配的,这是源代码未被Java代码释放的本地资源。

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


    通过在类的Finalize方法中创建类的新实例,可以创建移动内存泄漏。如果终结器创建多个实例,则可获得奖励积分。下面是一个简单的程序,根据堆的大小在几秒到几分钟之间泄漏整个堆:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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());
            }
        }
    }

    每个人都会忘记本机代码路由。以下是泄漏的简单公式:

  • 声明本机方法。
  • 在本机方法中,调用malloc。不要打电话给free
  • 调用本机方法。
  • 记住,本机代码中的内存分配来自于JVM堆。


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


    最近我遇到了一种更微妙的资源泄漏。我们通过类加载器的getresourceasstream打开资源,结果输入流句柄没有关闭。

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

    好吧,有趣的是:这样,您就可以泄漏底层进程的堆内存,而不是从JVM的堆中。

    您需要的是一个JAR文件,里面有一个文件,它将从Java代码中引用。JAR文件越大,分配的内存就越快。

    您可以使用以下类轻松创建这样一个jar:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.zip.ZipEntry;
    import java.util.zip.ZipOutputStream;

    public class BigJarCreator {
        public static void main(String[] args) throws IOException {
            ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
            zos.putNextEntry(new ZipEntry("resource.txt"));
            zos.write("not too much in here".getBytes());
            zos.closeEntry();
            zos.putNextEntry(new ZipEntry("largeFile.out"));
            for (int i=0 ; i<10000000 ; i++) {
                zos.write((int) (Math.round(Math.random()*100)+20));
            }
            zos.closeEntry();
            zos.close();
        }
    }

    只需粘贴到名为bigjarCreator.java的文件中,从命令行编译并运行它:

    1
    2
    javac BigJarCreator.java
    java -cp . BigJarCreator

    et voil_:您可以在当前工作目录中找到一个JAR存档,其中包含两个文件。

    让我们创建第二个类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class MemLeak {
        public static void main(String[] args) throws InterruptedException {
            int ITERATIONS=100000;
            for (int i=0 ; i<ITERATIONS ; i++) {
                MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
            }
            System.out.println("finished creation of streams, now waiting to be killed");

            Thread.sleep(Long.MAX_VALUE);
        }

    }

    这个类基本上什么也不做,只创建未引用的inputstream对象。这些对象将被立即垃圾收集,因此不会影响堆大小。对于我们的示例来说,从JAR文件加载一个现有的资源是很重要的,这里的大小很重要!

    如果您不确定,请尝试编译并启动上面的类,但请确保选择合适的堆大小(2 MB):

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

    在这里您不会遇到OOM错误,因为不保留任何引用,所以无论您在上面的示例中选择了多大的迭代,应用程序都将继续运行。除非应用程序进入wait命令,否则进程(在top(res/rss)或process explorer中可见)的内存消耗会增加。在上面的设置中,它将在内存中分配大约150 MB。

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

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

    并且您的进程不会超过35MB,与迭代计数无关。

    非常简单和令人惊讶。


    正如许多人所建议的,资源泄漏相当容易引起——就像JDBC示例一样。实际的内存泄漏有点困难-尤其是如果您不依赖于JVM的坏位来为您做这件事…

    创建占地面积非常大但又无法访问的对象的想法也不是真正的内存泄漏。如果没有任何东西可以访问它,那么它将被垃圾收集,如果有什么东西可以访问它,那么它就不是泄漏…

    不过,有一种方法曾经奏效——我不知道它是否仍然有效——就是有一个三个深的圆环链。正如在对象A中引用了对象B一样,对象B引用了对象C,而对象C引用了对象A。GC非常聪明,知道如果A和B不能被其他任何对象访问,但不能处理三向链,那么可以安全地收集两个深度链(如在A<->B中)。


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

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

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

    作为一个玩具例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    static void leakMe(final Object object) {
        new Thread() {
            public void run() {
                Object o = object;
                for (;;) {
                    try {
                        sleep(Long.MAX_VALUE);
                    } catch (InterruptedException e) {}
                }
            }
        }.start();
    }

    随你怎么说,就叫System.gc(),但传给leakMe的物体永远不会死。

    (*编辑)


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    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 {

    }

    我认为一个有效的例子可能是在线程集中的环境中使用线程局部变量。

    例如,在servlet中使用threadlocal变量与其他Web组件进行通信,使线程由容器创建,并将空闲线程维护在池中。如果没有正确清除线程局部变量,那么它将一直存在到同一个Web组件覆盖它们的值为止。

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


    我最近修复的一个例子是创建新的gc和image对象,但忘记调用dispose()方法。

    gc javadoc代码段:

    Application code must explicitly invoke the GC.dispose() method to
    release the operating system resources managed by each instance when
    those instances are no longer required. This is particularly important
    on Windows95 and Windows98 where the operating system has a limited
    number of device contexts available.

    image javadoc代码段:

    Application code must explicitly invoke the Image.dispose() method to
    release the operating system resources managed by each instance when
    those instances are no longer required.


    创建潜在巨大内存泄漏的另一种方法是保存对TreeMapMap.Entry的引用。

    很难理解为什么这只适用于TreeMap,但通过查看实现,原因可能是:TreeMap.Entry存储对其兄弟的引用,因此,如果TreeMap已准备好收集,但其他类持有对其任何Map.Entry的引用,那么整个映射将保留到内存中。

    现实生活场景:

    假设有一个DB查询返回一个大的TreeMap数据结构。人们通常使用TreeMap作为元素插入顺序的保留。

    1
    public static Map<String, Integer> pseudoQueryDatabase();

    如果查询被多次调用,那么对于每个查询(因此,对于返回的每个Map),您将在某个地方保存一个Entry,内存将不断增长。

    考虑以下包装类:

    1
    2
    3
    4
    5
    6
    7
    class EntryHolder {
        Map.Entry<String, Integer> entry;

        EntryHolder(Map.Entry<String, Integer> entry) {
            this.entry = entry;
        }
    }

    应用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class LeakTest {

        private final List<EntryHolder> holdersCache = new ArrayList<>();
        private static final int MAP_SIZE = 100_000;

        public void run() {
            // create 500 entries each holding a reference to an Entry of a TreeMap
            IntStream.range(0, 500).forEach(value -> {
                // create map
                final Map<String, Integer> map = pseudoQueryDatabase();

                final int index = new Random().nextInt(MAP_SIZE);

                // get random entry from map
                for (Map.Entry<String, Integer> entry : map.entrySet()) {
                    if (entry.getValue().equals(index)) {
                        holdersCache.add(new EntryHolder(entry));
                        break;
                    }
                }
                // to observe behavior in visualvm
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

        }

        public static Map<String, Integer> pseudoQueryDatabase() {
            final Map<String, Integer> map = new TreeMap<>();
            IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
            return map;
        }

        public static void main(String[] args) throws Exception {
            new LeakTest().run();
        }
    }

    每次调用pseudoQueryDatabase()之后,Map实例都应该准备好进行收集,但不会发生这种情况,因为至少有一个Entry存储在其他地方。

    根据您的jvm设置,由于OutOfMemoryError的原因,应用程序可能在早期崩溃。

    从这个visualvm图可以看出内存是如何不断增长的。

    Memory dump - TreeMap

    散列数据结构(HashMap)也不会发生同样的情况。

    这是使用HashMap时的图形。

    Memory dump - HashMap

    解决方案?直接保存键/值(可能已经这样做了),而不是保存Map.Entry

    我在这里写了一个更广泛的基准。


    面试官可能正在寻找一个循环参考解决方案:

    1
    2
    3
    4
    5
    6
    7
        public static void main(String[] args) {
            while (true) {
                Element first = new Element();
                first.next = new Element();
                first.next.next = first;
            }
        }

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

    - Wes Tarle


    不终止的线程(比如在其运行方法中无限期休眠)。即使我们松开了对它的引用,它也不会被垃圾收集。您可以添加字段使线程对象成为您想要的大对象。

    目前最热门的答案列出了更多关于这个的技巧,但这些似乎是多余的。


    我想给出一个关于如何使用JVM中可用的工具来监视应用程序内存泄漏的建议。它不显示如何生成内存泄漏,但解释了如何使用可用的最少工具检测它。

    首先需要监视Java内存消耗。

    最简单的方法是使用jvm附带的jstat实用程序。

    1
    jstat -gcutil <process_id> <timeout>

    它将报告每一代(年轻、年长和老年)的内存消耗和垃圾收集时间(年轻和满)。

    一旦您发现执行垃圾回收的频率太高、时间太长,就可以假定应用程序正在泄漏内存。

    然后需要使用jmap实用程序创建内存转储:

    1
    jmap -dump:live,format=b,file=heap.bin <process_id>

    然后,您需要使用内存分析器分析heap.bin文件,例如Eclipse内存分析器(mat)。

    mat将分析内存并提供有关内存泄漏的可疑信息。


    理论上你不能。Java内存模型阻止了它。但是,因为必须实现Java,所以您可以使用一些警告。取决于你能用什么:

    • 如果您可以使用本机,则可以分配以后不放弃的内存。

    • 如果这是不可用的,有一个关于Java的肮脏的小秘密,没有多少人知道。您可以请求一个不由GC管理的直接访问数组,因此可以很容易地用于造成内存泄漏。这是由DirectByteBuffer提供的(http://cliop.Oracle)/javas/1.5.0/DOCS/API/Java/NiO/ByTeBase.html

    • 如果您不能使用其中的任何一个,您仍然可以通过欺骗GC来造成内存泄漏。JVM是使用世代垃圾收集来实现的。这意味着这堆垃圾被分为几个区域:年轻人、成年人和老年人。一个物体,当它被创造时,从年轻的区域开始。随着越来越多的人使用他,他逐渐长大成人,直到长者。到达老年区的物体很可能不会被收集起来。你不能确定一个物体是否泄漏,如果你要求停止并清理GC,它可能会清理它,但在很长一段时间内,他会被泄漏。更多信息,请访问(http://java.sun.com/docs/hotspot/gc1.4.2/faq.html)

    • 另外,类对象不需要进行gc'ed。请给我一个方法。


    关于如何在Java中创建内存泄漏有很多答案,但是请注意在面试过程中提出的要点。

    "如何用Java创建内存泄漏?"是一个开放式问题,其目的是评估开发人员的经验程度。

    如果我问你"你有没有在Java中排除内存泄漏的经验?"你的回答很简单:"是"。然后我将不得不跟进"你能给我举个例子,在哪里你可以解决内存泄漏?",你可以给我举一两个例子。

    然而,当面试官问"如何用Java创建内存泄漏?"预期答案应遵循以下几行:

    • 我遇到了一个记忆泄露…(说什么时候)【这说明了我的经验】
    • 导致它的代码是…(解释代码)[您自己修复了它]
    • 我应用的修复是基于…(解释修复程序)【这给了我一个询问修复程序细节的机会】
    • 我做的测试是…[让我有机会询问其他测试方法]
    • 我是这样记录的…[加分]。很好,如果你有记录的话]
    • 所以,我们有理由认为,如果我们按照相反的顺序执行这个操作,也就是说,获取我修复的代码,然后删除我的修复,我们就会有内存泄漏。

    当开发人员未能遵循这一思路时,我试图引导他/她问:"你能给我一个Java如何泄漏内存的例子吗?"接着,"你是否需要修复Java中的内存泄漏?"

    请注意,我不要求在爪哇中如何泄漏内存的示例。那太傻了。谁会对能够有效地编写泄漏内存的代码的开发人员感兴趣?


    从Finalize方法引发未处理的异常。


    如果最大堆大小是x.y1….yn个实例,那么总内存=每个实例的实例数x字节。如果x1….xn是每个实例的字节数。那么总内存(m)=y1*x1+…..+yn*xn。所以,如果m>x,它会超过堆空间。以下可能是代码中的问题1.使用多个实例变量,然后使用本地变量。2.每次创建实例而不是池对象。3.不按需创建对象。4.在操作完成后使对象引用为空。再次,在程序中需要时重新创建。


    在Java关注的进程中,大多数内存泄漏都会失去同步。

    处理a通过tcp与b对话,并告诉进程b创建一些东西。B向资源发出一个ID,例如432423,A存储在一个对象中,并在与B交谈时使用该ID。在某个时刻,A中的对象被垃圾收集回收(可能是由于错误),但A从未告诉B(可能是另一个错误)。

    现在a不再拥有它在b的RAM中创建的对象的ID,b也不知道a不再引用该对象。实际上,物体是泄漏的。


    一些建议:

    • 在servlet容器中使用commons日志(可能有点挑衅)
    • 在servlet容器中启动线程,不要从其run方法返回
    • 在servlet容器中加载动画gif(这将启动动画线程)

    通过重新部署应用程序,可以"改进"上述效果;)

    最近偶然发现:

    • 调用"new java. U.L.zip,GooRealType";"不调用"Buffo.Enter()

    阅读http://bugs.sun.com/bugdatabase/view_bug.do?Bug_id=5072161,并链接问题进行深入讨论。


    一种可能是为只提供一个方法的ArrayList创建一个包装器:向ArrayList添加内容的方法。将ArrayList本身设为私有。现在,在全局范围内构造这些包装器对象之一(作为类中的静态对象),并用最后一个关键字(例如public static final ArrayListWrapper wrapperClass = new ArrayListWrapper())对其进行限定。所以现在不能更改引用。也就是说,wrapperClass = null无法工作,也无法用于释放内存。但是除了添加对象之外,也没有办法对wrapperClass做任何事情。因此,您添加到wrapperClass中的任何对象都无法回收。


    Java中的内存泄漏不是典型的C/C++内存泄漏。

    要了解JVM的工作原理,请阅读了解内存管理。

    基本上,重要的部分是:

    The Mark and Sweep Model

    The JRockit JVM uses the mark and sweep garbage collection model for
    performing garbage collections of the whole heap. A mark and sweep
    garbage collection consists of two phases, the mark phase and the
    sweep phase.

    During the mark phase all objects that are reachable from Java
    threads, native handles and other root sources are marked as alive, as
    well as the objects that are reachable from these objects and so
    forth. This process identifies and marks all objects that are still
    used, and the rest can be considered garbage.

    During the sweep phase the heap is traversed to find the gaps between
    the live objects. These gaps are recorded in a free list and are made
    available for new object allocation.

    The JRockit JVM uses two improved versions of the mark and sweep
    model. One is mostly concurrent mark and sweep and the other is
    parallel mark and sweep. You can also mix the two strategies, running
    for example mostly concurrent mark and parallel sweep.

    因此,在Java中创建内存泄漏;最简单的方法是创建一个数据库连接,做一些工作,而不是Close();然后在一个范围内生成一个新的数据库连接。例如,在一个循环中并不难做到这一点。如果您有一个从队列中提取并推送到数据库的工作者,那么您可以通过忘记Close()连接或在不需要时打开它们,等等,轻松地创建内存泄漏。

    最后,您将通过忘记Close()连接来使用分配给JVM的堆。这将导致JVM垃圾收集疯狂,最终导致java.lang.OutOfMemoryError: Java heap space错误。应该注意的是,错误可能并不意味着存在内存泄漏;它可能只是意味着您没有足够的内存;例如,Cassandra和ElasticSearch这样的数据库可以抛出该错误,因为它们没有足够的堆空间。

    值得注意的是,这对于所有GC语言都是正确的。下面是一些我见过的作为SRE工作的例子:

    • 节点使用redis作为队列;开发团队每12小时创建一次新连接,但忘记关闭旧连接。最后节点是oomd,因为它消耗了所有的内存。
    • golang(我对此感到内疚);使用json.Unmarshal解析大型JSON文件,然后通过引用传递结果并保持其打开状态。最终,这导致整个堆被意外的引用所消耗,我一直打开这些引用来解码JSON。

    Java 1.6中的字符串子串方法创建内存泄漏。这篇博文解释了这一点。

    http://javarevisited.blogspot.com/2011/10/how-substring-in-java-works.html


    在Java中,"内存泄漏"主要是因为你使用了太多的内存,这与C内存不同,因为你不再使用内存而忘记返回(免费)它。当面试官询问Java内存泄漏时,他们询问JVM内存使用是否只是在不断上升,他们决定定期重新启动JVM是最好的解决办法。(除非面试官非常精通技术)

    所以回答这个问题,就好像他们问是什么使得JVM内存使用量随着时间的推移而增长。好的答案是在超时时间过长的httpsessions中存储太多的数据,或者在内存缓存(singleton)中实现不好而从不刷新旧条目。另一个可能的答案是拥有大量的JSP或动态生成的类。类被加载到一个称为permgen的内存区域中,该区域通常很小,大多数JVM不实现类卸载。


    Swing使用对话框非常简单。创建一个jdialog,显示它,用户关闭它,泄漏!您必须调用dispose()或配置setDefaultCloseOperation(DISPOSE_ON_CLOSE)


    在有自己生命周期的类中不小心使用了一个非静态的内部类。

    在爪哇中,非静态内部和匿名类对其外部类持有隐式引用。另一方面,静态内部类则没有。

    以下是Android中内存泄漏的一个常见示例,虽然这并不明显:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class SampleActivity extends Activity {

      private final Handler mLeakyHandler = new Handler() { //non-static inner class, holds the reference to the SampleActivity outter class
        @Override
        public void handleMessage(Message msg) {
          // ...
        }
      }

      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Post a message and delay its execution for a long time.
        mLeakyHandler.postDelayed(new Runnable() {//here, the anonymous inner class holds the reference to the SampleActivity class too
          @Override
          public void run() {
         //....
          }
        }, SOME_TOME_TIME);

        // Go back to the previous Activity.
        finish();
      }}

    这将阻止对活动上下文进行垃圾收集。


    失效的listerners是内存泄漏的一个很好的例子:对象被添加为侦听器。当不再需要对象时,对该对象的所有引用都将为空。但是,忘记从侦听器列表中删除对象会使对象保持活动状态,甚至对事件做出响应,从而浪费内存和CPU。见http://www.drdobbs.com/jvm/java-qa/184404011


    如果不使用压缩垃圾收集器,则可能由于堆碎片而导致某种内存泄漏。


    JDK 1.7之前内存泄漏的实时示例

    假设您读取一个包含1000行文本的文件,并将其保存在字符串对象中

    1
    2
    3
    String fileText = 1000 characters from file

    fileText = fileText.subString(900, fileText.length());

    在上面的代码中,我最初读取1000个字符,然后执行子字符串以只获取最后100个字符。现在,filetext应该只引用100个字符,所有其他字符都应该被垃圾收集,因为我丢失了引用,但是在jdk 1.7子字符串函数间接引用最后100个字符的原始字符串之前,它会阻止整个字符串被垃圾收集,并且在您松开子字符串的引用之前,整个1000个字符都将在内存中。

    您可以像上面那样创建内存泄漏示例


    a memory leak is a type of resource leak that occurs when a computer program incorrectly manages memory allocations in such a way that memory which is no longer needed is not released => wiki definition

    这是一种相对基于上下文的主题,您可以根据自己的喜好创建一个,只要未使用的引用永远不会被客户使用,但仍然保持活跃。

    第一个示例应该是自定义堆栈,而不会在有效的Java项目6中删除过时的引用。

    当然,只要你想,还有更多,但是如果我们只看Java内置类,它可能是一些

    subList()

    让我们检查一些非常愚蠢的代码来产生泄漏。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class MemoryLeak {
        private static final int HUGE_SIZE = 10_000;

        public static void main(String... args) {
            letsLeakNow();
        }

        private static void letsLeakNow() {
            Map<Integer, Object> leakMap = new HashMap<>();
            for (int i = 0; i < HUGE_SIZE; ++i) {
                leakMap.put(i * 2, getListWithRandomNumber());
            }
        }



        private static List<Integer> getListWithRandomNumber() {
            List<Integer> originalHugeIntList = new ArrayList<>();
            for (int i = 0; i < HUGE_SIZE; ++i) {
                originalHugeIntList.add(new Random().nextInt());
            }
            return originalHugeIntList.subList(0, 1);
        }
    }

    实际上还有另一个技巧,我们可以利用hashmap的查找过程,使用它来引起内存泄漏。实际上有两种类型:

    • hashCode()总是相同的,但equals()是不同的;
    • 随机使用hashCode()equals()总是正确的;

    为什么?

    hashCode()->bucket=>equals()定位对

    我正要先提到substring(),然后提到subList(),但这个问题似乎已经解决了,因为它的源代码在JDK8中出现了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

    就这样!

    1
    2
    3
    4
    5
    6
    public static void main(String[] args) {
        List<Object> objects = new ArrayList<>();
        while(true) {
            objects.add(new Object());
        }
    }


    从有效的Java书籍

  • 每当一个类管理自己的内存时,程序员应该内存泄漏警报
  • .

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    /**
     * Ensure space for at least one more element, roughly doubling the capacity
     * each time the array needs to grow.
     */

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }

    }

    你能查出内存泄漏吗?那么内存泄漏在哪里呢?如果堆栈增长然后收缩,则对象即使程序使用堆栈时不再引用它们。这是因为堆栈对这些对象的过时引用。过时的引用只是一个引用这将永远不会被取消引用。在这种情况下,任何元素数组的"活动部分"已过时。活动部分包括索引小于大小的元素。


    Java中没有内存泄漏之类的东西。记忆泄漏是从C等人借来的一个短语。Java在GC的帮助下处理内存分配。存在内存浪费(即留下被搁浅的对象),但不存在内存泄漏。


    这是一个非常简单的Java程序,它将耗尽空间。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class OutOfMemory {

        public static void main(String[] arg) {

            List<Long> mem = new LinkedList<Long>();
            while (true) {
                mem.add(new Long(Long.MAX_VALUE));
            }
        }
    }