Java线上问题定位解决

本文资料网上搜集整理。

Java 应用系统常见的线上问题分为如下几种:

  1. 进程退出
  2. OutOfMemory问题
  3. CPU高
  4. 没有响应
  5. Timeout超时
  6. 环境变量问题

1进程退出

Java进程莫名奇妙的退出可能是由于下面几种情况引起的

1.1 OOM killer

为了处理内存不足时的问题,Linux内核发明了一种机制,叫OOM(Out Of Memory) killer,通过配置它可以控制内存不足时内核的行为。
当物理内存和交换空间都被用完时,如果还有进程来申请内存,内核将触发OOM killer,其行为如下:

1.检查文件/proc/sys/vm/panic_on_oom,如果里面的值为2,那么系统一定会触发panic
2.如果/proc/sys/vm/panic_on_oom的值为1,那么系统有可能触发panic(见后面的介绍)
3.如果/proc/sys/vm/panic_on_oom的值为0,或者上一步没有触发panic,那么内核继续检查文件/proc/sys/vm/oom_kill_allocating_task
3.如果/proc/sys/vm/oom_kill_allocating_task为1,那么内核将kill掉当前申请内存的进程
4.如果/proc/sys/vm/oom_kill_allocating_task为0,内核将检查每个进程的分数,分数最高的进程将被kill掉

解决方案

  1. 执行命令
grep Kill /var/log/messages

如果配置了syslog,日志可能在/var/log/syslog

grep oom /var/log/syslog
  1. 找到日志,查看详情,确定进程被Kill掉时的内存情况
  2. 确定需要的内存量,优化Java进程启动参数,给操作系统要留足内存量,推荐1G以上,重启应用。

1.2 被操作了Kill命令杀掉

可能是由于被人为操作了kill 命令杀掉的,或者其他进程操作了 kill 命令杀掉

解决方案

  1. 查看操作日志
history | grep kill

2 找到相应责任人

1.3 其他问题

解决方案

在进程工作目录下查找文件 hs_err_pid.log, 如果找不到说明非致命问题。
查找代码中是否有

System.exit()

如果找到该文件,查看文件内容,寻找线索:

  1. 触发致命错误的操作异常或者信号;
  2. 版本和配置信息;
  3. 触发致命异常的线程详细信息和线程栈;
  4. 当前运行的线程列表和它们的状态;
  5. 堆的总括信息;
  6. 加载的本地库;
  7. 命令行参数;
  8. 环境变量;
  9. 操作系统CPU的详细信息。

具体的解决方案可以参考如下章节。

2 OutOfMemory 问题

2.1 Java heap space/GC overhead limit exceeded

解决方案

  1. 生成 heapdump 文件

在启动参数中加上 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=文件保存路径
或者使用 jmap
jmap -dump:format=b,file=文件名 [pid]

  1. 使用MAT工具分析heapdump文件
  2. 占用内存多的代码进行优化
  3. 如果内存占用不多
  • 可能是创建了一个大对象导致,根据日志分析何时会创建大对象。
  • 死循环,使用jstack进行分析
  1. 调大参数 -Xmx, 例如: -Xmx2.4G

2.2 PermGen space

解决方案

  1. 调大PermSize, 例如: -XX:MaxPermSize=512MB
  2. 是否有动态加载Groovy脚本
  3. 是否有动态生成类逻辑,比如使用cglib大量动态生成类

2.3 Direct buffer memory

解决方案

  1. 默认占用-Xmx相同内存,可以通过增加参数-XX:MaxDirectMemorySize=1G设定
  2. 使用了Netty但是未限流
  3. 代码中是否使用DirectBuffer,并没有进行合理控制

2.4 StackOverflowError

解决方案

  1. 调小-Xss,使每个线程栈的内存占用减小
  2. 调小-Xmx 参数,给栈留更多内存空间
  3. 代码中是否有不合理的递归

2.5 StackOverflowError

解决方案

  1. 调小-Xss,使每个线程栈的内存占用减小
  2. 调小-Xmx 参数,给栈留更多内存空间
  3. 代码中是否有不合理的递归

2.5 Out of swap space

解决方案

  1. 地址空间不够,调整系统为64位
  2. 物理内存不足
  • jmap -histo:live pid,如果内存明显减少说明是DirectBuffer问题,通过设置-XX:MaxDirectMemorySize=1G设定
  • btrace Inflater/Deflater

BTrace是一个非常不错的java诊断工具。
BTrace 中的B表示bytecode,它是在字节码层面上对代码进行trace ,通过在运行中的java类中注入trace代码, 并对运行中的目标程序进行热交换(hotswap)来达到对代码的跟踪 。

Inflater/Deflater 适用于压缩/解压缩 数据包的API,使用不当会造成Native Memory Leak

2.6 unable to create new native thread

解决方案

  1. 线程数超过 ulimit限制
  • ulimit -a 查看max user process
  • 临时调大ulimit -u unlimited
  • 永久增加

vi /etc/security/limits.conf # 添加如下的行
* soft noproc 11000
* hard noproc 11000
* soft nofile 4100
* hard nofile 4100

  1. 线程数超过kernel.pid_max
  • /proc/sys/kernel/pid_max #操作系统线程数限制
  • /proc/sys/vm/max_map_count #单进程mmap的限制会影响当个进程可创建的线程数

2.7 Map failed

解决方案

