jvm的内存溢出与内存泄漏

本文详细介绍了Java中的内存溢出(包括栈溢出、堆溢出、直接内存溢出和方法区溢出)和内存泄漏的原因及示例,并通过代码解释了如何触发这些问题。此外,还探讨了内存泄漏的常见原因,如长生命周期对象持有短生命周期对象、未关闭的连接以及内部类引用问题。最后,区分了内存溢出和内存泄漏的区别,并提出了防止这两种情况的建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内存溢出

内存溢出的原因:程序在申请内存时,没有足够的内存空间

栈溢出

方法死循环递归调用(StackOverflowError)、不断建立线程,多个线程同时在跑(OutOfMemoryError)

/**
 * 1M *5000线程同时在跑 =5G 内存小于5G
 */
public class StackOverFlow {

    public void test(){
        test();//死递归
     }
    public static void main(String[] args)throws Throwable {
        StackOverFlow javaStack = new StackOverFlow();
        javaStack.test();
    }
}

堆溢出

不断创建对象,分配对象大于最大堆大小(OutOfMemoryError)

import java.util.LinkedList;
import java.util.List;


/**
 * @author yyb
 * VM Args:-Xms30m -Xmx30m -XX:+PrintGCDetails    堆的大小30M
 * 堆内存溢出
 */
public class HeapOom {
   public static void main(String[] args)
   {
       //String[] strings = new String[100000000];  //100m的数组(堆)
       List<Object> list = new LinkedList<>(); //在方法执行的过程中,它是GCRoots
       int i =0;
       while(true){
           i++;
           if(i%10000==0) System.out.println("i="+i);
           list.add(new Object());
       }
   }
}

本机内存直接溢出

JVM分配的本地直接内存大小大于JVM的限制(可以通过-XX:MaxDirectMemorySize来设置[不设置的话默认与堆内存最大值一样,也会出现OOM异常])

import java.nio.ByteBuffer;

/**
 * @author yyb
 * VM Args:-XX:MaxDirectMemorySize=100m
 * 限制最大直接内存大小100m
 * 直接内存溢出
 */
public class DirectOom {
    public static void main(String[] args) {
        //直接分配128M的直接内存(100M)
        ByteBuffer bb = ByteBuffer.allocateDirect(128*1024*1204);
    }
}

方法区溢出

在经常动态产生大量Class的应用中,CGLib字节码增强,动态语言,大量JSP(JSP第一次运行需要编译成Java类),基于OSGI的应用(同一个类,被不同的加载器加载也会设为不同的类)

内存泄漏

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

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

如将ArrayList设置为静态变量,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏

连接未关闭

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

变量作用域不合理

例如:1.一个变量定义的作用范围大于其使用范围

2.没有及时地把对象设置为null

内部类持有外部类

Java 的非静态内部类的这种创建方式,会隐式地持有外部类的引用,而且默认情况下这个引用是强引用,
因此,如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏 如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏(你认为垃圾回收器会回收掉外部类的实例,但由于内部类持有外部类的引 用,导致垃圾回收器不能正常工作)

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

Hash值改变

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

import java.util.HashSet;
import java.util.Iterator;

/**
 * @author yyb
 * 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值改变
        Iterator<Node> iterator = hashSet.iterator();
        while (iterator.hasNext()) {
            Node next = iterator.next();
            System.out.println(next.x + "==" + next.y);
        }

        boolean remove = hashSet.remove(nod2);
        System.out.println(remove);
        //删掉nod2节点
        System.out.println(hashSet.size());
    }

}

查看输出

1==3
3==7
false
2

查看结果可以发现,集合中的Node对象字段值确实被改变了,但是remove方法没有成功移除该元素

内存溢出与内存泄漏辨析

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

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

如何避免:

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值