`

Java内存泄露与溢出

阅读更多

          我们利用JVM对内存进行分配和管理的的最主要目的就是为了防止内存泄漏和溢出这两大问题,这也是我们初学的时候最容易忽略的;

          概念:

           内存泄露:分配出去的内存回收不了

           内存溢出:系统的内存不够用了

一、内存泄漏

          一般来说内存泄露都有两种情况,第一种情况在C/C++的,在堆中分配的内存 ,在没有将其释放掉的时候,就将所有能访问这块内存的方式都删掉(如指针重新赋值);另一种情况则是在内存对象明明已经不需要的时候,还仍然保留着这块内存和它的访问方式(引用)。第一种情况,在Java中已经由于垃圾回收机制的引入,得到了很好的解决。所以,Java中的内存泄漏,主要指的是第二种情况。 

         在Java中内存泄露最主要的原因有三个

         ①没有消除过期的对象引用:所谓的过期引用,指永远也不会被消除的引用

  eg:一个栈开始的时候在增加,增加到一定长度后,然后再收缩,这时候,从栈中弹出的对象将不会被当做垃圾回收,即使这个栈程序不再引用这些对象,他们也不会被回收;因为栈内部维护着这些对象的过期引用,这中情况下就发生了内存泄漏;我们写程序的时候最经常忽略的也是这些细节方面;

 

eg:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收。

 

          解决方法:就是一旦对象过期,我们就清空这些引用就行了,及时的清空引用的好处有,当我们再次调用这些过期对象的时候,程序会抛出异常,而不是错误的执行下去;在声明对象引用之前,明确内存对象的有效作用域。在一个函数内有效的内存对象,应该声明为local变量,与类实例生命周期相同的要声明为实例变量……以此类推。在内存对象不再需要时,记得手动将其引用置空。

          当然我们要注意的是不用过分小心,对于每一个对象引用,当我们不需要的时候,立马清空,这样做是没有必要的。清空对象引用应该是一种特例,而不是一种规范;一般而言只要类是自己管理内存的,我们就要警惕内存泄露的问题;

         ②内存泄漏的另一个常见来源就是缓存:

eg:你把对象引用放到缓存中,然后你给忘了,使他不再有用的很长一段时间里面仍然留在缓存中;

         解决方法:一种:当在缓存之外存在对某个项的键的引用,该项就有意义, 用WeakHashMap代表缓存,当对象过期的时候会自动消除;另一种:缓存项的生命周期是否有意义不容易确定,随着时间的推移,其中的项变得越来越没有价值,在这种情况下,缓存需要时不时的清理拿些没有用的项,LinkedHashMap类利用他的removeEldestEntry方法可以很容易的实现在该缓存添加新条目的时候清理这些过期项;对于更加复杂的缓存,必须直接使用java.lang.ref

          ③内存泄漏最常见的来源是监听器和其它回调

eg:当你实现了一个API,客户端在这个API中注册回调,却没有显式的取消注册,这时候你要是没有采取一些特殊的动作,他们机会积聚;

          解决方法:确保回调立即被当做垃圾回收的最佳方法就是保存他们的弱引用,例如,将他们保存成WeakHashMap中的键;

二、内存溢出 

       我们对JVM有所了解的都知道,在Java虚拟机规范中JVM是被分为好几个分区的,首先被分为线程共享区和线程非共享区:

        线程共享区:分为堆区、方法区和运行时常量池,其中Java堆区又被分为新生代和老年代,新生代又被分为Eden空间、From Survivor空间、To Survivor空间;二我们的方法区仅仅上是逻辑上独立的,物理上还是堆区的一部分,方法区中有一块特殊的运行时内存区,很多开发人员称之为永久代;而运行时常量池又是方法区的一部分;

        线程非共享区:不允许被所有线程共享访问的,只允许被所属的独立线程进行访问的一类内存区,包括PC寄存器(PC计数器)、Java栈区(储存对象实例)、本地方法栈(用于支持本地方法)

       我们的JVM大体上是这么进行分区的。言归正传,我们的内存溢出将围绕这些分区进行展开;

①栈溢出(StackOverFlowError)

       栈溢出抛出java.lang.StackOverflowError错误,出现此种情况是因为方法运行的时候栈的深度超过了虚拟机容许的最大深度所致。

       出现这个异常一般是程序错误导致的,例如一个死递归函数就可能造成这种情况:

package jvm;

/**
 * Created by Taoyongpan on 2017/8/6.
 */
public class stack01 {
    //死递归
    public void test(){
        test();
    }

    public static void main(String[] args){
        stack01 s = new stack01();
        s.test();
    }
}
 这段程序就是调用test()方法的递归,抛出如下的异常、

 

 

Exception in thread "main" java.lang.StackOverflowError
	at jvm.stack01.test(stack01.java:9)
 ②堆溢出(OutOfMemoryError:java heap space)、

 

        堆内存溢出的时候,虚拟机会抛出java.lang.OutOfMemoryReeor:java heap space,出现这种情况的时候,我们需要根据内存溢出的时候产生的dump文件具体分析(需要增加-XX:+HeapDumpOnOutOfMemoryErrorjvm启动参数)。出现此种问题的时候有可能是内存泄露,也有可能是内存溢出了。

        如果是内存泄漏了,我们要找出泄露的对象是怎么被GCROOT引用起来,然后根据引用链来具体分析泄漏的原因。

         如果出现你内存溢出的问题,这往往是程序本身需要的内存大雨了我们虚拟机配置的内存,这种情况下,我们可以通过调大-Xmx来解决问题:

持久带溢出(OutOfMemoryError: PermGen space)

       我们知道Hotspot jvm通过持久带实现了Java虚拟机规范中的方法区,而运行时的常量池就是保存在方法区中的,因此持久带溢出有可能是运行时常量池溢出,也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。当持久带溢出的时候抛出java.lang.OutOfMemoryError: PermGen space
我在工作可能在如下几种场景下出现此问题。

  1. 使用一些应用服务器的热部署的时候,我们就会遇到热部署几次以后发现内存溢出了,这种情况就是因为每次热部署的后,原来的class没有被卸载掉。
  2. 如果应用程序本身比较大,涉及的类库比较多,但是我们分配给持久带的内存(通过-XX:PermSize和-XX:MaxPermSize来设置)比较小的时候也可能出现此种问题。
  3. 一些第三方框架,比如spring,hibernate都通过字节码生成技术(比如CGLib)来实现一些增强的功能,这种情况可能需要更大的方法区来存储动态生成的Class文件。

我们知道Java中字符串常量是放在常量池中的,String.intern()这个方法运行的时候,会检查常量池中是否存和本字符串相等的对象,如果存在直接返回对常量池中对象的引用,不存在的话,先把此字符串加入常量池,然后再返回字符串的引用。那么我们就可以通过String.intern方法来模拟一下运行时常量区的溢出.下面我们通过如下的代码来模拟此种情况:

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.*;
import java.lang.*;
public class OOMTest{
 
        public static void main(String... args){
                List<String> list = new ArrayList<String>();
                while(true){
                        list.add(UUID.randomUUID().toString().intern());
                }
        }
 
}

我们通过如下的命令运行上面代码:

java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest

运行后的输入如下图所示:

1
2
3
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
        at java.lang.String.intern(Native Method)
        at OOMTest.main(OOMTest.java:8)

通过上面的代码,我们成功模拟了运行时常量池溢出的情况,从输出中的PermGen space可以看出确实是持久带发生了溢出,这也验证了,我们前面说的Hotspot jvm通过持久带来实现方法区的说法。

④OutOfMemoryError:unable to create native thread

        最后我们在来看看java.lang.OutOfMemoryError:unable to create natvie thread这种错误。 出现这种情况的时候,一般是下面两种情况导致的:

  1. 程序创建的线程数超过了操作系统的限制。对于Linux系统,我们可以通过ulimit -u来查看此限制。
  2. 给虚拟机分配的内存过大,导致创建线程的时候需要的native内存太少。我们都知道操作系统对每个进程的内存是有限制的,我们启动Jvm,相当于启动了一个进程,假如我们一个进程占用了4G的内存,那么通过下面的公式计算出来的剩余内存就是建立线程栈的时候可以用的内存。 线程栈总可用内存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序计数器占用的内存 通过上面的公式我们可以看出,-Xmx 和 MaxPermSize的值越大,那么留给线程栈可用的空间就越小,在-Xss参数配置的栈容量不变的情况下,可以创建的线程数也就越小。因此如果是因为这种情况导致的unable to create native thread,那么要么我们增大进程所占用的总内存,或者减少-Xmx或者-Xss来达到创建更多线程的目的。

内存溢出部分出自http://www.importnew.com/14604.html。

分享到:
评论

相关推荐

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

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

    Java内存泄露与溢出的区别.doc

    总的来说,理解Java内存泄露和溢出的区别有助于开发者编写更高效、更稳定的代码。通过合理地管理对象生命周期,避免长时间持有无用对象的引用,以及适当地配置JVM的内存参数,可以有效地防止这些问题的发生,提升...

    简单了解JAVA内存泄漏和溢出区别及联系

    JAVA内存泄漏和溢出区别及联系 JAVA 内存泄漏和溢出是两个常见的问题,它们之间存在着紧密的联系,但同时也存在着明显的区别。 内存泄漏(Memory Leak)是指程序在申请内存后无法释放已申请的内存空间,导致系统...

    内存泄漏与内存溢出

    在软件开发领域,尤其是涉及到数据库应用的场景下,内存泄漏(Memory Leak)和内存溢出(Out of Memory,简称OOM)是两个常见的问题,它们直接影响到程序的性能、稳定性和可扩展性。以下是对这两个概念及其相关知识...

    JAVA内存溢出问题总结

    JAVA内存溢出问题总结 JAVA 内存溢出问题是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用的内存大于虚拟机能提供的最大内存。内存溢出问题可以从容器和程序类两个方面进行排查,容器问题...

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

    Java内存泄露、溢出检查方法和工具 Java内存泄露是指在Java应用程序中由于某些原因导致的内存不能被正常释放,导致内存不断增加,直到溢出,影响系统性能和稳定性。内存溢出是指Java虚拟机(JVM)由于占用的内存...

    JAVA内存溢出

    JAVA内存溢出 JAVA中OutOfMemoryError(内存溢出)的三种情况及解决办法 Java中的OutOfMemoryError(内存溢出)是一种常见的错误,本文将详细介绍OutOfMemoryError的三种情况及其解决方法。 首先,我们需要了解...

    java内存泄露、溢出检查方法和工具归纳.pdf

    本篇主要讨论Java内存泄露和溢出的检查方法以及常用的工具。 首先,当Java应用抛出`java.lang.OutOfMemoryError`异常,这通常意味着JVM的内存使用已超出预定限制。JVM内存分为几个区域,包括新生代(Young ...

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

    Java内存管理和内存问题,尤其是内存泄露与溢出,是Java开发者必须关注的重要主题。当Java应用程序运行时,JVM(Java虚拟机)会为不同部分分配内存,包括堆(Heap)、年轻代(Young Generation)、年老代(Tenured ...

    JAVA内存泄漏分析工具

    "JAVA内存泄漏分析工具"正是一款用于解决此类问题的专业工具,它能帮助开发者定位并修复内存相关的问题,如内存泄漏和内存溢出。 内存泄漏是程序在申请内存后,无法释放已申请的内存空间,一次小的内存泄漏可能看似...

    Java加载dll,导致Java进程内存泄露

    标题“Java加载dll,导致Java进程内存泄露”涉及到的是Java平台与本地库(DLL)交互时可能出现的问题。在Java中,通过Java Native Interface (JNI) 可以调用C/C++编写的动态链接库(DLL),实现Java代码与本地代码的...

    java内存泄漏与内存溢出关系解析

    java内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。java内存泄露的根本原因是长生命周期的对象持有短生命周期对象的引用。具体主要有...

    JAVA内存溢出详解.doc

    Java内存溢出(Out Of Memory,OOM)是Java应用程序运行时常见的问题,它通常发生在程序对内存需求超过了Java虚拟机(JVM)所能提供的可用内存时。本文将深入探讨Java内存溢出的原因、表现以及如何解决。 1. **Java...

    Java 内存溢出解决办法

    Java内存溢出问题通常指的是程序在试图分配内存时,无法找到足够的连续内存空间而抛出的异常。在Java中,内存分为几个区域:堆(Heap)、栈(Stack)、方法区(Method Area,Java 8后被元空间取代)以及程序计数器和...

    java内存泄漏解决

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

    java 内存溢出分析工具 HeapAnalyzer

    Java内存溢出(Out of Memory,OOM)是Java应用程序中常见的问题,会导致程序崩溃或性能急剧下降。HeapAnalyzer是一款强大的工具,专为分析Java应用程序的内存状况,特别是针对内存溢出问题进行诊断。本文将详细介绍...

    Java内存溢出解决办法

    Java内存溢出问题,全称为Java OutOfMemoryError,是Java开发者经常遇到的运行时异常。内存溢出通常发生在程序运行过程中,系统无法为运行的应用程序分配足够的内存资源,导致程序无法正常执行。理解并解决Java内存...

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

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

Global site tag (gtag.js) - Google Analytics