cat /proc/sys/vm/max_map_count
max_map_count这个参数是允许一个进程在VMAs(虚拟内存区域)拥有最大数量,VMA是一个连续的虚拟地址空间,当进程创建一个内存映像文件时VMA的地址空间就会增加,当达到max_map_count了就是返回out of memory errors。

数据量增长,导致map file个数增长,应用启动参数上有-XX:+DisableExplicitGC,导致了在map file个数到达了max_map_count后直接OOM了(这也是因为heap比较大,所以full gc触发的频率低,这个问题就特别容易暴露)。

启动参数上加-XX:+DisableExplicitGC确实还是要小心,不仅map file这里是在OOM后靠显式的去执行System.gc来回收,Direct ByteBuffer其实也是这样,而这两个场景都有可能因为java本身full gc执行不频繁,导致达到了限制条件(例如map file个数达到max_map_count,而Direct ByteBuffer达到MaxDirectMemorySize),所以在CMS GC的场景下,看来还是去掉这个参数,改为加上-XX:+ExplicitGCInvokesConcurrent这个参数更稳妥一点。

3 CPU高

3.1 使用的命令

  • top top命令可以实时动态地查看系统的整体运行情况,是一个综合了多方信息监测系统性能和运行信息的实用工具。通过top命令所提供的互动式界面,用热键可以管理。
  • vmstat vmstat命令的含义为显示虚拟内存状态(“Viryual Memor Statics”),但是它可以报告关于进程、内存、I/O等系统整体运行状态。
  • sar sar命令是Linux下系统运行状态统计工具,它将指定的操作系统状态计数器显示到标准输出设备。sar工具将对系统当前的状态进行取样,然后通过计算数据和比例来表达系统的当前运行状态。它的特点是可以连续对系统取样,获得大量的取样数据。取样数据和分析的结果都可以存入文件,使用它时消耗的系统资源很小。
  • mpstat mpstat命令主要用于多CPU环境下,它显示各个可用CPU的状态系你想。这些信息存放在/proc/[stat](http://man.linuxde.net/stat "stat命令")文件中。在多CPUs系统里,其不但能查看所有CPU的平均状况信息,而且能够查看特定CPU的信息。

us: 用户CPU时间(非内核进程占用时间)(单位为百分比)。 us的值比较高时,说明用户进程消耗的CPU时间多
sy:系统使用的CPU时间(单位为百分比)。sy的值高时,说明系统内核消耗的CPU资源多,这并不是良性表现,我们应该检查原因。
wa: 等待IO的CPU时间,wait越大则机器io性能就越差。说明IO等待比较严重,这可能由于磁盘大量作随机访问造成,也有可能磁盘出现瓶颈(块操作)。

3.2 us 高

可能的原因是

  • Full GC,CMS GC
  • 代码有非常耗CPU操作,例如死循环
  • 整体代码消耗CPU比较多

解决方案

  1. 查看gc.log或者 jstat -gcutil [pid] 查看对应的gc日志
  2. 执行https://github.com/oldratlee/useful-scripts/blob/master/show-busy-java-threads.sh 脚本定位对应代码进行优化
  3. 如果耗费CPU的逻辑总是在变,说明是总体占用较高,需要逐个点分析。

3.3 sy 高

可能的原因是

  • 锁竞争激烈
  • 线程主动切换频繁

解决方案

  1. jstack 查看一下是否有锁,或者是否线程切换频繁

定位对应锁位置,改为无锁数据结构
对于线程切换频繁改为通知机制

  1. btrace ConditionObject.awaitNanos 是否存在很小值,最好是ms级别

3.4 iowait 高

可能的原因是

  • io读写频繁

解决方案

  1. 使用 iostat iotop lsof分析存在瓶颈的代码
  • 增加缓存
  • 同步写改为异步写
  • 随机写改为顺序写

4 应用无响应

应用进程没有响应的原因一般为以下几种:

  • CPU高
  • OutOfMemory
  • 死锁
  • 线程池满

CPU高 和 OutOfMemory 参见上面的章节

4.1 死锁

解决方案

  1. jstack -l 查看对应死锁的线程,去掉代码中的死锁。

4.2 线程池满

解决方案

  1. jstack 查看是否所有线程池都使用完毕,并增大线程池,优化代码。

5 timeout

超时
调用超时的原因一般为以下几种:

  • 服务器端逻辑慢
  • 服务器端或调用端gc
  • 服务器端或调用端CPU高
  • 传输大对象导致序列化慢
  • 网络问题,抖动或丢包

5.1 服务器端逻辑慢

解决方案

  1. 优化服务端代码,优化算法, 增加异步,增加缓存,增加消息等措施

5.2 服务器端或调用端gc

解决方案

  1. 查看两端的gc 日志,调整gc策略

5.3 服务器端或调用端CPU高

解决方案

  1. 查看前面章节的解决方案

5.4 传输大对象导致序列化慢

解决方案

  1. 优化对象,分页或者缓存。

5.5 网络问题,抖动或丢包

解决方案

  1. 联系网络管理员处理

6 环境变量问题

原因:

  • 时区错误
  • 变量错误
  • 编码方式错误

解决方案

  • jinfo [pid] 查看具体的启动参数
  • 如果是时区错误,不同操作系统改动方式不同,需要修改完启动java再通过jinfo查看是否生效
  • 如果是变量错误,以jinfo看到的为准,需要排查启动过程中参数是否被修改

附件:java内存模型图例


Java线上问题定位解决_第1张图片
java 虚拟机.png

你可能感兴趣的:(Java线上问题定位解决)