美文网首页
JVM 技术详解:面临复杂问题时的几个高级工具

JVM 技术详解:面临复杂问题时的几个高级工具

作者: you的日常 | 来源:发表于2020-12-15 14:24 被阅读0次

    前面提到了很多 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 项目地址:

    https://github.com/btraceio/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)”按钮。

    如果插件不显示,请更新 JDK 到最新版。

    82937996.png

    按照引导和提示,继续安装即可。

    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));
     }
    }
    
    

    执行结果

    可以看到,输出了简单的执行结果:

    相关文章

      网友评论

          本文标题:JVM 技术详解:面临复杂问题时的几个高级工具

          本文链接:https://www.haomeiwen.com/subject/ltlqrktx.html