使用Java创建内存泄漏

cet 发布于 2018-11-19 java 最后更新 2018-11-19 20:13 85 浏览

我刚刚接受了一次采访,并被要求用Java创建内存泄漏。 毋庸置疑,我对于如何开始创建一个自己而言毫无头绪。 一个例子会是什么?

已邀请:

fomnis

赞同来自:

这里的大多数例子都“过于复杂”。他们是边缘案件。使用这些示例,程序员犯了一个错误(比如不重新加工equals / hashcode),或者被JVM / JAVA的一个角落案例(带有静态的类加载......)所咬。我认为这不是面试官想要的例子,甚至是最常见的案例。 但是内存泄漏的情况确实比较简单。垃圾收集器只释放不再引用的内容。我们作为Java开发人员并不关心内存。我们在需要时分配它并让它自动释放。精细。 但任何长期存在的应用程序都倾向于共享状态。它可以是任何东西,静力学,单身......通常非平凡的应用程序往往会制作复杂的对象图。只是忘记设置null或更多的引用经常忘记从集合中删除一个对象足以导致内存泄漏。 当然,如果处理不当,所有类型的侦听器(如UI侦听器),缓存或任何长期共享状态都会产生内存泄漏。应该理解的是,这不是Java角落案例,也不是垃圾收集器的问题。这是一个设计问题。我们设计我们为一个长期存在的对象添加一个监听器,但是当不再需要时我们不会删除监听器。我们缓存对象,但我们没有策略将它们从缓存中删除。 我们可能有一个复杂的图形来存储计算所需的先前状态。但是之前的状态本身与之前的状态有关,依此类推。 就像我们必须关闭SQL连接或文件一样。我们需要将适当的引用设置为null并从集合中删除元素。我们将有适当的缓存策略(最大内存大小,元素数量或计时器)。允许侦听器得到通知的所有对象都必须同时提供addListener和removeListener方法。当这些通知符不再使用时,他们必须清除他们的听众列表。 内存泄漏确实是可能的,并且完全可以预测。无需特殊语言功能或角落案例。内存泄漏可能表明某些内容可能缺失甚至是设计问题。

fet

赞同来自:

如果您不理解JDBC,以下是一个非常毫无意义的示例。或者至少JDBC希望开发人员在丢弃ConnectionStatementResultSet实例之前关闭它们或丢失对它们的引用,而不是依赖于finalize的实现。

void doWork()
{
   try
   {
       Connection conn = ConnectionFactory.getConnection();
       PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
       ResultSet rs = stmt.executeQuery();
       while(rs.hasNext())
       {
          ... process the result set
       }
   }
   catch(SQLException sqlEx)
   {
       log(sqlEx);
   }
}
上面的问题是Connection对象没有关闭,因此物理连接将保持打开状态,直到垃圾收集器出现并看到它无法访问。 GC将调用finalize方法,但是有些JDBC驱动程序没有实现finalize,至少与实现Connection.close的方式不同。由此产生的行为是,由于收集了无法访问的对象而将回收内存,因此可能无法回收与Connection对象关联的资源(包括内存)。 在Connectionfinalize方法没有清理所有内容的情况下,实际上可能会发现与数据库服务器的物理连接将持续几个垃圾收集周期,直到数据库服务器最终发现连接不存在(如果是并且应该关闭。 即使JDBC驱动程序要实现finalize,也可能在最终确定期间抛出异常。由此产生的行为是,与现在“休眠”对象关联的任何内存都不会被回收,因为保证finalize只被调用一次。 在对象最终化期间遇到异常的上述情况与另一个可能导致内存泄漏的情况有关 - 对象复活。对象复活通常是通过从另一个对象创建对象的强引用来有意识地完成的。当对象复活被滥用时,它将导致内存泄漏以及其他内存泄漏源。 你可以想出更多的例子 - 比如
  • 管理一个List实例,您只添加到列表中而不是从中删除(尽管您应该删除不再需要的元素),或者
  • 打开Sockets或Files,但不再需要时关闭它们(类似于涉及Connection类的上述示例)。
  • 在关闭Java EE应用程序时不卸载单例。显然,加载单例类的类加载器将保留对类的引用,因此永远不会收集单例实例。当部署应用程序的新实例时,通常会创建一个新的类加载器,并且由于单例,前一个类加载器将继续存在。

vanimi

赞同来自:

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

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }
这是引用计数垃圾收集器的典型问题。然后,您可以礼貌地解释JVM使用更复杂的算法,但没有此限制。 -Wes Tarle

