常见的几种内存泄漏情况和示例

简介: 常见的几种内存泄漏情况和示例


内存泄漏的原因:

Java程序程序在申请内存后,无法释放已申请的内存空间。

内存泄漏的几种情况:

1.长生命周期的对象持有短生命周期对象的引用

这种情况一般发生在使用容器造成内存泄漏。

举个例子:

定义一个栈,可以进栈和出栈,有个成员变量数组可以保存栈内的元素。

public class Stack {
    public Object[] elements;//数组来保存
    private int size =0;
    private static final int Cap = 16;
    public Stack() {
        elements = new Object[Cap];
    }
    public void push(Object e){ //入栈
        elements[size] = e;
        size++;
    }
    public Object pop(){  //出栈
      size = size -1;
        Object o = elements[size];
//        elements[size] = null;  //让GC 回收掉
        return o;
    }
}

测试类

public class UseStack {
    public static void main(String[] args) {
        Stack stack = new Stack();  //new一个栈
        Object o = new Object(); //new一个对象
        System.out.println("o="+o);
        stack.push(o); //入栈
        Object o1 =  stack.pop(); //出栈
        //o对象没什么用
        System.out.println("o1="+o1);
        System.out.println(stack.elements[0]); //打印栈中的数据
    }
}

o对象放入栈再出栈,就没啥用了,看下栈中是否还有元素。

运行结果:

o=java.lang.Object@1b6d3586

o1=java.lang.Object@1b6d3586

java.lang.Object@1b6d3586

可以看到红色的地址,表示栈中还有元素。内存泄漏了,解决方式

elements[size] = null;  //让GC 回收掉

打开这行代码。

再看运行结果:

o=java.lang.Object@1b6d3586

o1=java.lang.Object@1b6d3586

null

栈中的元素已经释放,不会内存泄漏了。

2.连接未关闭

如数据库连接、网络连接(socket)和IO连接等,只有连接被关闭后,垃圾回收器才会回收对应的对象。

解决方式

手动关闭流:

Scanner scanner = null;
try {
    scanner = new Scanner(new File("test.txt"));
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    //这里必须手动关闭流
    if (scanner != null) {
        scanner.close();
    }
}

或者用java7出现的 try with resouce:

try (Scanner scanner = new Scanner(new File("test.txt"))) {
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException fnfe) {
    fnfe.printStackTrace();
}

将声明对象防止try的小括号中,他会自动帮我们关闭连接资源。

3.变量作用域不合理

  • 1.一个变量的定义的作用范围大于其使用范围
  • 2.如果没有及时地把对象设置为null
    举例如下:
class Test(){
    private Object o;
    public void test1(){
        o = new Object();
        //some other codes;
    }
    public void test2(){
        //some codes
    }
}

此例中,对象o的作用域仅仅限于第一个方法,另外一个方法并不会用到它。但是,第一个方法结束之后o所分配的内存并不会被释放,z只有在整个类的对象被释放时对象o才会被释放,因此造成了内存泄漏。

,可以把该对象设置为第一个方法中的局部变量。也可以在第一个方法结束时将其赋值为null,这样的话在第一个方法结束时该对象就可以被回收。

4.内部类持有外部类

Java的非静态内部类的这种创建方式,会隐式地持有外部类的引用,而且默认情况下这个引用是强引用,因此,如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏

如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏(你认为垃圾回收器会回收掉外部类的实例,但由于内部类持有外部类的引用,导致垃圾回收器不能正常工作)

解决方法1:你可以在内部类的内部显示持有一个外部类的软引用(或弱引用),并通过构造方法的方式传递进来,在内部类的使用过程中,先判断一下外部类是否被回收;

解决方法2:内部类设置成静态内部类。

举例如下:

public class NoStaticInternal {
    /**
     * @param args
     */
    public  int k=13;
    private static String string="hu";
    protected float j=1.5f;
    public static void show(){
        System.out.println("show");
    }
    private void add(){
        System.out.println("add");
    }
    public static void main(String[] args) {
        NoStaticInternal m=new NoStaticInternal();
        //非静态内部类的构造方式
        //Child c=m.new Child();
        Child c= new Child();
        c.test();
    }
    //内部类Child --静态的,防止内存泄漏
    static  class Child{
        public int i;
        public void test(){
            //System.out.println("k=:"+k);
            System.out.println("string:"+string);
            //add();
            //System.out.println("j=:"+j);
            show();
        }
    }
}

内部类一般定义类静态,这样内部类没有父类的引用,不需要对父类初始化。

