阿里面试-Java内存溢出场景

  Java内存溢出即程序在申请内存时,没有足够的空间供其使用,出现out of memory。

       1、栈溢出(StackOverflowError)、

       2、堆溢出(OutOfMemoryError:java heap space)、

       3、永久代溢出(OutOfMemoryError: PermGen space)、

       4、OutOfMemoryError:unable to create native thread,以下一一进行总结。

 

 

 

一、栈溢出 (java.lang.StackOverflowError : Thread Stack space)

java栈空间是线程私有的,是java方法执行的内存模型。每个方法执行时都会在java栈空间产生一个栈帧,存放方法的变量表,返回值等信息,方法的执行到结束就是一个栈帧入栈到出栈的过程。

栈溢出了,JVM依然是采用栈式的虚拟机,这个和C和Pascal都是一样的。函数的调用过程都体现在堆栈和退栈上了。调用构造函数的 "层"太多了,以致于把栈区溢出了。 通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要 1K的空间(这个大约相当于在一个C函数内声明了256个int类型的变量),那么栈区也不过是需要1MB的空间。通常栈的大小是1-2MB的。通俗一点讲就是单线程的程序需要的内存太大了。 通常递归也不要递归的层次过多,很容易溢出。

        解决方法:1:修改程序。2:通过 -Xss: 来设置每个线程的Stack大小即可。

        在Java虚拟机规范中,对这个区域规定了两种异常状况:StackOverflowError和OutOfMemoryError异常。

  (1)StackOverflowError异常

       stackOverFlowError 顾名思义 就是 栈溢出

         每当java程序代码启动一个新线程时,Java虚拟机都会为它分配一个Java栈。Java栈以帧为单位保存线程的运行状态。当线程调用java方法时,虚拟机压入一个新的栈帧到该线程的java栈中。只要这个方法还没有返回,它就一直存在。如果线程的方法嵌套调用层次太多(如递归调用),随着java栈中帧的逐渐增多,最终会由于该线程java栈中所有栈帧大小总和大于-Xss设置的值,而产生StackOverflowError内存溢出异常。例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * VM Args: -Xss128k
 */
public class JavaVMStackSOF {
     
    private int count = 0;
 
    public static void main(String[] args) {
        new JavaVMStackSOF().method();
    }
     
    public void method() {
        System.out.println(++count);
        method();
    }
 
 
}

-Xss为128k。其中的一次测试结果为,当count的值累加到2230时,发生如下异常:

复制代码
Exception in thread "main" java.lang.StackOverflowError
    at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
    at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:564)
    at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:619)
    at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:561)
    at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)
    at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
    at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
    at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
    at java.io.PrintStream.write(PrintStream.java:526)
    at java.io.PrintStream.print(PrintStream.java:597)
    at java.io.PrintStream.println(PrintStream.java:736)
    at com.demo.test.JavaVMStackSOF.method(JavaVMStackSOF.java:15)
复制代码

    (2)OutOfMemoryError异常

java程序代码启动一个新线程时,没有足够的内存空间为该线程分配java栈(一个线程java栈的大小由-Xss参数确定),jvm则抛出OutOfMemoryError异常。例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class JavaVMStackOOM {
 
    public static void main(String[] args) {
        int count = 0;
        while (true) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    while (true) {
                        try {
                            Thread.sleep(5000);
                        } catch (Exception e) {
                        }
                    }
                }
            });
            thread.start();
            System.out.println(++count);
        }
    }
 
}

-Xss为128k。其中的一次测试结果为,当count的值累加到11958时,发生如下异常:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:693)
    at com.demo.test.JavaVMStackOOM.main(JavaVMStackOOM.java:21)

 

二、JVM Heap(堆)溢出:java.lang.OutOfMemoryError: Java heap space

       Heap的大小是 新生代(Young Generation) 和 老年代(Tenured Generaion) 之和。在JVM中如果98%的时间是用于GC,且可用的Heap size 不足2%的时候将抛出此异常信息。

   JVM在启动的时候会自动设置JVM Heap的值, 可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。

       解决方法:手动设置JVM Heap(堆)的大小。

       Java堆用于储存对象实例。当需要为对象实例分配内存,而堆的内存占用又已经达到-Xmx设置的最大值。将会抛出OutOfMemoryError异常。例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * VM Args: -Xms5m -Xmx5m
 */
public class HeapOOM {
     
    public static void main(String[] args) {
        int count = 0;
        List<Object> list = new ArrayList<Object>();
        while(true){
            list.add(new Object());
            System.out.println(++count);
        }
    }
 
}

然后在运行时设置jvm参数,如下:

      

 

 

 -Xmx为5m。其中的一次测试结果为,当count的值累加到360145时,发生如下异常:

复制代码
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2245)
    at java.util.Arrays.copyOf(Arrays.java:2219)
    at java.util.ArrayList.grow(ArrayList.java:213)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:187)
    at java.util.ArrayList.add(ArrayList.java:411)
    at com.demo.test.HeapOOM.main(HeapOOM.java:12)
复制代码

三、永久代溢出 ( PermGen space溢出: java.lang.OutOfMemoryError: PermGen space )

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。为什么会内存溢出,这是由于这块内存主要是被JVM存放Class和Meta信息的,Class在被Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同,sun的 GC不会在主程序运行期对PermGen space进行清理,所以如果你的APP会载入很多CLASS的话,就很可能出现PermGen space溢出。一般发生在程序的启动阶段。
解决方法: 通过-XX:PermSize和-XX:MaxPermSize设置永久代大小即可。

      方法区用于存放java类型的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。在类装载器加载class文件到内存的过程中,虚拟机会提取其中的类型信息,并将这些信息存储到方法区。当需要存储类信息而方法区的内存占用又已经达到-XX:MaxPermSize设置的最大值,将会抛出OutOfMemoryError异常。对于这种情况的测试,基本的思路是运行时产生大量的类去填满方法区,直到溢出。这里需要借助CGLib直接操作字节码运行时,生成了大量的动态类。例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class MethodAreaOOM {
 
    public static void main(String[] args) {
        int count = 0;
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(MethodAreaOOM.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    return proxy.invoke(obj, args);
                }
            });
            enhancer.create();
            System.out.println(++count);
        }
    }
 
}