uut

赞同来自:

最近我遇到了一种更为微妙的资源泄漏。 我们通过类加载器的getResourceAsStream打开资源,并且输入流句柄没有关闭。 嗯,你可能会说,多么愚蠢。 那么,有趣的是:这样,你可以泄漏底层进程的堆内存,而不是来自JVM的堆。 您只需要一个jar文件,其中包含一个文件,该文件将从Java代码中引用。 jar文件越大,分配的内存越快。 您可以使用以下类轻松创建此类jar:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class BigJarCreator {
    public static void main(String[] args) throws IOException {
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
        zos.putNextEntry(new ZipEntry("resource.txt"));
        zos.write("not too much in here".getBytes());
        zos.closeEntry();
        zos.putNextEntry(new ZipEntry("largeFile.out"));
        for (int i=0 ; i<10000000 ; i++) {
            zos.write((int) (Math.round(Math.random()*100)+20));
        }
        zos.closeEntry();
        zos.close();
    }
}
只需粘贴到名为BigJarCreator.java的文件中,从命令行编译并运行它:
javac BigJarCreator.java
java -cp . BigJarCreator
Etvoilà:你在当前的工作目录中找到一个jar存档,里面有两个文件。 让我们创建第二个类:
public class MemLeak {
    public static void main(String[] args) throws InterruptedException {
        int ITERATIONS=100000;
        for (int i=0 ; i<ITERATIONS ; i++) {
            MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
        }
        System.out.println("finished creation of streams, now waiting to be killed");
Thread.sleep(Long.MAX_VALUE);
    }
}
这个类基本上什么都不做,但创建了未引用的InputStream对象。这些对象将立即被垃圾收集,因此不会对堆大小产生影响。 对于我们的示例来说,从jar文件加载现有资源非常重要,大小在这里很重要! 如果您有疑问,请尝试编译并启动上面的类,但请确保选择合适的堆大小(2 MB):
javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak
这里不会遇到OOM错误,因为没有保留引用,无论您在上面的示例中选择了多大的ITERATIONS,应用程序都将继续运行。 除非应用程序进入wait命令,否则进程的内存消耗(在顶层(RES / RSS)或进程资源管理器中可见)会增长。在上面的设置中,它将在内存中分配大约150 MB。 如果您希望应用程序安全播放,请在创建它的位置关闭输入流:
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();
并且您的过程不会超过35 MB,与迭代次数无关。 非常简单和令人惊讶。

yquo

赞同来自:

