1、内存溢出和内存泄漏的区别?
- 内存溢出(
OutOfMemory
): 指程序在申请内存时,没有足够的内存空间供其使用。 - 内存泄露(
Memory Leak
): 指程序在申请内存后,无法释放已申请的内存空间,内存泄漏最终将导致内存溢出。
2、堆溢出的原因?
答:堆用于存储对象实例,只要不断创建对象并保证 GC Roots
到对象有可达路径避免垃圾回收,随着对象数量的增加,总容量触及最大堆容量后就会 OOM
,例如在 while
死循环中一直 new
创建实例。
堆 OOM
是实际应用中最常见的 OOM
,处理方法是通过内存映像分析工具对 Dump
出的堆转储快照分析,确认内存中导致OOM
的对象是否必要,分清到底是内存泄漏还是内存溢出。
如果是内存泄漏,通过工具查看泄漏对象到 GC Roots
的引用链,找到泄露对象是通过怎样的引用路径、与哪些 GC Roots
关联才导致无法回收,一般可以准确定位到产生内存泄漏代码的具体位置。
如果不是内存泄漏,即内存中对象都必须存活,应当检查 JVM
堆参数,与机器内存相比是否还有向上调整的空间。再从代码检查是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运行期的内存消耗。
3、栈溢出的原因?
答: 由于 HotSpot
不区分虚拟机和本地方法栈,设置本地方法栈大小的参数没有意义,栈容量只能由-Xss
参数来设定,存在两种异常:
- StackOverflowError: 如果线程请求的栈深度大于虚拟机所允许的深度,将抛
StackOverflowError
,例如一个递归方法不断调用自己。该异常有明确错误堆栈可供分析,容易定位到问题所在。 - OutOfMemoryError: 如果 JVM 栈可以动态扩展,当扩展无法申请到足够内存时会抛出
OutOfMemoryError
。HotSpot
不支持虚拟机栈扩展,所以除非在创建线程申请内存时就因无法获得足够内存而出现OOM
,否则在线程运行时是不会因为扩展而导致溢出的。
4、运行时常量池溢出的原因?
答:String
的 intern
方法是一个本地方法,作用是如果字符串常量池中已包含一个等于此 String
对象的字符串,则返回池中这个字符串的 String
对象的引用,否则将此 String
对象包含的字符串添加到常量池并返回此 String
对象的引用。
在 JDK6
及之前常量池分配在永久代,因此可以通过 -XX:PermSize
和 -XX:MaxPermSize
限制永久代大小,间接限制常量池。在 while
死循环中调用 intern
方法导致运行时常量池溢出。在 JDK7
后不会出现该问题,因为存放在永久代的字符串常量池已经被移至堆中。
5、方法区溢出的原因?
答:方法区主要存放类型信息,如类名、访问修饰符、常量池、字段描述、方法描述等。只要不断在运行时产生大量类,方法区就会溢出。例如使用 JDK
反射或 CGLib
直接操作字节码在运行时生成大量的类。
很多框架如Spring
、Hibernate
等对类增强时都会使用 CGLib
这类字节码技术,增强的类越多就需要越大的方法区保证动态生成的新类型可以载入内存,也就更容易导致方法区溢出。
JDK8
使用元空间取代永久代,HotSpot
提供了一些参数作为元空间防御措施,例如 -XX:MetaspaceSize
指定元空间初始大小,达到该值会触发 GC
进行类型卸载,同时收集器会对该值进行调整,如果释放大量空间就适当降低该值,如果释放很少空间就适当提高。
6、内存溢出问题的定位
会出现内存溢出问题的区域:直接内存、元空间、本地方法栈、虚拟机栈、堆。
本地方法栈与虚拟机栈的OOM
咱们可以不用管,为什么呢?
因为这两个区域的OOM
你在开发阶段或在测试阶段就能发现。GET
到了吗?小伙伴们。所以这两个区域的OOM
是不会生成dump
文件的。