MST

星途 面试题库

面试题:Java中常见的导致OutOfMemoryError的场景有哪些

请简要阐述在Java编程中,会引发OutOfMemoryError错误的常见场景,并说明原因。例如在集合操作、对象创建等方面可能出现的情况。
12.2万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

堆内存溢出(java.lang.OutOfMemoryError: Java heap space)

  1. 集合操作
    • 场景:不断向集合(如ArrayListHashMap等)中添加元素,而没有相应的移除操作,且集合的增长不受控制。例如,在一个循环中持续向ArrayList添加对象,而堆内存大小不足以容纳不断增长的集合。
    • 原因:Java堆用于存储对象实例,集合中的对象都存放在堆中。当集合持续增长,占用的堆空间超过了JVM所分配的最大堆内存(通过-Xmx参数设置),就会抛出此错误。
  2. 对象创建
    • 场景:创建大量对象且这些对象长时间存活。比如在一个循环中创建大量自定义的大对象(例如包含大数组或大量成员变量的对象),并且这些对象一直被引用,不会被垃圾回收。
    • 原因:大量对象占用堆空间,垃圾回收器无法及时回收这些对象占用的空间(因为对象一直被引用),当堆空间耗尽时就会抛出OutOfMemoryError
  3. 内存泄漏
    • 场景:对象已经不再使用,但仍然被某些长生命周期的对象持有引用,导致垃圾回收器无法回收这些对象。例如,将对象放入静态集合中,而没有在合适的时候移除,即使这些对象的业务逻辑已经不再需要。
    • 原因:由于对象一直被引用,垃圾回收器不能将其视为垃圾进行回收,随着时间推移,越来越多不再使用的对象占用堆空间,最终导致堆内存溢出。

方法区溢出(java.lang.OutOfMemoryError: PermGen space / Metaspace)

  1. 类加载
    • 场景:动态加载大量类,例如在某些框架中,根据不同的业务需求动态加载大量的类文件。如果类加载器没有正确管理已加载的类,导致类不断被加载但无法卸载。
    • 原因:在Java 7及之前,方法区(永久代)用于存储类的元数据(如类的结构、方法信息、常量池等)。当加载的类过多,占用的方法区空间超过了其最大限制(通过-XX:MaxPermSize参数设置),就会抛出OutOfMemoryError: PermGen space。在Java 8及之后,使用元空间(Metaspace)代替永久代,元空间使用本地内存,默认情况下,元空间大小仅受本地内存限制,但如果加载过多类导致本地内存耗尽,也会抛出OutOfMemoryError: Metaspace
  2. 字符串常量池
    • 场景:在Java 7及之前,字符串常量池位于永久代。如果程序中创建大量的字符串常量,例如在一个循环中使用intern()方法将字符串放入常量池,且字符串数量过多。
    • 原因:大量字符串常量占用永久代空间,当永久代空间不足以容纳这些常量时,就会抛出OutOfMemoryError: PermGen space。在Java 8中,字符串常量池移到了堆中,虽然减少了永久代溢出的风险,但如果创建过多字符串常量,仍然可能导致堆内存溢出。

栈溢出(java.lang.OutOfMemoryError: unable to create new native thread)

  1. 线程创建
    • 场景:创建过多线程。每个线程在创建时会分配一定大小的栈空间(通过-Xss参数设置,默认大小因操作系统和JVM版本而异)。例如,在一个循环中不断创建新线程,而系统资源(如内存)不足以支持更多线程的创建。
    • 原因:创建线程需要占用系统内存来分配栈空间,当系统可用内存不足以分配给新线程所需的栈空间时,就会抛出此错误。同时,操作系统对进程能够创建的线程数量也有限制,超过这个限制也会导致无法创建新线程并抛出OutOfMemoryError
  2. 递归调用
    • 场景:方法进行无限制的递归调用,且没有正确的终止条件。例如,一个递归方法中忘记添加终止条件,导致方法不断调用自身。
    • 原因:每次递归调用都会在栈中创建一个新的栈帧,用于存储方法的局部变量、参数等信息。随着递归深度不断增加,栈空间会被不断消耗,当栈空间耗尽时,就会抛出java.lang.StackOverflowError,这也是OutOfMemoryError的一种具体表现形式。