获取在任何servlet容器中运行的任何Web应用程序(Tomcat,Jetty,Glassfish,等等......)。连续重新部署应用程序10次或者20次(可能只需触摸服务器的autodeploy目录中的WAR即可。 除非有人实际测试过这个,否则很有可能在经过几次重新部署后你会得到一个OutOfMemoryError,因为应用程序没有注意自己清理。您甚至可以通过此测试在服务器中找到错误。 问题是,容器的生命周期比应用程序的生命周期长。您必须确保容器可能对应用程序的对象或类的所有引用都可以进行垃圾回收。 如果只有一个引用在您的Web应用程序取消部署后仍然存在,则相应的类加载器将导致您的Web应用程序的所有类都无法进行垃圾回收。 您的应用程序启动的线程,ThreadLocal变量,日志记录附加程序是导致类加载器泄漏的一些常见嫌疑。

dsit

赞同来自:

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

somnis

赞同来自:

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

  • 如果你可以使用原生,你可以分配你以后不会放弃的记忆。
  • 如果没有这个,那么关于java的一个肮脏的小秘密并不是很多人都知道的。您可以要求不由GC管理的直接访问阵列,因此可以很容易地用于导致内存泄漏。这是由DirectByteBuffer(http://download.oracle.com/javase/1.5.0/docs/api/java/nio/ByteBuffer.html#allocateDirect(int))提供的。
  • 如果你不能使用其中任何一个,你仍然可以通过欺骗GC来造成内存泄漏。 JVM使用Generational垃圾回收集实现。这意味着堆被分为几个区域:年轻人,成年人和老年人。创建的对象从年轻区域开始。随着他的使用越来越多,他逐渐成长为成年人。到达老年人区域的物体很可能不会被收集。您不能确定物体是否泄漏,如果您要求停止并清洁GC,它可能会清洁它,但是很长一段时间它会被泄漏。更多信息,请访问(http://java.sun.com/docs/hotspot/gc1.4.2/faq.html)
  • 此外,类对象不需要GC。我可以这样做。

eomnis

赞同来自:

我最近修复的一个例子是创建新的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.
图片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.

tmodi

赞同来自:

下面将有一个非显而易见的案例,其中Java泄漏,除了被遗忘的侦听器的标准情况,静态引用,哈希映射中的虚假/可修改键,或者只是没有任何机会结束其生命周期的线程。

  • File.deleteOnExit() - 总是泄漏字符串,如果字符串是子字符串,泄漏情况更糟(底层char []也泄漏) - 在Java中7子字符串也复制char[],所以后者不适用; @Daniel,不需要投票。
我将专注于线程,以显示大多数非托管线程的危险,不希望甚至触摸挥杆。
  • Runtime.addShutdownHook并且不删除...然后甚至使用removeShutdownHook由于ThreadGroup类中有关未启动线程的错误而无法收集它,有效地泄漏了ThreadGroup。 JGroup在GossipRouter中有漏洞。
  • 创建但未启动的Thread与上述类别相同。
  • 创建一个线程继承ContextClassLoaderAccessControlContext,加上ThreadGroup和任何InheritedThreadLocal,所有这些引用都是潜在的泄漏,以及类加载器和所有静态引用加载的整个类,以及ja-ja。整个j.u.c.Executor框架具有超级简单的ThreadFactory接口,但效果特别明显,但大多数开发人员都不知道潜伏的危险。此外,许多库都会根据请求启动线程(太多行业流行的库)。
  • ThreadLocal缓存;那些在许多情况下是邪恶的。我确信每个人都已经看到了很多基于ThreadLocal的简单缓存,这也是坏消息:如果线程持续超过预期生命的上下文ClassLoader,那么这是一个纯粹的小漏洞。除非确实需要,否则不要使用ThreadLocal缓存。
  • 当ThreadGroup本身没有线程时调用ThreadGroup.destroy(),但它仍保留子ThreadGroups。一个错误的泄漏会阻止ThreadGroup从其父级中删除,但所有子级都变得不可枚举。
  • 使用WeakHashMap和值(in)直接引用键。没有堆转储,这是一个很难找到的。这适用于所有可能将硬引用保留回受保护对象的扩展Weak/SoftReference
  • java.net.URL与HTTP(S)协议一起使用,并从(!)加载资源。这个是特殊的,KeepAliveCache在系统ThreadGroup中创建一个新线程,它泄漏当前线程的上下文类加载器。当没有活动线程存在时,线程是在第一个请求时创建的,所以你可能会幸运或只是泄漏。泄漏已经在Java 7中得到修复,并且创建线程的代码正确地删除了上下文类加载器。还有一些创建类似线程的情况(,如ImageFetcher ,也已修复)。
  • 使用InflaterInputStream在构造函数中传递new java.util.zip.Inflater()(例如PNGImageDecoder)而不调用inflater的end()。好吧,如果你只使用new传入构造函数,那就没有机会了......是的,如果它作为构造函数参数手动传递,则在流上调用close()不会关闭inflater。这不是真正的泄漏,因为它会被终结者释放......当它认为有必要时。直到那一刻它吃掉本机内存如此糟糕,它可能导致Linux oom_killer肆无忌惮地杀死进程。主要问题是Java的最终确定非常不可靠,G1在7.0.2之前变得更糟。故事的道德:尽快释放本地资源;终结者太穷了。
  • java.util.zip.Deflater相同的情况。这个更糟糕,因为Deflater在Java中需要内存,即总是使用15位(最大)和8位内存级别(最大值为9)分配几百KB的本机内存。幸运的是,Deflater并未得到广泛使用,据我所知,JDK不包含任何错误。如果手动创建DeflaterInflater,请始终调用end()。最后两个中最好的部分:您无法通过常用的分析工具找到它们。
(我可以根据要求添加更多时间浪费。) 祝你好运,保持安全;泄漏是邪恶的!

sit_in

赞同来自:

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

public E remove(int index) {
    RangeCheck(index);
modCount++;
    E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work
return oldValue;
}
如果你自己实现它,你会想到清除不再使用的数组元素(elementData[--size] = null)吗?这个参考可能会让一个巨大的物体活着......

eomnis

赞同来自:

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

quia_a

赞同来自:

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

inemo

赞同来自:

我最近遇到了log4j造成的内存泄漏情况。 Log4j具有这种称为Nested Diagnostic Context(NDC)的机制,它是一种区分不同来源的交错日志输出的工具。 NDC工作的粒度是线程,因此它分别区分不同线程的日志输出。 为了存储特定于线程的标记,log4j的NDC类使用一个由Thread对象本身键入的Hashtable(而不是线程id),因此直到NDC标记在内存中保留所有挂起线程的对象对象也留在记忆中。在我们的Web应用程序中,我们使用NDC标记带有请求ID的logoutput,以区分日志和单个请求。将NDC标记与线程相关联的容器也会在从请求返回响应时将其删除。在处理请求的过程中,生成了一个子线程,类似于以下代码:

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    
因此,NDC上下文与生成的内联线程相关联。作为此NDC上下文的键的线程对象是内联线程,其中有hugeList对象。因此,即使在线程完成其正在执行的操作之后,对NDC上下文Hastable仍然保持对hugeList的引用,从而导致内存泄漏。

oqui

赞同来自:

我在java中看到的大多数内存泄漏都与进程不同步有关。 进程A通过TCP与B通信,并告诉进程B创建一些东西。 B向资源发出一个ID,比如432423,A存储在一个对象中并在与B交谈时使用。在某些时候,A中的对象被垃圾收集回收(可能是由于一个bug),但是A从不告诉B(也许是另一个bug)。 现在A不再具有它在B的RAM中创建的对象的ID,并且B不知道A没有对该对象的更多引用。实际上,该对象被泄露。

sea

赞同来自:

GUI代码中的一个常见示例是创建窗口小部件/组件并向某个静态/应用程序范围对象添加侦听器,然后在窗口小部件被销毁时不删除侦听器。您不仅会遇到内存泄漏,而且还会受到性能影响,因为无论您何时正在收听火灾事件,您的所有旧听众都会被调用。

reum

赞同来自:

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

fet

赞同来自:

一个不终止的线程(比如在其run方法中无限期地休眠)。即使我们放弃了对它的引用,它也不会被垃圾收集。您可以添加字段以使线程对象变得很大。 目前最热门的答案列出了更多的技巧,但这些似乎是多余的。

comnis

赞同来自:

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

  1. 声明原生方法。
  2. 在本机方法中,调用malloc。请勿致电free
  3. 调用本机方法。
请记住,本机代码中的内存分配来自JVM堆。

ncum

赞同来自:

java中的内存泄漏不是典型的C / C++内存泄漏。 要了解JVM的工作原理,请阅读Understanding Memory Management。 基本上,重要的是:

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。

jalias

赞同来自:

线程在终止之前不会被收集。它们用作垃圾收集的roots。它们是为数不多的仅通过忘记它们或清除对它们的引用而无法回收的对象之一。 考虑:终止工作线程的基本模式是设置线程看到的一些条件变量。线程可以定期检查变量并将其用作终止信号。如果变量未声明为volatile,则线程可能看不到对变量的更改,因此它不会知道终止。或者想象一下,如果某些线程想要更​​新共享对象,但在尝试锁定它时会出现死锁。 如果您只有少数线程,这些错误可能会很明显,因为您的程序将无法正常工作。如果您有一个根据需要创建更多线程的线程池,那么可能不会注意到过时/卡住的线程,并且将无限累积,从而导致内存泄漏。线程可能会在您的应用程序中使用其他数据,因此也会阻止他们直接引用的任何数据被收集。 作为一个玩具示例:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}
您可以随意调用System.gc(),但传递给leakMe的对象永远不会消亡。 (*编辑*)

cvelit

赞同来自:

如果最大堆大小是X. Y1 .... Yn没有实例所以,总内存=实例数X每个实例的字节数。如果X1 ...... Xn是每个实例的字节数。那么总内存(M)= Y1 X1 + ..... + Yn Xn。所以,如果M> X,它超过了堆空间。以下可能是代码中的问题 1.使用更多实例变量然后使用本地变量。 2.每次创建实例而不是汇集对象。 3.不按需创建对象。 4.在完成操作后使对象引用为null。再次,在程序中需要时重新创建。

afuga

赞同来自:

我曾经有过一次与PermGen和XML解析有关的“内存泄漏”。 我们使用的XML解析器(我不记得它是哪一个)在标记名称上做了一个String.intern(),以便更快地进行比较。 我们的一位客户最好不要将数据值存储在XML属性或文本中,而是存储为标记名,因此我们有一个文档,如:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>
实际上,他们并没有使用数字,而是使用更长的文本ID(大约20个字符),这些ID是独一无二的,每天的速度为10-15百万。这样每天就会产生200亿MB的垃圾,这种垃圾再也不需要了,而且从来没有GCed(因为它是PermGen)。我们将permgen设置为512 MB,因此内存异常(OOME)需要大约两天才能到达......

liste

赞同来自:

这是一个简单/险恶的http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29

public class StringLeaker
{
    private final String muchSmallerString;
public StringLeaker()
    {
        // Imagine the whole Declaration of Independence here
        String veryLongString = "We hold these truths to be self-evident...";
// The substring here maintains a reference to the internal char[]
        // representation of the original string.
        this.muchSmallerString = veryLongString.substring(0, 1);
    }
}
因为子串指的是原始字符串的内部表示,所以原始字符串保留在内存中。因此,只要你有一个StringLeaker,你就可以将整个原始字符串放在内存中,即使你可能认为你只是坚持使用单字符字符串。 避免将不需要的引用存储到原始字符串的方法是执行以下操作:
...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...
对于增加的不良,您可能还有.intern()子字符串:
...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...
即使在丢弃StringLeaker实例之后,这样做也会将原始长字符串和派生子字符串保留在内存中。

enemo

赞同来自:

在Java中没有内存泄漏这样的东西。内存泄漏是从C等人那里借来的一句话。 Java在GC的帮助下内部处理内存分配。内存浪费(即留下搁浅的对象),但不是内存泄漏。

nsunt

赞同来自:

我认为一个有效的例子可能是在线程被池化的环境中使用ThreadLocal变量。 例如,使用Servlet中的ThreadLocal变量与其他Web组件进行通信,让容器创建线程并在池中维护空闲的线程。 ThreadLocal变量如果没有被正确清理,将会存在,直到可能相同的Web组件覆盖它们的值。 当然,一旦确定,问题就可以轻松解决。

mut

赞同来自:

什么是内存泄漏:

  • 这是由错误或糟糕的设计造成的。
  • 这是浪费记忆。
  • 随着时间的推移会变得更糟。
  • 垃圾收集器无法清除它。
典型例子: 对象缓存是弄乱事物的好起点。
private static final Map<String, Info> myCache = new HashMap<>();
public void getInfo(String key)
{
    // uses cache
    Info info = myCache.get(key);
    if (info != null) return info;
// if it's not in cache, then fetch it from the database
    info = Database.fetch(key);
    if (info == null) return null;
// and store it in the cache
    myCache.put(key, info);
    return info;
}
您的缓存增长和增长。很快整个数据库就被吸进了内存。更好的设计使用LRUMap(仅在缓存中保留最近使用的对象)。 当然,你可以让事情变得更复杂:
  • 使用ThreadLocal构造。
  • 添加更复杂的参考树。
  • 或第三方图书馆造成的泄密。
经常发生的事情: 如果此Info对象具有对其他对象的引用,则其他对象也会引用其他对象。在某种程度上,你也可以认为这是某种内存泄漏(由不良设计引起)。

bea

赞同来自:

我认为还没有人说过这个:你可以通过覆盖finalize()方法来复活一个对象,以便finalize()在某处存储一个引用。垃圾收集器只会在对象上调用一次,因此对象永远不会被销毁。

qullam

赞同来自:

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

public class OutOfMemory {
public static void main(String[] arg) {
List<Long> mem = new LinkedList<Long>();
        while (true) {
            mem.add(new Long(Long.MAX_VALUE));
        }
    }
}

yut

赞同来自:

我觉得有趣的是没有人使用内部类的例子。如果你有内部课程;它本身就维护了对包含类的引用。当然,从技术上讲,它不是内存泄漏,因为Java最终会将其清理干净;但这可能会导致课程比预期更长时间。

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}
现在,如果您调用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.
  }
}
我也听说过一个谣言,如果你的变量存在的时间超过特定的时间; Java假设它将永远存在,并且如果再也无法在代码中访问它,它实际上永远不会尝试清理它。但这完全未经证实。

