面试题答案
一键面试堆内存溢出(java.lang.OutOfMemoryError: Java heap space)
- 集合操作:
- 场景:不断向集合(如
ArrayList
、HashMap
等)中添加元素,而没有相应的移除操作,且集合的增长不受控制。例如,在一个循环中持续向ArrayList
添加对象,而堆内存大小不足以容纳不断增长的集合。 - 原因:Java堆用于存储对象实例,集合中的对象都存放在堆中。当集合持续增长,占用的堆空间超过了JVM所分配的最大堆内存(通过
-Xmx
参数设置),就会抛出此错误。
- 场景:不断向集合(如
- 对象创建:
- 场景:创建大量对象且这些对象长时间存活。比如在一个循环中创建大量自定义的大对象(例如包含大数组或大量成员变量的对象),并且这些对象一直被引用,不会被垃圾回收。
- 原因:大量对象占用堆空间,垃圾回收器无法及时回收这些对象占用的空间(因为对象一直被引用),当堆空间耗尽时就会抛出
OutOfMemoryError
。
- 内存泄漏:
- 场景:对象已经不再使用,但仍然被某些长生命周期的对象持有引用,导致垃圾回收器无法回收这些对象。例如,将对象放入静态集合中,而没有在合适的时候移除,即使这些对象的业务逻辑已经不再需要。
- 原因:由于对象一直被引用,垃圾回收器不能将其视为垃圾进行回收,随着时间推移,越来越多不再使用的对象占用堆空间,最终导致堆内存溢出。
方法区溢出(java.lang.OutOfMemoryError: PermGen space / Metaspace)
- 类加载:
- 场景:动态加载大量类,例如在某些框架中,根据不同的业务需求动态加载大量的类文件。如果类加载器没有正确管理已加载的类,导致类不断被加载但无法卸载。
- 原因:在Java 7及之前,方法区(永久代)用于存储类的元数据(如类的结构、方法信息、常量池等)。当加载的类过多,占用的方法区空间超过了其最大限制(通过
-XX:MaxPermSize
参数设置),就会抛出OutOfMemoryError: PermGen space
。在Java 8及之后,使用元空间(Metaspace)代替永久代,元空间使用本地内存,默认情况下,元空间大小仅受本地内存限制,但如果加载过多类导致本地内存耗尽,也会抛出OutOfMemoryError: Metaspace
。
- 字符串常量池:
- 场景:在Java 7及之前,字符串常量池位于永久代。如果程序中创建大量的字符串常量,例如在一个循环中使用
intern()
方法将字符串放入常量池,且字符串数量过多。 - 原因:大量字符串常量占用永久代空间,当永久代空间不足以容纳这些常量时,就会抛出
OutOfMemoryError: PermGen space
。在Java 8中,字符串常量池移到了堆中,虽然减少了永久代溢出的风险,但如果创建过多字符串常量,仍然可能导致堆内存溢出。
- 场景:在Java 7及之前,字符串常量池位于永久代。如果程序中创建大量的字符串常量,例如在一个循环中使用
栈溢出(java.lang.OutOfMemoryError: unable to create new native thread)
- 线程创建:
- 场景:创建过多线程。每个线程在创建时会分配一定大小的栈空间(通过
-Xss
参数设置,默认大小因操作系统和JVM版本而异)。例如,在一个循环中不断创建新线程,而系统资源(如内存)不足以支持更多线程的创建。 - 原因:创建线程需要占用系统内存来分配栈空间,当系统可用内存不足以分配给新线程所需的栈空间时,就会抛出此错误。同时,操作系统对进程能够创建的线程数量也有限制,超过这个限制也会导致无法创建新线程并抛出
OutOfMemoryError
。
- 场景:创建过多线程。每个线程在创建时会分配一定大小的栈空间(通过
- 递归调用:
- 场景:方法进行无限制的递归调用,且没有正确的终止条件。例如,一个递归方法中忘记添加终止条件,导致方法不断调用自身。
- 原因:每次递归调用都会在栈中创建一个新的栈帧,用于存储方法的局部变量、参数等信息。随着递归深度不断增加,栈空间会被不断消耗,当栈空间耗尽时,就会抛出
java.lang.StackOverflowError
,这也是OutOfMemoryError
的一种具体表现形式。