java内存泄漏与内存溢出

20 篇文章 0 订阅

一、为什么要了解内存泄漏和内存溢出


1、内存泄露一般是代码设计存在缺陷导致的,通过了解内存泄露的场景,可以避免不必要的内存溢出和提高自己的代码编写水平。
2、通过了解内存溢出的几种常见情况,可以在出现内存溢出的时候快速的定位问题的位置,缩短解决故障的时间。


二 、基本概念


(1) 软件内存分配的时候一般会放在三种位置:静态存储区域、堆和栈


1、静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序整个运行期间都存在。它主要存放静态数据、全局static数据和常量
2、栈:就是CPU的寄存器(并不是内存),特点是容量很小但是速度最快,函数或者方法体内声明的变量或者指向对象的引用、局部变量即分配在这里,生命周期到该函数或者方法体尾部即止
3、堆:就是动态内存分配区(就是实体的内存RAM),java中的new和垃圾回收直接操作的就是这里的区域,类的成员变量分配在这里

从上面即可看出静态存储区域是编译时已经分配好的,栈是CPU自动控制的,那么我们所讨论的内存泄漏问题实际上就是分配在堆里面的内存出现了问题。


(2) 内存泄漏:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。


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


(4) 内存泄漏和内存溢出的区别:

内存泄漏是导致内存溢出的原因之一;内存泄露积累起来将导致内存溢出。内存泄漏可以通过完善代码来避免;内存溢出可以通过调整配置来减少发生频率,但无法彻底避免。


三 、内存泄漏的原因


引起内存泄漏的根本原因:长生命周期的对象持有短生命周期对象的引用,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致其不能被回收,从而引起内存泄漏。


四 、内存泄漏常见类型


(1)静态集合类引起内存泄漏

class StaticTest{   
    private static Vector v = new Vector(10);
    public void init(){
        for (int i = 1; i < 100; i++){
            Object object = new Object();
            v.add(object);
            object = null;
        }
    }
}

说明:在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的,最简单的方法就是将Vector对象设置为null。


(2)各种连接

比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。


(3)监听器

在Java中,我们经常会使用到监听器,如对某个控件添加单击监听器addOnClickListener(),但往往释放对象的时候会忘记删除监听器,这就有可能造成内存泄漏。好的方法就是,在释放对象的时候,应该记住释放所有监听器,这就能避免了因为监听器而导致的内存泄漏。

(4)内部类和外部模块的引用

内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如:
public void registerMsg(Object b);
这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。


(5)单例模式

不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏

class A{
public A(){
B.getInstance().setA(this);
}}
//B类采用单例模式
class B{
private A a;
private static B instance=new B();
public B(){}
public static B getInstance(){
return instance;
}
public void setA(A a){
this.a=a;
}}

说明:显然B采用单例模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况


五 、如何避免内存泄漏


1、尽早释放无用对象的引用

2、使用字符串处理时避免使用String,应使用StringBuffer,每一个String对象都得独立占用内存一块区域

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

4、避免在循环中创建对象


六 、内存溢出常见类型及解决方案


1、java.lang.OutOfMemoryError: PermGen space

JVM管理两种类型的内存,堆和非堆。堆是给开发人员用的,是在JVM启动时创建;非堆是留给JVM自己用的,用来存放类的信息的。非堆运行期内GC不会释放空间。如果webapp用了大量的第三方jar或者应用有太多的class文件而恰好MaxPermSize设置较小,超出了也会导致这块内存的占用过多造成溢出。

解决方案:
可在配置文件中设置MaxPermSize:

<jvm-arg>-XX:PermSize=128m</jvm-arg><!-- 非堆内存初始值-->
<jvm-arg>-XX:MaxPermSize=256m</jvm-arg><!-- 最大非堆内存的大小-->


2、java.lang.OutOfMemoryError: Java heap space

这种内存溢出是最常见的情况之一,主要体现在堆内存的溢出,产生的原因可能是:
1) 设置的内存参数过小(ms/mx, NewSize/MaxNewSize);
2) 程序问题:
Heap space其默认空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。如果内存剩余不到40%,JVM就会增大堆到Xmx设置的值,内存剩余超过70%,JVM就会减小堆到Xms设置的值。所以服务器的Xmx和Xms设置一般应该设置相同避免每次GC后都要调整虚拟机堆的大小。假设物理内存无限大,那么JVM内存的最大值跟操作系统有关,一般32位机是1.5g到3g之间,而64位的就不会有限制了。

解决方案:可在配置文件中设置堆内存相关参数:

<jvm-arg>-Xms512m</jvm-arg>
<jvm-arg>-Xmx512m</jvm-arg>
<jvm-arg>-Xmn128m</jvm-arg>
<jvm-arg>-XX:MaxNewSize=256m</jvm-arg>

3、java.lang.StackOverflowError

线程栈是线程独有的一块内存结构,所以线程栈发生问题必定是某个线程运行时产生的错误。一般线程栈溢出是由于递归太深或方法调用层级过多导致的。

public class StackOverflowTest {   
    public static void main(String[] args) {  
        int i =0;  
        digui(i);  
    }  
    private static void digui(int i){  
        System.out.println(i++);  
        String[] s = new String[50];  
        digui(i);  
    }  
  }
解决方案:代码尽量避免死循环或循环产生过多重复的对象实体







  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值