siure

赞同来自:

在Java中,“内存泄漏”主要是你使用了太多的内存,这与你不再使用内存而忘记返回(免费)的C不同。当面试官询问Java内存泄漏时,他们会询问JVM内存使用情况是否会继续上升,并且他们确定定期重新启动JVM是最佳解决方案。 (除非面试官非常精通技术) 所以回答这个问题,好像他们问到了什么使JVM内存使用量随着时间的推移而增长好的答案是在HttpSessions中存储过多的数据,超时时间过长或内存缓存(Singleton)实现不好,从而不会刷新旧条目。另一个可能的答案是拥有大量JSP或动态生成的类。类被加载到一个名为PermGen的内存区域,该区域通常很小,大多数JVM都不实现类卸载。

ut_sit

赞同来自:

正如许多人所建议的那样,资源泄漏很容易引起 - 就像JDBC示例一样。实际的内存泄漏有点困难 - 特别是如果你不依赖于JVM的破碎位来为你做这件事...... 创建具有非常大的占用空间然后无法访问它们的对象的想法也不是真正的内存泄漏。如果没有什么可以访问它那么它将被垃圾收集,如果有东西可以访问它那么它不是泄漏... 过去工作的一种方式 - 我不知道它是否仍然存在 - 是有一个三深的圆形链。如在对象A中引用了对象B,对象B具有对象C的引用,而对象C具有对对象A的引用.GC足够聪明地知道两条深链 - 如在A< - >中B - 如果A和B无法通过其他任何方式访问,可以安全地收集,但无法处理三向链...

