前面提到了很多 JVM 的分析工具,本节里我们会再介绍几种有用的工具,大家可以在需要的时候按需使用。
OOM Killer
在前面的章节,我们简单提及过 Linux 系统上的 OOM Killer(Out Of Memory killer,OOM 终结者)。假如物理内存不足,Linux 会找出“一头比较壮的进程”来杀掉。
OOM Killer 参数调优
Java 的堆内存溢出(OOM),是指堆内存用满了,GC 没法回收导致分配不了新的对象。
而操作系统的内存溢出(OOM),则是指计算机所有的内存(物理内存 + 交换空间),都被使用满了。
这种情况下,默认配置会导致系统报警,并停止正常运行。当然,将 /proc/sys/vm/panic_on_oom 参数设置为 0 之后,则系统内核会在发生内存溢出时,自动调用 OOM Killer 功能,来杀掉最壮实的那头进程(Rogue Process,流氓进程),这样系统也许就可以继续运行了。
以下参数可以基于单个进程进行设置,以手工控制哪些进程可以被 OOM Killer 终结。这些参数位于 proc 文件系统中的 /proc/pid/ 目录下,其中 pid 是指进程的 ID。
- oomadj:正常范围是 -16 到 15,用于计算一个进程的 OOM 评分(oomscore)。这个分值越高,该进程越有可能被 OOM Killer 给干掉。如果设置为 -17,则禁止 OOM Killer 杀死该进程。
- proc 文件系统是虚拟文件系统,某个进程被杀掉,则 /proc/pid/ 目录也就被销毁了。
OOM Killer 参数调整示例
例如进程的 pid=12884
,root 用户执行:
$ cat /proc/12884/oom_adj
0
# 查看最终得分
$ cat /proc/12884/oom_score
161
$ cat /proc/12884/oom_score_adj
0
# 修改分值 ...
$ echo -17 > /proc/12884/oom_adj
$ cat /proc/12884/oom_adj
-17
$ cat /proc/12884/oom_score
0
# 查看分值修正值
$ cat /proc/12884/oom_score_adj
-1000
# 修改分值
$ echo 15 > /proc/12884/oom_adj
$ cat /proc/12884/oom_adj
15
$ cat /proc/12884/oom_score
1160
$ cat /proc/12884/oom_score_adj
1000
这样配置之后,就允许某个占用了最多资源的进程,在操作系统内存不足时,也不会杀掉他,而是先去杀别的进程。
案例
我们通过以下这个案例来展示 OOM Killer。
1. 问题描述
某个 Java 应用经常挂掉,原因疑似 Java 进程被杀死。
2. 配置信息
配置如下:
- 服务器:阿里云 ECS
- IP 地址:192.168.1.52
- CPU:4 核,虚拟 CPU Intel Xeon E5-2650 2.60GHz
- 物理内存:8GB
3. 可用内存
内存不足:4 个 Java 进程,2.1+1.7+1.7+1.3=6.8G,已占用绝大部分内存。
4. 查看日志
Linux 系统的 OOM Killer 日志:
sudo cat /var/log/messages | grep killer -A 2 -B 2
经排查发现,具有如下日志:
$ sudo cat /var/log/messages | grep killer -A 2 -B 2
May 21 09:55:01 web1 systemd: Started Session 500687 of user root.
May 21 09:55:02 web1 systemd: Starting Session 500687 of user root.
May 21 09:55:23 web1 kernel: java invoked oom-killer: gfp_mask=0x201da,order=0,oom_score_adj=0
May 21 09:55:24 web1 kernel: java cpuset=/ mems_allowed=0
May 21 09:55:24 web1 kernel: CPU: 3 PID: 25434 Comm: java Not tainted 3.10.0-514.6.2.el7.x86_64 #1
--
May 21 12:05:01 web1 systemd: Started Session 500843 of user root.
May 21 12:05:01 web1 systemd: Starting Session 500843 of user root.
May 21 12:05:22 web1 kernel: jstatd invoked oom-killer: gfp_mask=0x201da,order=0,oom_score_adj=0
May 21 12:05:22 web1 kernel: jstatd cpuset=/ mems_allowed=0
May 21 12:05:23 web1 kernel: CPU: 2 PID: 10467 Comm: jstatd Not tainted 3.10.0-514.6.2.el7.x86_64 #1
可以确定,确实是物理内存不足引起的。
注意:所有 Java 进程的
-Xmx
加起来,如果大于系统的剩余内存,就可能发生这种情况。
查询系统所有进程的 oom_score:
ps -eo pid,comm,pmem --sort -rss | awk '{"cat /proc/"$1"/oom_score" | getline oom; print $0"\t"oom}'
重要提示:
如果调整过某个进程的 oom_adj 配置,那么由该进程创建的所有进程,都会继承 oom_score 分值。例如,假设某个 sshd 进程受 OOM Killer 的保护,则所有的 SSH 会话也将受到保护。这样的配置,如果发生 OOM,有可能会影响 OOM Killer 拯救系统的功能。
我们现在设想一个场景,假如我们想要随时调试跟踪线上运行的系统,需要用什么样的工具呢?下面就介绍 2 款这样的工具。
BTrace 诊断分析工具
BTrace 是基于 Java 语言的一款动态追踪工具,可用于辅助问题诊断和分析。
BTrace 项目地址:
在 Wiki 页面 中有一些简单的介绍:
BTrace 基于 ASM、Java Attach API、Instruments 开发,提供很多注解。通过这些注解,可以通过 Java 代码来编写 BTrace 脚本进行只读监控,而无需深入了解 ASM 对字节码的操纵。
下面我们来实际操作一下。
BTrace 下载
找到 Release 页面,找到最新的压缩包下载:
下载完成后解压即可使用:
1613271.png可以看到,bin 目录下是可执行文件,samples 目录下是脚本示例。
示例程序
我们先编写一个有入参有返回值的方法,示例如下:
package demo.jvm0209;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class RandomSample {
public static void main(String[] args) throws Exception {
//
int count = 10000;
int seed = 0;
for (int i = 0; i < count; i++) {
seed = randomHash(seed);
TimeUnit.SECONDS.sleep(2);
}
}
public static int randomHash(Integer seed) {
String uuid = UUID.randomUUID().toString();
int hashCode = uuid.hashCode();
System.out.println("prev.seed=" + seed);
return hashCode;
}
}
这个示例程序很简单,循环很多次调用某个方法,使用其他程序也是一样的。
然后运行程序,可以看到控制台每隔一段时间就有一些输出:
prev.seed=1601831031
...
BTrace 提供了命令行工具,但使用起不如在 JVisualVM 中方便,下面通过 JVisualVM 中集成 BTrace 插件进行简单的演示。
JVisualVM 环境中使用 BTrace
安装 JVisualVM 插件的操作,我们在前面的章节《JDK 内置图形界面工具》中介绍过。
细心的同学可能已经发现,在安装 JVisualVM 的插件时,有一款插件叫做“BTrace Workbench”。安装这款插件之后,在对应的 JVM 实例上点右键,就可以进入 BTrace 的操作界面。
1. BTrace 插件安装
打开 VisualVM,选择菜单“工具–插件(G)”:
82699966.png然后在插件安装界面中,找到“可用插件”:
82770532.png勾选“BTrace Workbench”之后,点击“安装(I)”按钮。
82937996.png如果插件不显示,请更新 JDK 到最新版。
按照引导和提示,继续安装即可。
82991766.png接受协议,并点击安装。
83219940.png等待安装完成:
83257210.png点击“完成”按钮即可。
BTrace 插件使用
85267702.png打开后默认的界面如下:
85419826.png可以看到这是一个 Java 文件的样子。然后我们参考官方文档,加一些脚本进去。
BTrace 脚本示例
我们下载的 BTrace 项目中,samples 目录下有一些脚本示例。 参照这些示例,编写一个简单的 BTrace 脚本:
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class TracingScript {
@OnMethod(
clazz = "/demo.jvm0209.*/",
method = "/.*/"
)
// 方法进入时
public static void simple(
@ProbeClassName String probeClass,
@ProbeMethodName String probeMethod) {
print("entered " + probeClass);
println("." + probeMethod);
}
@OnMethod(clazz = "demo.jvm0209.RandomSample",
method = "randomHash",
location = @Location(Kind.RETURN)
)
// 方法返回时
public static void onMethodReturn(
@ProbeClassName String probeClass,
@ProbeMethodName String probeMethod,
@Duration long duration,
@Return int returnValue) {
print(probeClass + "." + probeMethod);
print(Strings.strcat("(), duration=", duration+"ns;"));
println(Strings.strcat(" return: ", ""+returnValue));
}
}
执行结果
可以看到,输出了简单的执行结果:
网友评论