问:我刚参加面试,面试官问我Java代码会导致内存泄露怎么写。我对这个问题一无所知,这很尴尬。A1:内存泄漏(程序代码无法访问某些对象,但它们仍保留在内存中)可以通过以下步骤轻松创建:应用程序创建一个长时间运行的线程(或使用线程池,这会发生得更快内存泄漏)。线程通过类加载器(可以自定义)加载类。这个类分配了一大块内存(比如newbyte[1000000]),将强引用存储在静态变量中,然后将自己的引用存储在ThreadLocal中。分配额外的内存newbyte[1000000]是可选的(类实例泄漏就足够了),但它会使内存泄漏更快。该线程清理自定义类或加载该类的类加载器。重复以上步骤。由于没有对类和类加载器的引用,因此无法访问ThreadLocal中的存储。ThreadLocal持有这个对象的引用,它也持有这个类和它的类加载器的引用。类加载器持有它加载的类的所有引用,因此GC无法回收存储在ThreadLocal中的内存。在很多JVM实现中,Java类和类加载器直接分配到permgen区而不进行GC,这导致了更严重的内存泄漏。这种泄漏模式的一种变体是,如果您频繁地重新部署以任何形式使用ThreadLocal的应用程序,应用程序容器(例如Tomcat)很容易泄漏内存(由于应用程序容器如前所述使用线程,因此每个时都会使用新的类加载器第一次重新部署应用程序)。A2:静态变量引用对象类MemorableClass{staticfinalArrayListlist=newArrayList(100);}调用String.intern()oflongstringStringstr=readString();//读取任何源数据库、文本框/jsp等的冗长字符串。//这会将字符串放入您无法从中删除的内存池str.intern();未关闭打开流(文件、网络等)try{BufferedReaderbr=newBufferedReader(newFileReader(inputFile));......}catch(Exceptione){e.printStacktrace();}连接未关闭try{Connectionconn=ConnectionFactory.getConnection();......}catch(Exceptione){e.printStacktrace();}JVM的GC不可达区域,比如native方法分配的内存。application范围内的web应用的对象没有重启或者没有显式移除getServletContext().setAttribute("SOME_MAP",map);session范围内的web应用的对象没有过期或者没有被session.setAttribute("SOME_MAP",map)显式移除;IBMJDK的noclassgc等不正确或不合适的JVM选项阻止无用类的垃圾回收如果集合不能忽略它应该忽略的元素,它的大小只能继续增长,而这些元素不能被删除。如果你想生成错误的键值对,你可以这样做:classBadKey{//nohashCodeorequals();公共最终字符串键;publicBadKey(Stringkey){this.key=key;}}Mapmap=System.getProperties();map.put(newBadKey("key"),"value");//内存泄漏,即使你的线程死了。A4:除了被遗忘的监听器,静态引用,hashmap中的key错误/修改或线程阻塞等典型的内存泄漏场景无法结束生命周期。下面介绍Java中一些不太明显的内存泄漏案例,主要与线程有关。Runtime.addShutdownHook未移除后,即使使用removeShutdownHook,由于未启动线程的ThreadGroup类的bug,可能无法回收,导致ThreadGroup内存泄漏。线程创建但不启动,同上,创建线程继承ContextClassLoader和AccessControlContext,ThreadGroup和InheritedThreadLocal的使用,所有这些引用都是潜在的泄漏,类加载器加载的所有类和所有静态引用等。.这对整个j.u.c.Executor框架(java.util.concurrent)作为ThreadFactory接口的重要组成部分影响非常明显,很多开发者没有注意到它潜在的危险。许多图书馆会根据要求启动线程。在许多情况下,ThreadLocal缓存不是一个好的做法。基于ThreadLocal的简单缓存的实现有很多,但是如果线程在其预期生命周期之外继续运行,ContextClassLoader就会泄漏。除非确实需要,否则不要使用ThreadLocal缓存。ThreadGroup.destroy()在ThreadGroup本身没有线程但仍然有子线程组时被调用。内存泄漏将阻止线程组从其父线程组中移除,并且无法枚举子线程组。使用Wea??kHashMap时,value直接(间接)引用了key,这是一种很难找到的情况。这也适用于继承Weak/SoftReference的类,这些类可能持有对受保护对象的强引用。使用http(s)协议的java.net.URL下载资源。KeepAliveCache在系统ThreadGroup中创建一个新的线程,导致当前线程的context类加载器内存泄漏。当没有存活的线程时,会在第一次请求时创建线程,因此很可能会发生泄漏。(Java7已经修复,创建线程的代码适当去掉context类加载器。)使用InflaterInputStream在构造函数(如PNGImageDecoder)中传递newjava.util.zip.Inflater(),不调用end充气装置()。如果只是new的话是很安全的,但是如果自己创建类作为构造函数参数,调用stream的close()无法关闭inflater,就可能会出现内存泄漏。这并不是真正的内存泄漏,因为它会被终结器释放。但是这样会消耗大量的native内存,导致linux的oom_killer杀死进程。所以给我们的教训是:尽早释放原生资源。java.util.zip.Deflater也是一样,情况比较严重。好处可能是很少使用Deflater。如果您自己创建一个Deflater或Inflater,请记住调用end()。