iipsa

赞同来自:

面试官可能正在寻找一个循环引用,如下面的代码(顺便说一下,只有在使用引用计数的非常旧的JVM中泄漏内存,情况不再如此)。但这是一个非常模糊的问题,因此这是展示您对JVM内存管理的理解的绝佳机会。

class A {
    B bRef;
}
class B {
    A aRef;
}
public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}
然后您可以通过引用计数来解释,上面的代码会泄漏内存。但是大多数现代JVM不再使用引用计数,大多数使用扫描垃圾收集器,实际上会收集这个内存。 接下来,您可以解释创建具有基础本机资源的Object,如下所示:
public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}
然后你可以解释这在技术上是一个内存泄漏,但实际上泄漏是由JVM中的本机代码分配底层本机资源引起的,这些资源没有被Java代码释放。 在一天结束时,使用现代JVM,您需要编写一些Java代码,以便在JVM意识的正常范围之外分配本机资源。

uid

赞同来自:

答案完全取决于面试官的想法。 在实践中是否有可能使Java泄漏?当然是,并且在其他答案中有很多例子。 但是有多个元问题可能会被问到?

  • 理论上“完美”的Java实现是否容易受到泄漏?
  • 候选人是否理解理论与现实之间的区别?
  • 候选人是否了解垃圾收集的工作原理?
  • 或者垃圾收集在理想情况下应该如何工作?
  • 他们是否知道可以通过原生界面调用其他语言?
  • 他们知道用其他语言泄漏记忆吗?
  • 候选人是否知道内存管理是什么,以及Java中幕后发生了什么?
