浅析JVM堆溢出和栈溢出以及其产生可能原因的排查要点

一、JVM 堆溢出

  在 jvm 运行 java 程序时,如果程序运行所需要的内存大于系统的堆最大内存(-Xmx),就会出现堆溢出问题。创建对象时如果没有可以分配的堆内存,JVM就会抛出OutOfMemoryError:java heap space异常。

复制代码
// 执行该段代码需要大于10m内存空间
public class HeadOverflow {
    public static void main(String[] args) {
        List<Object> listObj = new ArrayList<Object>();
        for(int i=0; i<10; i++){
            Byte[] bytes = new Byte[1*1024*1024];
            listObj.add(bytes);
        }
        System.out.println("添加success");
    }
}
 
// 设置该程序的jvm参数信息
-Xms1m -Xmx10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
初始堆内存和最大可以堆内存 Gc详细日志信息
复制代码
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid2464.hprof ...
Heap dump file created [16991068 bytes in 0.047 secs]

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.ghs.test.OOMTest.main(OOMTest.java:16)

  在正式项目部署环境程序默认读取的是系统的内存,一般设置程序的堆初始内存(-Xms) == 堆最大可用内存(-Xmx)。

二、JVM 栈溢出

1、栈溢出介绍:栈空间不足时,需要分下面两种情况处理:

(1)线程请求的栈深度大于虚拟机允许的最大深度   -   StackOverflowError

(2)虚拟机在扩展栈深度时,无法申请到足够的内存空间  -   OutOfMemoryError

  理解:每次方法调用都会有一个栈帧压入虚拟机栈,操作系统给JVM分配的内存是有限的,JVM分配给“虚拟机栈”的内存是有限的。如果方法调用过多,导致虚拟机栈满了就会溢出。这里栈深度就是指栈帧的数量。

2、案例

复制代码
// 循环递归调用,一直达到jvm的最大深度
public class StackOverflow {
     private static int count;
     public static void count(){
        try {
             count++;
             count(); 
        } catch (Throwable e) {
            System.out.println("最大深度:"+count);
            e.printStackTrace();
        }
     }
     public static void main(String[] args) {
         count();
    }
}
复制代码

3、调整 jvm 栈大小:设置-Xss5m设置最大调用深度后调用

  每个计算机都会有一个极限最大调用深度,避免递归在代码中无限循环。局部变量表内容越多,栈帧越大,栈深度越小。

  这里有一篇文章讲的是各自溢出的问题,可以看看:《写代码实现堆溢出、栈溢出、永久代溢出、直接内存溢出 - https://blog.csdn.net/u011983531/article/details/63250882》

  (1)栈内存溢出:程序所要求的栈深度过大。

  (2)堆内存溢出: 分清内存泄露还是 内存容量不足。泄露则看对象如何被 GC Root 引用,不足则通过调大-Xms,-Xmx参数。

  (3)永久代溢出:Class对象未被释放,Class对象占用信息过多,有过多的Class对象。

  (4)直接内存溢出:系统哪些地方会使用直接内存。

三、内存溢出的原因是什么

1、内存溢出与内存泄漏:

  内存溢出:申请内存空间,超出最大堆内存空间。

  内存泄露:其实包含了内存溢出,堆内存空间被无用对象占用没有及时释放,导致占用内存,最终导致内存泄露。

2、内存溢出的可能原因排查

  内存溢出是由于没被引用的对象(垃圾)过多造成JVM没有及时回收,造成的内存溢出。如果出现这种现象可进行代码排查:

(1)是否应用中的类中和引用变量过多使用了Static修饰,如 public staitc Students;

  在类中的属性中使用 static 修饰的最好只用基本类型或字符串。如:public static int i = 0;  public static String str;

(2)是否 应用 中使用了大量的递归或无限递归(递归中用到了大量的建新的对象

(3)是否App中使用了大量循环或死循环(循环中用到了大量的新建的对象)

(4)检查 应用 中是否使用了向数据库查询所有记录的方法。即一次性全部查询的方法,如果数据量超过10万多条了,就可能会造成内存溢出。所以在查询时应采用“分页查询”。

(5)检查是否有数组,List,Map中存放的是对象的引用而不是对象,因为这些引用会让对应的对象不能被释放,会大量存储在内存中。

(6)检查是否使用了“非字面量字符串进行+”的操作

  因为String类的内容是不可变的,每次运行"+"就会产生新的对象,如果过多会造成新String对象过多,从而导致JVM没有及时回收而出现内存溢出

复制代码
//
String s1 = "My name";
String s2 = "is";
String s3 = "xuwei";
String str = s1 + s2 + s3 +.........;  // 这是会容易造成内存溢出的

// 但是String str =  "My name" + " is " + " xuwei" + " nice " + " to " + " meet you"; 
// 这种就不会造成内存溢出。因为这是”字面量字符串“,在运行"+"时就会在编译期间运行好。不会按照JVM来执行的。
复制代码

  在使用 String、StringBuffer、StringBuilder 时,如果是字面量字符串进行"+"时,应选用String性能更好;如果是String类进行"+"时,在不考虑线程安全时,应选用 StringBuilder 性能更好。

  再比如下面这些错误的示例:

复制代码
    public class Test {  
        public void testHeap(){  
            for(;;){  //死循环一直创建对象,堆溢出
                  ArrayList list = new ArrayList (2000);  
              }  
        }  
        int num=1;  
        public void testStack(){  //无出口的递归调用,栈溢出
            num++;  
            this.testStack();  
         }  
        public static void main(String[] args){  
            Test  t  = new Test ();  
            t.testHeap();  
            t.testStack();     
        }  
    } 
复制代码

3、栈溢出的原因

(1)是否有递归调用

(2)是否有大量循环或死循环

(3)全局变量是否过多

(4)数组、List、map数据是否过大

(5)使用DDMS工具进行查找大概出现栈溢出的位置

posted @   古兰精  阅读(3432)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2020-09-14 mybatis使用记录:#{}与${}区别、缓存机制、传递数组参数和List参数时if-test判空和判断长度的写法、Mybatis查询数据返回基本类型数组
2020-09-14 服务对外提供接口以供不同站点之间使用:Spring Cloud Feign使用记录及携带token请求
点击右上角即可分享
微信分享提示