-XX:MaxPermSize为10m。其中的一次测试结果为,当count的值累加到800时,发生如下异常: 

Caused by: java.lang.OutOfMemoryError: PermGen space
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:792)
    ... 8 more

随着-XX:MaxPermSize参数值的增大,java方法区中可以存储的类型数据也越多。

 

四、OutOfMemoryError 

       操作系统对每个进程的内存都是有一定限制的,当堆内存和非堆内存分配过大时,剩余的内存不足以创建足够的线程栈,就会产生OutOfMemoryError。因此我们可以增大进程占用的总内存或减小堆内存等来解决问题。

     1、PermGen space

发生这种问题的原意是程序中使用了大量的jar或class,使java虚拟机装载类的空间不够,与Permanent Generation space有关。解决这类问题有以下两种办法:

1.1、增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大 小,XX:MaxPermSize是最大永久保存区域大小。如针对tomcat6.0,在catalina.sh 或catalina.bat文件中一系列环境变量名说明结束处(大约在70行左右) 增加一行: JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m" 如果是windows服务器还可以在系统环境变量中设置。感觉用tomcat发布sprint+struts+hibernate架构的程序时很容易发生这种内存溢出错误。使用上述方法,我成功解决了部署ssh项目的tomcat服务器经常宕机的问题。

1.2、清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到 tomcat共同的lib下,减少类的重复加载。这种方法是网上部分人推荐的,我没试过,但感觉减少不了太大的空间,最靠谱的还是第一种方法。

 2OutOfMemoryError: Java heap space

发生这种问题的原因是java虚拟机创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已经用满了,与Heap space有关。解决这类问题有两种思路:

2.1、检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。 我以前写一个使用K-Means文本聚类算法对几万条文本记录(每条记录的特征向量大约10来个)进行文本聚类时,由于程序细节上有问题,就导致了 Java heap space的内存溢出问题,后来通过修改程序得到了解决。

2.2、增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m

 

#面试复盘##春招##笔试题目##面经##Java##校招##职场##技术栈#
小码哥高频面经及八股文 文章被收录于专栏

宝剑锋从磨砺出,梅花香自苦寒来,我是小码哥为你圆梦大厂少走弯路,值得关注。

全部评论
内存溢出是最讨厌的事了
点赞 回复 分享
发布于 2022-05-14 22:56

相关推荐

2024-12-31 11:37
已编辑
腾讯_前端开发
秋招结束,做了一个与牛客主流想法完全相反的决定。bg写在开头:本人是电气专业的211本硕,研0开始零基础转前端。历时一年半,刷了三段实习,暑期在🐧厂实习,最后顺利转正了,开的价位也挺满意的,不是白菜。其实家里面非常非常希望我去电网,几乎是从高中开始就帮我选好了这条路。所以家里的意见也一直是我转码路上相当大的阻力,尤其是今年大大小小的吵了很多架。最终也是拗不过我自己的想法,爸妈看到我拿到很好的offer以后,也终于尊重了我的选择。简单说说心理历程:大学期间没有想太多,一直在折腾七七八八的副业。作为期末冲刺型选手,保研了本校本专业。大四毕业做了算法相关的毕设,才发现编程没有想象中的难。于是研0开始考虑别的职业选择。转码的过程不展开多说,也是扎扎实实学了很久。如果也有非科班零基础自学的朋友想看经验分享我也可以后续展开写写。没选电网会不会后悔?我个人觉得,不会。做出这个选择我几乎没有一丝犹豫。1.&nbsp;电网最大的优势就是稳定性,而这恰巧是我最不看重的点。现在不会失业不代表十年后二十年后不会失业;&nbsp;世界局势随时可能发生变化,进电网也并非一劳永逸。个人认为这个世界上没有永远稳定的工作,只有稳定的个人能力。2.&nbsp;本人在大学期间就已经尝试过很多种乱七八糟的副业。也得出了一个结论:当今这个社会想饿死自己是一件很难的事情,在大城市靠一些信息差很容易就能赚到钱。所以也没那么怕失业裁员。3.&nbsp;本人是金牛座很爱钱,进互联网可以让我在年轻的适合就享受相对高品质的生活,以及更快的攒到第一桶金。以及,其实在东亚家庭里面,钱代表话语权。4.&nbsp;讨厌体制内的中年领导和热衷打探你私生活没有边界感的同事。当然,以上仅能代表个人的择业观。省会电网本身是个非常非常好的工作,只是不适合和我类似情况的个体。因为在牛客看到了太多劝退互联网无脑进体制内的内容,也想代表非主流的观点发发声。最后,希望所有人都能基于心底真正的想法来进行工作的选择,而非基于对未来不确定性的担忧和恐惧~还没找定工作的朋友们也不要着急,最近有很多补录机会。希望大家都能拿到自己满意的offer!希望大家的2025都能精彩充实! #我的求职思考#&nbsp;&nbsp;#秋招结束# #大厂#&nbsp;&nbsp;#电网#&nbsp;&nbsp;#2025秋招#&nbsp;&nbsp;
Java抽象带篮子:集美你的决定是正确的😍
点赞 评论 收藏
分享
评论
8
13
分享

创作者周榜

更多
牛客网
牛客企业服务