我正在读你的元问题:“在这次面试中我能用到什么答案”。因此,我将专注于面试技巧而不是Java。我相信你更有可能在面试中重复不知道问题答案的情况,而不是在需要知道如何使Java泄漏的地方。所以,希望这会有所帮助。 您可以为面试开发的最重要的技能之一是学习积极倾听问题并与面试官合作以提取他们的意图。这不仅可以让你以他们想要的方式回答他们的问题,而且还表明你有一些重要的沟通技巧。当谈到许多同样有才华的开发人员之间的选择时,我会聘请那些在每次回复之前倾听,思考和理解的人。

tmodi

赞同来自:

从有效的java书

  1. 每当一个类管理自己的内存时,程序员应该是 提醒内存泄漏
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);
}
} 你能发现内存泄漏吗? 那么内存泄漏在哪里?如果堆栈增长然后收缩,则对象 即使是程序,从堆栈弹出的也不会被垃圾收集 使用堆栈没有更多的引用。这是因为堆栈维护 对这些对象的过时引用。过时的参考只是一个参考 永远不会再被解除引用。在这种情况下,任何引用之外 元素数组的“活动部分”已过时。活动部分包括 索引小于大小的元素。

cnisi

赞同来自:

从finalize方法中抛出未处理的异常。

uut

赞同来自:

