java - 内存爆炸、CPU100% 问题分析、定位与解决


背景

  今天给java开发伙伴们分享一个如何对线上项目进行内存和CPU问题分析、定位和解决的方法。相信大多时候我们的工作流程是业务开发,开发完后自己用示例测试,测试好了和前端集成,测试版本发布后如果有测试部门进行一系列的测试,最终给用户部署版本。

  而测试在因为一些测试盲区导致某些方法未经测试进行部署,而在使用中发现有内存占用比例高,或CPU100%的问题。

  而对线上项目问题排查能力是DevOps或者中高级工程师需要掌握的一项技能。


堆内存溢出

问题思路整理

1、堆内存什么情况会溢出 ?
   当资源申请内存时候,申请内存大小超过了剩余可申请大小会抛出OutOfMeMoryError。
2、如何定位申请大量内存的函数?
   当内存溢出抛出异常,程序会终止服务,则不能使用jmap获取,需要进行配置获取内存异常保存快照设置。
3、查找申请内存函数的具体代码类和方法。
   使用工具定位问题代码位置。

  1. 堆内存申请

    配置vm 内存 -Xms256m -Xmx256m
    byte[] buff = new byte[256 * 1204 * 1024];

  2. 定位申请内存的代码位置
      在内存溢出时候,程序会因为抛出异常而终止运行,则需要在抛出异常退出前对内存进行快照保存,以完成代码问题的位置和原因

    内存快照

    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\dump2.hprof

    也可以对正在运行jvm程序进行手动快照保存

    #jmap -dump:live,format=b,file=heap.bin <pid>

    java工具 visualVM

      对于查看内存快照可以使用 java visualVM 对快照进行加载进行分析
    通过该工具可以查看到内存占用情况
    查看内存申请情况
      很多人都应该对 visualVM 是jvm提供的一个工具,可以对内存快照进行剖析,了解到该快照的内部情况,但这里对于分析内存调用方法显然不太直观。
       接下来给大家推荐MAT工具。

    MAT

    Memory Analyzer Tool

   通过加载内存快照显示对应的快照页面。
report
    点击 Leak Suspects
  在这里插入图片描述
  从上面的Problem Suspect会列出可能引发内存溢出的问题代码类的位置,有目的类就可以快速定位到对应方法。

	MAT调试工具使用可以通过独立下载,也可以作为插件使用。

堆外内存分析

问题思路整理

1、为什么要使用堆外内存 ?
   一些底层工具为了实现一些特殊方法或者为了追求效率,会使用到unsafe申请堆外内存。
2、堆外内存什么情况会溢出 ?
   同堆内内存溢出道理相同,不过抛出异常为 OutOfMeMoryError: Direct buffer memory。
3、如何定位申请大量内存的函数?
   因为是堆外内存溢出,通过配置堆内存异常快照,无法在堆外异常时候保存快照,则需要使用第三方调优工具代替jvm申请内存。
4、根据第三方调优工具提供的内存申请日志,查找有大量内存申请的方法,定位到问题代码位置

1、已知调用排查方法

一般使用堆外内存我们会使用到ButeBuffer.allocaeDirect方法。
我们使用到BTrace进行方法定位。

BTrace的使用
1)BTrace 插件

在jdk 中 visualVm工具安装BTrace插件

在这里插入图片描述
在需要排查的java进行打开BTrace
在这里插入图片描述
添加脚本
在这里插入图片描述
点击 start,如果有代码调用该方法,则会在控制台显示,进行精准定位。

2)BTrace命令

一般在服务器使用到Linux,如果没有X server,则BTrace命令方法更适合

btrace-bin-1.3.11.3.tgz

在代码中编写btrace脚本

@BTrace
public class TracingScript {
	/* put your code here */
	@OnMethod{
		clazz = "java.nio.ByteBuffer",
		method = "allocateDirect"
	}
	public static void traceExecute() {
		println("who call ByteBuffer.allocateDirect :");
		jstack();
	}
}

进入到项目目录内,执行以下命令

bin/btrace -cp build/ pid /tracePath/TracingSript

则当有调用会在命令中打印显示。
在这里插入图片描述

2、未知方法排查

通过上面已知方法可以进行快速定位,但对于未知方法则无法使用到BTrace进行定位,但jvm无法监视到堆外内存,则需要使用到gperftools工具

gperftools 是谷歌提供的一个工具,可以替代jvm进行堆内外内存申请,该工具不用做第三方引擎,而用来作为调优工具进行问题排查。

使用逻辑图

在这里插入图片描述

gperftools

在这里插入图片描述
1) 安装libunwind

tar -zxvf libunwind-1.1.tar.gz
cd libunwind-1.1
//配置
./configure --prefix=/root/tools/libunwind-1.1/ CFLAGS=-U_FORTRIFY_SOURCE
 ...
make

2) 安装 gperftools

tar -zxvf gperftools-2.7.tar.gz
cd gperftools-2.7
//配置
./configure --prefix=/root/tools/gperftools-2.7 LDFLAGS=-L/root/tools/libunwind=1.1/lib CPPFLAGS=-L/root/tools/libunwind-1.1/include
 ...
make
make install

3) 存放日志配置

mkdir -p /root/tools/gperftool-heap
//配置环境
export HEAPPROFILE=/root/tool/gperftool-heap/heap
//配置不再使用原来引擎,使用新的引擎
export LD_PRELOAD=/root/tools/gperftools-2.7/lib/libtcmalloc.so

4) 使用

#启动进程
java -classpath subject-1.jar com.study.jvm.Demo -XX:MaxDirectMemorySize=128m -XX:+HeapDumpOnOutOfMemoryError

dump快照日志
在这里插入图片描述
抛出异常

在这里插入图片描述
问题定位,查看快照日志

在这里插入图片描述
以便方便查找对日志重定向到txt

cd gperftools2.7/bin
//对日志重定向到txt
./pprof --text $JAVA_HOME/bin/java /root/tools/gperftool-heap/heap.0003.heap > result.txt
more result.txt

定位问题
在这里插入图片描述
通过以上定位找到了调用类和方法的使用。

接下来就可以使用上述 已知调用排查方法 进行代码位置定位。

CPU 100%问题排查

引发原因

1、cpu100%是如何引起的?
   cpu满负荷时就会导致cpu100%。
2、如何查找问题代码位置?
   找到问题代码进程,通过jdk命令查看下进程

1)CPU满负荷

  在一段代码中,如果没有阻塞代码并且cpu资源释放代码,则会导致cpu100%,也就是我们所说的死循环计算代码。

例如以下代码会导致cpu100%。

while(true) {
	new Random().next();
}

2)问题代码定位

通过top -p pod 命令查看java 进程cpu占用情况。
通过快捷键 Shift + H

在这里插入图片描述
则可以查看到每个线程的cpu占用情况。

通过jstack 1959 > cpu.txt,对进程 1959 转换十六进制 为 0x63C

查看结果:
在这里插入图片描述
3)cpu100%线上处理

如果生产CPU100%会出现什么问题,如何解决?
外网如果登录则无法进行访问
切断外网请求访问,让cpu慢慢冷静下来。
如果业务数据不严重,则重启下服务器吧。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