JVM学习-调优篇(上)
前言
视频教程:https://www.bilibili.com/video/BV1Dz4y1A7FB?p=2&vd_source=85ac5ee1b07df12a44b648a8751d30f6
相关知识点
案例篇章-OOM
堆溢出
报错信息
java.lang.OutOfMemoryError: Java heap space
JVAM参数配置
-XX:+PrintGCDetails -XX:MetaspaceSize=64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdump.hprof -XX:+PrintGCDateStamps -Xms200M -Xmx200M -Xloggc:log/gc-oomHeap.log
原因及解决方案
- 原因
- 代码中可能存在大对象分配
- 可能存在内存泄漏,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对
- 解决方法
- 检查是否存在大对象的分配,最有可能的是大数组分配
- 通过jmap命令,把堆内存dump下来,使用MAT等工具分析一下,检查是否存在内存泄漏的问题
- 如果没有找到明显的内存泄漏,使用 -Xmx 加大堆内存
- 还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性
元空间溢出
报错信息
java.lang.OutOfMemoryError: Metaspace
参数配置
-XX:+PrintGCDetails -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=60m -Xss512K -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdumpMeta.hprof -XX:SurvivorRatio=8 -XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:+PrintGCDateStamps -Xms60M -Xmx60M -Xloggc:log/gc-oomMeta.log
原因及解决方案
JDK8后,元空间替换了永久代,元空间使用的是本地内存
-
原因
- 运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载
- 应用长时间运行,没有重启
- 元空间内存设置过小
-
解决方案
- 检查是否永久代空间或者元空间设置的过小
- 检查代码中是否存在大量的反射操作
- dump之后通过mat检查是否存在大量由于反射生成的代理类
- 如果加载同一个代理类即可,可以创建对象以后调用对象的
setUseCache(true)
方法,
选择为true的话,使用和更新一类具有相同属性生成的类的静态缓存,而不会在同一个类文件还继续被动态加载并视为不同的类,这个其实跟类的equals()和hashCode()有关,它们是与cglib内部的class cache的key相关的。
GC overhead limit exceeded
报错信息
GC overhead limit exceeded
原因及解决方案
- 原因
- 超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常,本质是一个预判性的异常,抛出该异常时系统没有真正的内存溢出。
- 解决方案
- 检查项目中是否有大量的死循环或有使用大内存的代码,优化代码
- 添加参数
-XX:-UseGCOverheadLimit
禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap space - dump内存,检查是否存在内存泄漏,如果没有,加大内存
线程溢出
报错信息
java.lang.OutOfMemoryError : unable to create new native Thread
原因及解决方案
-
原因
-
出现这种异常,基本上都是创建了大量的线程导致的
-
正常情况下,在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右
-
能创建的线程数的具体计算公式如下:
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threadsMaxProcessMemory 指的是进程可寻址的最大空间
JVMMemory JVM内存
ReservedOsMemory 保留的操作系统内存
ThreadStackSize 线程栈的大小 -
在Java语言里, 当你创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象同时创建一个操作系统线程,而这个系统线程的内存用的不是JVMMemory,而是系统中剩下的内存(MaxProcessMemory - JVMMemory - ReservedOsMemory)。
-
由公式得出结论:你给JVM内存越多,那么你能创建的线程越少,越容易发生java.lang.OutOfMemoryError: unable to create new native thread
-
-
解决方案
-
如果程序中有bug,导致创建大量不需要的线程或者线程没有及时回收,那么必须解决这个bug,修改参数是不能解决问题的。
-
如果程序确实需要大量的线程,现有的设置不能达到要求,那么可以通过修改MaxProcessMemory,JVMMemory,ThreadStackSize这三个因素,来增加能创建的线程数。
- MaxProcessMemory 使用64位操作系统
- JVMMemory 减少JVMMemory的分配
- ThreadStackSize 减小单个线程的栈大小
经实测,在32位windows系统下较为严格遵守;64位系统下只能保证正/负相关性,甚至说相关性也不能保证。即:
在测试的过程中,64位操作系统下调整Xss的大小并没有对产生线程的总数产生影响,程序执行到极限的时候,操作系统会死机。无法看出效果 -
线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制:
-
/proc/sys/kernel/pid_max 系统最大pid值,在大型系统里可适当调大
-
/proc/sys/kernel/threads-max 系统允许的最大线程数
-
maxuserprocess(ulimit -u) 系统限制某用户下最多可以运行多少进程或线程
-
/proc/sys/vm/max_map_count
max_map_count文件包含限制一个进程可以拥有的VMA(虚拟内存区域)的数量。虚拟内存区域是一个连续的虚拟地址空间区域。在进程的生命周期中,每当程序尝试在内存中映射文件,链接到共享内存段,或者分配堆空间的时候,这些区域将被创建。调优这个值将限制进程可拥有VMA的数量。限制一个进程拥有VMA的总数可能导致应用程序出错,因为当进程达到了VMA上线但又只能释放少量的内存给其他的内核进程使用时,操作系统会抛出内存不足的错误。如果你的操作系统在NORMAL区域仅占用少量的内存,那么调低这个值可以帮助释放内存给内核用。
-
-
面试题
说到内存泄漏,问有没有碰到,内存泄漏怎么解决?(拼多多)
内存泄漏是怎么造成的?(拼多多、字节跳动)
如何理解内存泄漏问题?有哪些情况会导致内存泄露?如何解决? (阿里)
1 |