您可以使用sun.misc.Unsafe类进行内存泄漏。实际上,此服务类用于不同的标准类(例如,在java.nio类中)。您无法直接创建此类的实例,但您可以使用反射来执行此操作。 代码无法在Eclipse IDE中编译 - 使用命令javac进行编译(在编译期间,您将收到警告)

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class TestUnsafe {
public static void main(String[] args) throws Exception{
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field f = unsafeClass.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.print("4..3..2..1...");
        try
        {
            for(;;)
                unsafe.allocateMemory(1024*1024);
        } catch(Error e) {
            System.out.println("Boom :)");
            e.printStackTrace();
        }
    }
}

nnam

赞同来自:

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

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

cet

赞同来自:

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

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}
在冗长的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 cant remove
str.intern();
(未封闭的)开放流(文件,网络等......)
try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}
未封闭的连接
try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}
JVM的垃圾收集器无法访问的区域,例如通过本机方法分配的内存 在Web应用程序中,某些对象存储在应用程序范围中,直到明确停止或删除应用程序。
getServletContext().setAttribute("SOME_MAP", map);
不正确或不适当的JVM选项,例如IBM JDK上的noclassgc选项,用于防止未使用的类垃圾收集 请参阅IBM jdk settings

aad

赞同来自:

我可以从这里复制我的答案: Easiest way to cause memory leak in Java? “当计算机程序消耗内存但无法将其释放回操作系统时,会发生计算机科学(或泄漏,在此上下文中)的内存泄漏。” (维基百科) 简单的答案是:你不能。 Java执行自动内存管理,并将释放您不需要的资源。你不能阻止这种情况发生。它总是能够释放资源。在具有手动内存管理的程序中,这是不同的。你可以使用malloc()在C中获得一些内存。要释放内存,需要malloc返回的指针并在其上调用free()。但是如果你不再使用指针(覆盖或超过生命周期),那么你很遗憾无法释放这个内存,因此你有内存泄漏。 到目前为止,所有其他答案都在我的定义中并不是真正的内存泄漏。它们都旨在快速地用无意义的东西填充内存。但是在任何时候你仍然可以取消引用你创建的对象,从而释放内存 - >没有泄漏。尽管我不得不承认,acconrad's answer非常接近,因为他的解决方案实际上只是通过强制它在无限循环中“崩溃”垃圾收集器。 长的答案是:您可以通过使用JNI编写Java库来获取内存泄漏,JNI可能具有手动内存管理,因此存在内存泄漏。如果你调用这个库,你的java进程将泄漏内存。或者,您可以在JVM中出现错误,以便JVM丢失内存。 JVM中可能存在错误,甚至可能存在一些已知错误,因为垃圾收集不是那么简单,但它仍然是一个错误。按设计这是不可能的。你可能会要求一些受这种bug影响的java代码。对不起我不知道一个,无论如何它在下一个Java版本中可能不再是一个bug。

sea

赞同来自:

这是在纯Java中创建真正的内存泄漏(通过运行代码但仍然存储在内存中无法访问的对象)的好方法:

  1. 应用程序创建一个长时间运行的线程(或者使用线程池来更快地泄漏)。
  2. 线程通过(可选自定义)ClassLoader加载一个类。
  3. 类分配大块内存(例如new byte[1000000]),在静态字段中存储对它的强引用,然后在ThreadLocal中存储对自身的引用。分配额外的内存是可选的(泄漏Class实例就足够了),但它会使泄漏工作更快。
  4. 该线程清除对自定义类或从中加载的ClassLoader的所有引用。
  5. 重复。
这是有效的,因为ThreadLocal保留对该对象的引用,该对象保持对其Class的引用,而Class又保持对其ClassLoader的引用。反过来,ClassLoader保持对它已加载的所有类的引用。 (在许多JVM实现中,尤其是在Java 7之前,情况更糟,因为Classes和ClassLoader直接分配到permgen并且根本就没有GC。但是,无论JVM如何处理类卸载,ThreadLocal仍然会阻止被回收的类对象。) 此模式的一个变体是,如果您经常重新部署碰巧以任何方式使用ThreadLocals的应用程序,那么应用程序容器(如Tomcat)可能会像筛子那样泄漏内存。 (由于应用程序容器使用了所描述的线程,每次重新部署应用程序时都会使用新的ClassLoader。) 更新:由于很多人不断要求它,here's some example code that shows this behavior in action

