`
deepinmind
  • 浏览: 451547 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:41631
社区版块
存档分类
最新评论

Java的内存泄露

阅读更多

Java有垃圾回收,因此不会出现内存泄露。

大错特错。


这个说法存在好几个问题。尽管Java的确有垃圾回收器来回收那些不用的内存块,但你不要指望它能够点铁成金。GC减轻了开发人员肩上的负担,而原本的那些工作非常容易出错,不过并不是所有内存分配的问题它都能够解决。更糟糕的是,Java的设计允许它可以欺骗GC,使得它能够保留一些程序已经不再使用的内存。经历了20年的C开发以及7年的Java开发后(中间有重叠),我敢说,在这方面Java绝对是远比C/C++要好。尽管它仍有改进的空间。在这些改进成为现实之前,作为开发人员最好能了解下内存处理的基本原理以及一些常见的坑,以免栽到里面去。但首先,

什么是内存泄露?



内存泄露是指程序不停地分配内存,但不再使用的时候却没有释放掉它,这会导致本来就有限的内存的占用量出现飙升,并且这不受程序控制,最终导致程序的运行变慢。


在那些美好的C语言开发的时代,我们说的内存泄露是指程序遗失了某个内存段的引用而没有释放掉它。这种情况下,程序获取不到这个内存区域的句柄或者指针,也无法调用free函数来释放掉它,因此这个内存块会一直处于分配的状态,没法被程序重用,这样就造成了内存的浪费。当然了,程序退出的话,操作系统会回收掉这块内存的。

这是个非常典型的内存泄露,不过我上面给出的定义更广泛一些。还有一种情况是代码仍旧拥有这块内存的指针,尽管现在这块内存已经不用了,但程序也不去释放它。就比如说一个程序员创建了一个链表,把所有通过malloc分配的内存指针全存了进去,但他从来不去调用free函数释放掉它们。结果也是一样的。既然结果是一样的,能不能获取到释放内存的指针也不那么重要了,因为你根本就不去释放它。这只是影响到了解决问题的方式,不过不管是哪种情况,修复BUG总是得修改代码的。

如果我们来看下Java和它的GC,你会发现经典的那个由于释放了内存引用导致无法释放内存的那种情况几乎不可能发生。如果是那样的话GC判断出分配的内存的所有引用已经释放掉了就自己去释放内存了。事实上,这也是Java里面标准的释放内存的方法:你只需不再引用某个对象就可以了,GC会去回收它的。这里没有垃圾桶,也没有分类垃圾箱(不需要你去扔垃圾)。别管它就行了,垃圾回收器会去回收它的。这也正是很多开发人员认为Java不存在内存泄露的原因。从实际的角度来看这的确几乎是正确的:和使用C/C++或者其它没有垃圾回收器的语言相比,使用Java开发内存泄露的麻烦事的确少了不少。

我们终于要说到重点了:Java里面是如何发生内存泄露的?

线程以及线程本地存储就非常容易产生内存泄露。通过下面的五个步骤可以很容易地产生内存泄露:

1. 应用程序创建一个长时间运行的线程(或者使用线程池,那样泄露会更快一些)。
2. 线程通过某个类加载器加载了一个类。
3. 这个类分配了一大块内存(比如,new byte[1000000]),并且在它的静态字段中存储了一个强引用,然后在ThreadLocal中存储它自身的一个引用。额外分配的这个内存(new byte[1000000])其实是多余的(类实例的泄露就足够了),不过这样能使内存泄露的速度更快一些。
4. 线程清理了自定义的类以及加载它的类加载器的所有引用。
5. 重复以上步骤。

因为你已经没有这个类以及它的类加载器的引用了,也就不能再访问它的ThreadLocal中的存储了,因此你也就无法访问分配的这块内存(除非你开始用反射来获取)。但是这个ThreadLocal的存储还存在引用,因此GC无法回收这块内存。这个线程本地的存储不是弱引用(顺便提一句,为什么不用弱引用?)

如果你从来没有类似的经验,你可能会想,这得多脑残才能搞出这么极端的一个场景。但事实上,上述这种泄露的模式是非常自然的(好吧,程序员们,你们可别故意这么搞 ),当你在Tomcat上调试自己的程序的时候就会出现这样的泄露。对Java来说这太正常了。重新部署应用但却不重启Tomcat实例,这通常会导致内存越来越少,这正是发生了上述的这种泄露,很少有Tomcat能够避免这种情况。应用程序应当谨慎地使用ThreadLocal。

使用静态变量存储大块数据时也应当同样小心。最好避免使用静态变量,除非你很相信这个运行你程序的容器不会发生泄露。这些容器的类加载的层次结构和Java的比起来要灵活多了。如果你把大量的数据存储到一个Map或者Set里,为什么不使用它们的弱引用的版本呢?如果KEY都没了,还需要关联的那个值干嘛?

现在来说下HashMap和HashSet。如果你使用了没有实现或者错误地实现了eqauls和hashCode方法的对象来作为KEY的话,调用put()方法会把你的数据扔向深渊。你再也没法恢复它了,更糟糕的是,每当你再放一个对象到这个集合中的时候,还会产生更多的副本。你把你的内存带上了一条不归路。

在Java中,还有许许多多的内存泄露的例子。尽管它们和C/C++相比,出现的频率要少得多。通常来说,有GC总比没有的要好。

原创文章转载请注明出处:http://it.deepinmind.com

英文原文链接
4
3
分享到:
评论
9 楼 风云无浪 2014-05-08  
mfkvfn 写道
这不叫内存泄漏吧?应该叫“内存满载”,无内存可用时会OutOfMemery,一般也翻译成“内存溢出”,从没听人说“内存泄漏”

OutOfMemory是所有的内存都是合理的情况下,楼主说的情况是可能导致部分内存永远无法访问,造成的内存浪费。
8 楼 mfkvfn 2014-05-07  
这不叫内存泄漏吧?应该叫“内存满载”,无内存可用时会OutOfMemery,一般也翻译成“内存溢出”,从没听人说“内存泄漏”
7 楼 lvwenwen 2014-05-07  
mark 学习下
6 楼 deepinmind 2014-05-07  
风云无浪 写道
deepinmind 写道
风云无浪 写道
1、ThreadLocal不应该在线程结束时销毁么?当ThreadLocal被销毁的时候,那块内存不就被释放了么?在线程池里,ThreadLocal是不会被销毁的。
2、静态变量是静态内存区域,当没有引用的时候,自然会被回收。

“在Java中,还有许许多多的内存泄露的例子。”这句话说的非常不对,在JAVA里内存还是很安全的。我忘了Thinking in Java里的内存泄漏的例子了,好像Native的code要多注意。


你说的没错。不过安全是相对的,相对来说在Java里内存的确还是很安全的。

1的话,原文中的场景应该是指这个线程在一直运行的,还没有销毁,就比如Tomcat没重启,容器的线程还没有销毁。2. 如果类加载器发生了泄露,它加载的类也会随之泄露的,包括它的静态变量。在Java EE的许多容器里,都很容易出现类加载器泄露的情况。


1.线程不销毁的话,ThreadLocal始终应该占着一个内存,之前的应该会被GC回收掉吧?我觉得这和tomcat的重启还不太一样,tomcat启动要加载很多东西,可能是那些引起的问题。而ThreadLocal只是一个指针而已
2.类加载器没有研究过,本身应该没问题吧,可能有问题的应该是被加载的类是否有native之类的代码。我觉得归根到底不是java自己的代码引起的内存泄漏。


嗯,有空我先针对这种情况写个程序试试,看看会不会出现传说中的泄露
5 楼 deepinmind 2014-05-07  
kidneyball 写道
引用

因此这个内存块会一直处于分配的状态,没法被程序重用,这样就造成了内存的浪费。当然了,程序退出的话,操作系统会回收掉这块内存的。


红字是不对的,至少在win98时代(现在如何不清楚了),应用程序分配heap内存是向系统登记的,系统不会区分这片内存是被谁分配走了,自然在程序退出时,也不会主动释放。如果程序不主动释放的话,那么直到下次重启系统,这片内存就无法被系统重用了。这才是“内存泄漏”最开始的定义 ———— “系统总内存慢慢越来越少”。

Java最开始标榜的“因此不会出现内存泄漏”主要是针对这种情况。当然真实原因不是它有垃圾回收,而是它有JVM。


感谢,学习了~
4 楼 kidneyball 2014-05-07  
引用

因此这个内存块会一直处于分配的状态,没法被程序重用,这样就造成了内存的浪费。当然了,程序退出的话,操作系统会回收掉这块内存的。


红字是不对的,至少在win98时代(现在如何不清楚了),应用程序分配heap内存是向系统登记的,系统不会区分这片内存是被谁分配走了,自然在程序退出时,也不会主动释放。如果程序不主动释放的话,那么直到下次重启系统,这片内存就无法被系统重用了。这才是“内存泄漏”最开始的定义 ———— “系统总内存慢慢越来越少”。

Java最开始标榜的“因此不会出现内存泄漏”主要是针对这种情况。当然真实原因不是它有垃圾回收,而是它有JVM。
3 楼 风云无浪 2014-05-07  
deepinmind 写道
风云无浪 写道
1、ThreadLocal不应该在线程结束时销毁么?当ThreadLocal被销毁的时候,那块内存不就被释放了么?在线程池里,ThreadLocal是不会被销毁的。
2、静态变量是静态内存区域,当没有引用的时候,自然会被回收。

“在Java中,还有许许多多的内存泄露的例子。”这句话说的非常不对,在JAVA里内存还是很安全的。我忘了Thinking in Java里的内存泄漏的例子了,好像Native的code要多注意。


你说的没错。不过安全是相对的,相对来说在Java里内存的确还是很安全的。

1的话,原文中的场景应该是指这个线程在一直运行的,还没有销毁,就比如Tomcat没重启,容器的线程还没有销毁。2. 如果类加载器发生了泄露,它加载的类也会随之泄露的,包括它的静态变量。在Java EE的许多容器里,都很容易出现类加载器泄露的情况。


1.线程不销毁的话,ThreadLocal始终应该占着一个内存,之前的应该会被GC回收掉吧?我觉得这和tomcat的重启还不太一样,tomcat启动要加载很多东西,可能是那些引起的问题。而ThreadLocal只是一个指针而已
2.类加载器没有研究过,本身应该没问题吧,可能有问题的应该是被加载的类是否有native之类的代码。我觉得归根到底不是java自己的代码引起的内存泄漏。
2 楼 deepinmind 2014-05-07  
风云无浪 写道
1、ThreadLocal不应该在线程结束时销毁么?当ThreadLocal被销毁的时候,那块内存不就被释放了么?在线程池里,ThreadLocal是不会被销毁的。
2、静态变量是静态内存区域,当没有引用的时候,自然会被回收。

“在Java中,还有许许多多的内存泄露的例子。”这句话说的非常不对,在JAVA里内存还是很安全的。我忘了Thinking in Java里的内存泄漏的例子了,好像Native的code要多注意。


你说的没错。不过安全是相对的,相对来说在Java里内存的确还是很安全的。

1的话,原文中的场景应该是指这个线程在一直运行的,还没有销毁,就比如Tomcat没重启,容器的线程还没有销毁。2. 如果类加载器发生了泄露,它加载的类也会随之泄露的,包括它的静态变量。在Java EE的许多容器里,都很容易出现类加载器泄露的情况。
1 楼 风云无浪 2014-05-06  
1、ThreadLocal不应该在线程结束时销毁么?当ThreadLocal被销毁的时候,那块内存不就被释放了么?在线程池里,ThreadLocal是不会被销毁的。
2、静态变量是静态内存区域,当没有引用的时候,自然会被回收。

“在Java中,还有许许多多的内存泄露的例子。”这句话说的非常不对,在JAVA里内存还是很安全的。我忘了Thinking in Java里的内存泄漏的例子了,好像Native的code要多注意。

相关推荐

    如何解决Java内存泄漏

    ### 如何解决Java内存泄漏 #### 1. 背景 Java凭借其垃圾回收机制大大简化了内存管理,使得开发者无需手动管理内存的释放,从而提升了开发效率。然而,这种自动化管理也可能成为一把双刃剑,特别是当开发人员忽视...

    java内存泄露、溢出检查方法和工具

    本文将深入探讨如何检测和分析Java内存泄露与溢出,并介绍一种常用的工具——Memory Analyzer(MAT)。 首先,理解内存泄露的概念至关重要。在Java中,内存泄露通常发生在对象不再被程序使用但仍然保持在内存中,...

    java内存泄漏分析工具

    Java内存泄漏是一个严重的问题,它会导致程序性能下降,甚至可能导致应用程序崩溃。为了有效地诊断和解决这类问题,开发者需要借助特定的分析工具。本篇将详细探讨Java内存泄漏及其相关的分析工具。 内存泄漏是指...

    JAVA内存泄漏分析工具

    Java内存泄漏分析是一个关键的系统优化任务,尤其是在大型企业级应用中,长期运行的系统可能会因为内存泄漏导致性能下降甚至服务中断。"JAVA内存泄漏分析工具"正是一款用于解决此类问题的专业工具,它能帮助开发者...

    Java内存泄露_JVM监控工具介绍

    "Java内存泄露_JVM监控工具介绍" Java内存泄露是Java开发中常见的一种问题,发生内存泄露可能会导致Java应用程序崩溃或性能下降。在Java中,内存泄露的原因非常多样,例如,静态变量、循环引用、数据库连接池、...

    java内存泄漏问题追踪

    4. "java内存泄露专题研究和应用_石麟.docx"可能提供了更深入的研究和实际案例,包括如何识别特定类型的内存泄漏,以及针对不同场景下的解决方案。而"ha450.jar"可能是一个示例应用或者工具,用于演示内存泄漏问题...

    Java内存泄露及内存无法回收解决方案

    本文将深入探讨Java内存泄露的原理,分析内存无法回收的原因,并提供相应的解决方案。 首先,我们要了解Java内存模型。Java虚拟机(JVM)中有三个主要的内存区域:堆内存(Heap)、栈内存(Stack)和方法区(Method...

    java内存泄露分析工具 eclipse3.5插件

    Java内存泄露是一个严重的问题,它可能导致程序性能下降,甚至导致应用程序崩溃。Eclipse Memory Analyzer(MAT)是一款强大的Java内存分析工具,特别适用于检测和解决内存泄露。标题提到的"java内存泄露分析工具 ...

    java 内存泄漏

    ### Java内存泄漏详解 #### JVM内存管理概览 在探讨Java内存泄漏之前,我们先简要回顾一下JVM(Java虚拟机)的基本架构及其内存管理机制。这有助于更好地理解内存泄漏的发生原因及其解决方法。 ##### 类装载子...

    java内存泄漏解决

    ### Java内存泄漏解决方案详解 #### 一、Java内存泄漏概述 在Java开发过程中,经常会遇到内存泄漏的问题,尤其是在长时间运行的应用程序中更为常见。本文将详细介绍如何解决Java内存泄漏问题,帮助开发者更好地...

    Java内存泄露检测

    Java内存泄露检测是Java开发中一个关键的议题,因为它直接影响到程序的稳定性和资源效率。内存泄露是指程序中已分配的内存无法被正确地释放,从而导致系统资源的浪费和可能导致程序性能下降甚至崩溃。 首先,理解...

    java内存泄露.pdf

    Java内存泄露是一个重要的技术主题,尤其对于开发大型、长期运行的应用程序来说,它可能导致系统性能下降,甚至引发系统崩溃。本文将深入探讨内存泄露的概念、分类、后果以及检测方法。 内存泄露简介: 内存泄露指...

    关于java内存泄漏

    ### 关于Java内存泄漏 #### 一、Java内存管理机制 Java的一大特色在于其自动化的内存管理机制,这主要依赖于垃圾收集器(Garbage Collection, GC)来自动完成对象的内存分配与回收。尽管这一特性极大地减轻了...

    关于Java内存泄漏的讨论

    什么是内存泄漏?造成内存泄漏的原因?如何解决内存泄漏?以及如何避免内存泄漏等。。。

    详解Java内存泄露的示例代码

    Java内存泄露的示例代码的知识点总结 Java内存泄露是指Java应用程序中由于某些原因导致的内存不能被正确释放,导致JVM OutOfMemory的错误。本文通过一个Demo来介绍ThreadLocal和ClassLoader导致内存泄露最终OutOf...

    java内存泄露定位与分析.pdf

    java内存泄露定位与分析

    java 内存泄露分析流程

    Java内存泄露是一个严重的问题,它可能导致系统性能下降,甚至服务崩溃。分析和解决这些问题需要深入理解Java内存模型、垃圾收集机制以及JVM优化策略。以下是对这个主题的详细阐述: 1. **Java内存模型** Java内存...

    关于JAVA内存泄漏问题注意事项

    Java内存泄漏问题是一个重要的主题,尤其对于大型的J2EE应用程序而言,理解并避免内存泄漏至关重要。虽然Java的垃圾收集机制能自动管理内存,但并不意味着程序员可以完全忽视内存管理。以下是一些关于Java内存泄漏的...

Global site tag (gtag.js) - Google Analytics