【虚拟机JVM】内存泄露和内存溢出。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Soldier49Zed/article/details/102755174

内存泄露

一般来讲:内存泄露主要有两种情况:一是在堆中申请的空间没有被释放;二是对象已不再被使用,但仍然还在内存中保留着。垃圾回收机制的引入可以有效的解决第一种情况;而对于第二种情况,垃圾回收机制则无法保证不再使用的对象会被释放。因此,Java语言中的内存泄露主要指的是第二种情况。

内存泄露的典型案例是一个没有重写hashCode和equals方法的key类在HashMap中保存的情况,最后会生成更多重复的对象。所有的内存泄露最后都会抛出OutOfMemoryError异常(Exception in thread "main" java.lang.OutOfMemoryError:Java heap space)。

例如:

Vector v = new Vector();
for(int i = 1;i < 10;i++) {
    Object o = new Object();
    v.add(o);
}

在上述的循环中,不断创建新的对象加到Vector对象中,当退出循环后,o的作用域将会结束,但是由于v在使用这些对象,因此垃圾回收期无法将其会输,此时就造化了内存泄漏。只有将这些对象从Vector中删除才能释放创建的这些对象。

在Java语言中,容易引起内存泄露的原因很多,主要有以下几个方面的内容:

1)静态集合类。例如HashMap和Vector。如果这些容器为静态的,由于他们的生命周期与程序一致,那么容器中的对象在程序结束之前将不能被释放,从而造成泄露。

2)各种连接,例如数据库连接、网络连接以及IO连接等。在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close()方法来释放与数据的连接。只有连接被关闭后垃圾回收期才会回收对应的对象。否则,如果在访问数据库的过程中,对Connection、Statement或Result不显示地关闭,将会造成大量的对象无法被回收,从而引起内存泄露。

3)监听器。在Java语言中,往往会使用到监听器。通常一个应用中会应用到多个监听器,但在释放对象的同时往往没有对应的删除监听器,这也可能导致内存泄露。

4)变量不合理的作用域。一般而言,如果一个变量的作用范围大于其使用范围,很有可能会造成内存泄露,另一方面如果没有及时地把对象设置为null,很有可能会导致内存泄露的发生,实例如下:

class Server{
    private String msg;
    public void receiveMsg() {
        readFromNet();
        saveDB();
    }
}

在上述的伪代码中,通过readFromNet()方法接收的消息保存在变量msg中,然后调用saveDB()方法把msg的内容保存到数据库中,此时msg已经没有用了,但是由于msg的生命周期与对象的生命周期相同,此时msg还不能被回收,一次造成了内存泄露。对于这种问题,有如下两种解决办法:第一种 方法,由于msg的作用范围只在receiveMsg()方法内,一次可以把msg定义为这个方法的局部变量,当方法结束后,msg的生命周期就会结束,此时垃圾回收器就可以回收msg的内容了,第二种方法,在使用完msg后就把msg后就会把msg设置为null,这样的垃圾回收器也会自动回收msg内容所占的内存空间。

5)单例模式可能会造成内存泄露。

class BigClass{

}

class Singleton{
    private BigClass bc;
    private static Singleton instance = new Singleton(new BigClass());
    private Singleton(BigClass bc) {
        this.bc = bc;
    }
    public Singleton getInstance() {
        return instance;
    }
}

在上述实现的单例模式中,Singleton存在一个对对象BigClass的引用,由于单例对象以静态变量的方式存储,因此它在JVM的整个生命周期中都存在,同时由于它有一个对对象BigClass的引用,这样会导致BigClass类的对象不能够被回收。

内存泄露的解决方法:

1)避免在循环中创建对象

3)尽早释放无用对象的引用

3)尽量少用静态变量,因为静态变量存在永久代(方法区),永久代基本不参与垃圾回收

4)使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域。

内存溢出

指程序运行过程中无法申请到足够的内存而导致的一种错误。

内存溢出的几种情况(OOM异常):

OutOfMemoryError异常:

除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError异常的可能。

1)虚拟机栈和本地方法栈溢出

如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。

如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

2)堆溢出

一般的异常信息:java.lang.OutOfMemoryError:Java heap spaces。

出现这种异常,一般手段是先通过内存印象分析工具(Eclipse Memory Analyzer)对dump出来的转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄露(Memory Leak)还是内存溢出(Memory Overflow)。

如果是内存泄露,可进一步通过工具查看泄露对象到GC Roots的引用链。于是就能找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。

如果不存在泄露,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。

3)方法区溢出

异常信息:java.lang.OutOfMemoryError:PermGen space

4)运行时常量池溢出

异常信息:java.lang.OutOfMemoryError:PermGen space

如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。该方法的作用是:如果池中已经包含了一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。

导致内存溢出的原因:

1)内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

2)集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;

3)代码中存在死循环或循环产生过多重复的对象实体;

4)启动参数内存值设定的过小。

内存溢出的解决方法

第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加.一般要将-Xms和-Xmx选项设置为相同,以避免在每次GC后调整堆的大小;建议堆的最大值设置为可用内存的最大值的80%)。

第二步,检查错误日志,查看OutOfMemory错误前是否有其他异常或错误。

第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

第四步,使用内存查看工具动态查看内存使用情况(Jconsole)。

猜你喜欢

转载自blog.csdn.net/Soldier49Zed/article/details/102755174