yut

赞同来自:

java 1.6中的String.substring方法创建内存泄漏。这篇博文解释了它。 http://javarevisited.blogspot.com/2011/10/how-substring-in-java-works.html

id_cum

赞同来自:

一些建议:

  • 在servlet容器中使用commons-logging(可能有点挑衅)
  • 在servlet容器中启动一个线程,不要从它的run方法返回
  • 在servlet容器中加载GIF动画(这将启动一个动画线程)
通过重新部署应用程序可以“改善”上述效果;) 最近偶然发现了这个:
  • 调用“new java.util.zip.Inflater();”没有调用“Inflater.end()”曾经
阅读http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5072161和链接的问题以进行深入讨论。

dquia

赞同来自:

也许通过JNI使用外部本机代码? 使用纯Java,几乎是不可能的。 但这是关于“标准”类型的内存泄漏,当您无法再访问内存时,它仍然由应用程序拥有。您可以保留对未使用对象的引用,或者在不关闭它们的情况下打开流。

cnon

赞同来自:

一个简单的事情是使用不正确(或不存在)hashCode()equals()的HashSet,然后继续添加“重复”。而不是忽略重复,它只会增长,你将无法删除它们。 如果你想让这些坏键/元素闲逛,你可以使用静态字段

class BadKey {
   // no hashCode or equals();
   public final String key;
   public BadKey(String key) { this.key = key; }
}
Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.

non_et

赞同来自:

关于如何在Java中创建内存泄漏有很多答案,但请注意在访谈期间提出的要点。 “如何使用Java创建内存泄漏?”是一个开放式问题,其目的是评估开发人员的经验程度。 如果我问你“你有解决Java中内存泄漏问题的经验吗?”,你的答案将是一个简单的“是”。 然后我会跟进“你能举例说明你在哪里解决内存泄漏问题吗?”,你会给我一两个例子。 然而,当面试官问“如何用Java创建内存泄漏?”预期答案应遵循以下方针:

  • 我遇到了内存泄漏......(说什么时候)[告诉我经验]
  • 导致它的代码是......(解释代码)[你自己修理]
  • 我应用的修复程序基于...(解释修复)[这让我有机会询问有关修复的具体信息]
  • 我做的测试是...... [让我有机会询问其他测试方法]
  • 我用这种方式记录了...... [加分。好的,如果你记录下来的那样]
  • 因此,有理由认为,如果我们按相反的顺序执行此操作,即获取修复的代码,并删除我的修复程序,那么我们就会发生内存泄漏。
当开发人员不遵循这一思路时,我试着引导他/她问“你能给我一个Java泄漏内存的例子吗?”,然后是“你有没有必要修复Java中的任何内存泄漏?” 请注意,我不是要求一个关于如何在Java中泄漏内存的示例。那太傻了。谁会对能够有效编写泄漏内存的代码的开发人员感兴趣?

wmagni

赞同来自:

使用对话框,Swing非常容易。创建一个JDialog,显示它,用户关闭它,泄漏! 您必须致电dispose()或配置setDefaultCloseOperation(DISPOSE_ON_CLOSE)

zomnis

赞同来自:

像这样!

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

ksit

赞同来自:

创建一个静态Map并继续添加对它的硬引用。那些永远不会是GC。

public class Leaker {
    private static final Map<String, Object> CACHE = new HashMap<String, Object>();
// Keep adding until failure.
    public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}

sit_et

赞同来自:

每当你保持对不再需要的对象的引用时,就会发生内存泄漏。有关内存泄漏如何在Java中表现出来以及您可以采取哪些措施的示例,请参阅Handling memory leaks in Java programs

psunt

赞同来自:

粗心地在具有自己生命周期的类中使用非静态内部类。 在Java中,非静态内部类和匿名类包含对其外部类的隐式引用。另一方面,静态内部类则不然。 以下是在Android中发生内存泄漏的常见示例,但这并不明显:

public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() { //non-static inner class, holds the reference to the SampleActivity outter class
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }
@Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
// Post a message and delay its execution for a long time.
    mLeakyHandler.postDelayed(new Runnable() {//here, the anonymous inner class holds the reference to the SampleActivity class too
      @Override
      public void run() {
     //.... 
      }
    }, SOME_TOME_TIME);
// Go back to the previous Activity.
    finish();
  }}
这将防止活动上下文被垃圾收集。

要回复问题请先登录注册