5.Hash值改变

在集合中,如果修改了对象中的那些参与计算哈希值的字段,会导致无法从集合中单独删除当前对象,造成内存泄露。

例子:

public class Node {
    private int x;
    private int y;
    public Node(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }
    //重写HashCode的方法
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        result = prime * result + y;
        return result;
    }
    //改变y的值:同时改变hashcode
    public void setY(int y) {
        this.y = y;
    }
}

测试:

public static void main(String[] args) {
        HashSet<Node> hashSet = new HashSet<Node>();
        Node nod1 = new Node(1, 3);
        Node nod2 = new Node(3, 5);
        hashSet.add(nod1);
        hashSet.add(nod2);
        nod2.setY(7);//nod2的Hash值改变
        hashSet.remove(nod2);//删掉nod2节点
        System.out.println(hashSet.size());
    }

运算结果:

2

可以看出,在修改了node2的参与哈希值计算的字段之后,用remove()方法也无法移除node2。但是hashSet仍然有2个元素大小,说明hashSet仍然持有该对象的引用,因此该对象无法被回收,造成内存泄漏。

内存泄漏和内存溢出区别

相同与不同

内存溢出:实实在在的内存空间不足导致;

内存泄漏:该释放的对象没有释放,多见于自己使用容器保存元素的情况下。

如何避免

内存溢出:检查代码以及设置足够的空间

内存泄漏:一定是代码有问题

往往很多情况下,内存溢出往往是内存泄漏造成的。


大功告成!!

相关文章
|
程序员 C++
智能指针避坑指南——几种常见的错误用法
智能指针避坑指南——几种常见的错误用法
|
存储 数据可视化 数据管理
基于阿里云服务的数据平台架构实践
本文主要介绍基于阿里云大数据组件服务,对企业进行大数据平台建设的架构实践。
2038 2
|
消息中间件 中间件 Kafka
分布式事务最全详解 ,看这篇就够了!
本文详解分布式事务的一致性及实战解决方案,包括CAP理论、BASE理论及2PC、TCC、消息队列等常见方案,助你深入理解分布式系统的核心技术。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式事务最全详解 ,看这篇就够了!
|
9月前
|
人工智能 程序员 API
Motia:程序员福音!AI智能体三语言混编,零基础秒级部署
Motia 是一款专为软件工程师设计的 AI Agent 开发框架,支持多种编程语言,提供零基础设施部署、模块化设计和内置可观测性功能,帮助开发者快速构建和部署智能体。
788 15
Motia:程序员福音!AI智能体三语言混编,零基础秒级部署
uniapp项目的.gitignore
uniapp项目的.gitignore
221 0
|
机器学习/深度学习 人工智能 算法
【AI系统】模型压缩基本介绍
模型压缩旨在通过减少存储空间、降低计算量和提高计算效率,降低模型部署成本,同时保持模型性能。主要技术包括模型量化、参数剪枝、知识蒸馏和低秩分解,广泛应用于移动设备、物联网、在线服务系统、大模型及自动驾驶等领域。
676 4
【AI系统】模型压缩基本介绍
|
前端开发 JavaScript
使用 JavaScript 实现图片预览功能
使用 JavaScript 实现图片预览功能
371 0
|
11月前
|
关系型数据库 API 数据库
Python流行orm框架对比
Python中有多个流行的ORM框架,如SQLAlchemy、Django ORM、Peewee、Tortoise ORM、Pony ORM、SQLModel和GINO。每个框架各有特点,适用于不同的项目需求。SQLAlchemy功能强大且灵活,适合复杂项目;Django ORM与Django框架无缝集成,易用性强;Peewee轻量级且简单,适合小型项目;Tortoise ORM专为异步框架设计;Pony ORM查询语法直观;SQLModel结合Pydantic,适合FastAPI;GINO则适合异步环境开发。初学者推荐使用Django ORM或Peewee,因其易学易用。
1492 4
|
Java 数据库连接 Android开发
Java中的内存泄漏及其排查方法
Java中的内存泄漏及其排查方法
|
机器学习/深度学习 人工智能 自然语言处理
人工智能与模型知识库在移动医疗产品中的落地应用
在现代医疗体系中,通义千问大模型与MaxKB知识库的结合,为医生和患者提供了前所未有的支持与便利。该系统通过实时问答、临床决策辅助、个性化学习和患者教育等功能,显著提升了诊疗效率和患者满意度。实际应用如乐问医学APP展示了其强大优势,但数据隐私和安全问题仍需关注。
769 0