GC日志
GC日志参数
- -verbose:gc 输出gc日志信息,默认输出到标准输出
- -XX:+PrintGC 输出GC日志,类似:verbose:gc
- -XX:+PrintGCDetails 在发生垃圾回收时打印内存回收详细的日志,并在进程退出时输出当前内存各区域分配情况
- -XX:+PrintGCTimeStamps 输出GC发生时的时间戳
- -XX:+PrintGCDateStamps 输出GC发生的时间戳(以日期的形式)
- -XX:+PrintHeapAtGC 每一次GC前和GC后,都打印堆信息
- -Xloggc<file> 表示把GC日志写入到一个文件中去,而不是打印到标准输出中
GCeasy网站可以在线分析GC日志文件
JVM调优
jmap命令
dump命令
jmap -dump:live,format=b,file=heap.bin <pid>
- live 仅打印存活的对象
- all 打印所有的对象
- format=b 二进制格式
- gz=<number> 指定是否压缩以及压缩程度,堆转储将使用给定的压缩级别以gzip格式写入,1最快,9压缩最大
- file=<file> 转储到哪个文件
OOM案例
OOM问题的排除和解决没有一个放之四海而皆准的银弹,只能是具体的情况根据监控和技术手段去分析(甚至需要深入的了解业务在某些特殊情况下),不过大体的思路还是可以借鉴的
堆溢出
- 原因
- 代码中可能存在大对象分配
- 可能存在内存泄漏,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。
- 解决方法
- 检查是否存在大对象的分配,最有可能的是大数组分配
- 通过jmap命令,把堆内存dump下来,使用MAT等工具分析一下,检查是否存在内存泄漏的问题
- 如果没有找到明显的内存泄漏,使用 -Xmx 加大堆内存
- 还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性
元空间溢出
JDK8后,元空间替换了永久代,元空间使用的是本地内存
- 原因:
- 运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载
- 应用长时间运行,没有重启
- 元空间内存设置过小
- 因为该 OOM 原因比较简单,解决方法有如下几种:
- 检查是否永久代空间或者元空间设置的过小
- 检查代码中是否存在大量的反射操作
- dump之后通过mat检查是否存在大量由于反射生成的代理类
GC overhead limit exceeded
这个是JDK6新加的错误类型,一般都是堆太小导致的。Sun 官方对此的定义: 超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。本质是一个预判性的异常,抛出该异常时系统没有真正的内存溢出
解决方法:
- 检查项目中是否有大量的死循环或有使用大内存的代码,优化代码.
- 添加参数-XX:-UseGCOverheadLimit’ 禁用这个检查,其实这个参数解决不了内存题,只是把错误的信息延后,最终出现 java.lang.OutofMemoryError: Java heapspace。
- dump内存,检查是否存在内存泄漏,如果没有,加大内存。
线程溢出
解决方向1
- 通过 -Xss 设置每个线程栈大小的容量
- JDK5.以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。
- 正常情况下,在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000到5000左右。
- 能创建的线程数的具体计算公式如下:(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Numberof threads
- MaxProcessMemory 指的是进程可寻址的最大空间
- JVMMemory JVM内存
- ReservedOsMemory 保留的操作系统内存
- ThreadStackSize 线程栈的大小
在Java语言里,当你创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象,同时创建一个操作系统线程,而这个操作系统线程的内存用的不是JVMMemory,而是系统中剩下的内存,(MaxProcessMemory - JVMMemory - ReservedOsMemory).所以得出结论.你给JVM的内存越多,那么你能创建的内存越少,越容易发生这个问题
解决方向2
线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制:
- /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区域仅占用少量的内存,那么调低这个值可以帮助释放内存给内核用。
jvm自动的调优
栈上分配对象(标量替换)
参数设置
- 在JDK 6u23版本之后,HotSpot中默认就已经开启了逃逸分析。
- 如果使用的是较早的版本,开发人员则可以通过:
- 通过选项“-XX:+DoEscapeAnalysis”显式开启逃逸分析通过选项
- “-XX: +PrintEscapeAnalysis”查看逃逸分析的筛选结果。
结论:开发中能使用局部变量的,就不要使用在方法外定义。
锁消除
多个锁会被JIT并为一个,或者无用锁甚至会被直接消除掉
jvm优化
设置堆的大小
Java整个堆大小设置,Xmx和Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍。方法区 (永久代 PermSize和MaxPermSize 或 元空间 MetaspaceSize 和MaxMetaspaceSize) 设置为老年代存活对象的1.2-1.5倍。年轻代Xmn的设置为老年代存活对象的1-1.5倍老年代的内存大小设置为老年代存活对象的2-3倍。
估算方式1
JVM参数中添加GC目志,GC日志中会记录每次FulIGC之后各代的内存大小,观察老年代GC之后的空间大小。可观察一段时间内(比如2天) 的FullGC之后的内存情况,根据多次的FulIGC之后的老年代的空间大小数据来预估FullGC之后老年代的存活对象大小(可根据多次FulIGC之后的内存大小取平均值)。
估算方式2
- 会影响线上服务,慎用!
- 方式1的方式比较可行,但需要更改JVM参数,并分析日志。同时,在使用CMS回收器的时候,有可能不能触发FulIGC,所以日志中并没有记录FullGC的日志。在分析的时候就比较难处理。 所以,有时候需要强制触发一次FullGC,来观察FullGC之后的老年代存活对象大小。
- 注: 强制触发FullGC,会造成线上服务停顿(STW),要谨慎!建议的操作方式为,在强制FullGC前先把服务节点摘除,FullGC之后再将服务挂回可用节点,对外提供服务,在不同时间段触发FullGC,根据多次FullGC之后的老年代内存情况来预估FullGC之后的老年代存活对象大小
- 如何强制触发Full GC?
- jmap -dump:live,format=b,file=heap.bin <pid> 将当前的存活对象dump到文件,此时会触发FulIGC
- jmap -histo:live <pid> 打印每个class的实例数目,内存占用,类全名信息.live子参数加上后,只统计活的对象数量。此时会触发FulIGC
- 在性能测试环境,可以通过Java监控工具来触发FullGC,比如使用VisualVM和JConsole.VisualVM集成了JConsole,VisualVM或者]Console上面有一个触发GC的按钮。
估算youngGC的频率
比如从数据库获取一条数据占用128个字节,需要获取1000条数据,那么一次读取到内存的大小就是128 B/1024 Kb/1024M) * 1000 = 0.122M ,那么我们程序可能需要并发读取,比如每秒读取100次,那么内存占用就是0.122100 = 12.2M ,如果堆内存设置1个G,那么年轻代大小大约就是333M,那么333M80% /12.2M =21.84s ,也就是说我们的程序几乎每分钟进行两到三次youngGC。这样可以让我们对系统有一个大致的估算。(实际上是算不出来的,因为请求太多太复杂,还是要看实时的日志还有监控来确定)
CPU占用飙高调优
排查流程
- ps aux| grep java 查看到当前java进程使用cpu、内存、磁盘的情况获取使用量异常的进程
- top -Hp 进程pid 检查当前使用异常线程的pid
- 把线程pid变为16进制如 31695 -> 7bcf 然后得到0x7bcf
- jstack 进程的pid| grep -A20 0x7bcf 得到相关进程的代码
日均百万订单优化JVM
堆稍微分配大一些,适当的提高年轻代的比例(防止对象过多全进入到老年代),然后水平扩展更多的机器,走负载均衡.
ARTHAS
在线的Java调优分析工具,以及bug的查找,非常之好用
官方网址传送门