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