Java面试常见问题:JVM内存异常及内存参数设置
前文《Java面试必考问题:JVM内存区域如何划分? 》介绍了Java虚拟机的内存区域划分。内存异常问题是程序开发过程中经常遇到的问题,也是面试中常问到的,本文重点介绍一下JVM的内存异常以及相关的内存参数如何设置。
JVM内存异常
遇到的内存不足方面的异常主要是以下三种:
- java.lang.OutOfMemoryError: Java heap space,表示堆内存不足。
- java.lang.OutOfMemoryError: PermGen space,表示永久代空间不足。
- java.lang.StackOverflowError,表示栈溢出。
堆区(Heap)是Java垃圾回收的主要处理区域。如果程序创建很多类实例,而堆内存设置过小,即使有垃圾回收机制,也可能会出现堆内存不足的异常。
永久代(PermGen:Permanent Generation)是HotSpot虚拟机对Java虚拟机规范中定义的方法区的具体实现,这部分一般不会进行垃圾回收,但是在FULL GC时还是会进行回收的。
栈溢出的出现往往意味着程序逻辑存在问题,需要检查代码中是否存在无限递归,递归过多,调用过深等情况。
在Java8中,不再使用永久代, 而是引入了元空间(Metaspace)作为方法区的实现。元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
永久代空间不足的异常,也被元空间不足的异常取代:
- java.lang.OutOfMemoryError: Metaspace,表示元空间已被用满。
元空间使用量与JVM加载到内存中的 class 大小和数量有关。元空间OOM错误的主要原因就是加载到内存中的 class 数量太多或者体积太大。
堆内存的设置
遇到内存问题,最直接的办法是调整JVM的内存设置。
堆内存的主要参数包括:
- -Xms1500m:初始堆大小,设置为1.5GB。
- -Xmx2000m:最大堆大小,设置为2GB。
通常来说,堆内存设置的参数主要是这两个参数。当出现堆内存OOM错误的时候,可能是程序运行中产生了超出预期的数据量,可以通过增加最大堆大小来尝试解决。如果仍然不行,需要考虑使用多个虚拟机进行负载均衡。
另外也需关注程序是否存在内存泄露(Memory leak),如果存在,那么随着运行时间的推移, 泄漏的对象会占用越来越多的内存,直到耗光堆中的所有内存。增加再大的堆内存也无济于事。
通常堆内存只需要设置上面这两个参数就行了。除了这两个参数以外,还有其他一些堆内存相关的参数。
- -XX:NewSize=n:设置年轻代的初始大小
- -XX:MaxNewSize=n:设置年轻代的最大大小
- -Xmn: 表示固定设置年轻代的大小,也就是上面两个值变成同一个值了。
增大年轻代的大小,将会减小年老代的大小。此外还可以设置年轻代(包括Eden和两个Survivor区)与年老代的内存比例。
- -XX:NewRatio=4 表示年轻代与年老代所占比例为1:4,年轻代占整个堆内存的1/5.
其他JVM内存设置
永久代设置(Java8以前)
永久代(PermGen) 使用量和JVM加载到内存中的 class 大小及数量有关。出现OOM错误的原因是加载到内存中的 class 数量太多或体积太大。以下两个参数与永久代设置有关:
- -XX:PermSize=512m:设置永久代初始大小为512MB。
- -XX:MaxPermSize=512m:设置永久代最大大小为512MB。
元空间设置(Java8及以后)
JDK1.8以后出现了元空间,作用和永久代类似。元空间的大小用以下参数设置:
- -XX:MetaspaceSize=512m
- -XX:MaxMetaspaceSize=512m
上面的意思就是将元空间大小的初始值和最大值设置为 512MB。只要不超过,就不会出现OOM。
栈区设置
- -Xss:设置栈的大小,也就是每个线程可使用的内存大小。一般默认为512K。
栈的设置参数一般很少用到。
总结
堆内存不是设置得越大越好。堆内存太大,虽然不会出现OOM,但是可能会增加垃圾回收暂停的时间,影响程序的吞吐量和延迟。对于JVM内存设置,我们还是要结合自己应用的特点,选择合适的参数。
我会持续更新关于物联网、云原生以及数字科技方面的文章,用简单的语言描述复杂的技术,也会偶尔发表一下对IT产业的看法,欢迎大家关注、转发和评论,谢谢。