一、内存监控背景
在做JVM内存分析前,需要堆JVM内存及垃圾回收算法和垃圾回收器有一定了解,具体可以参考我之前的一篇文章:常见的垃圾回收器及垃圾回收算法
1.1、为什么要做内存监控
我们在做开发的时候不可避免的会遇到一些问题,诸如下面这些问题:
- 生产环境发生了内存溢出该如何处理?
- 生产环境应该给服务器分配多少内存合适?
- 如何对垃圾回收器的性能进行调优?
- 生产环境CPU负载飙高该如何处理?
- 生产环境出现死锁该如何定位?
- 生产环境应该给应用分配多少线程合适?
- 不加log,如何确定请求是否执行了某一行代码?
- 不加log,如何实时查看某个方法的入参与返回值?
- 其他......
做内存监控可以帮助我尽可能避免这些问题,做内存分析可以帮我们更快的定位问题所在,不论如何,结果都是我们要达到尽量减少问题、减少程序的相应时间、增加服务的处理速度和吞吐量,给用户带来更好的体验。
1.2、调优概述
我们一直说调优调优,调优调的是什么?既然是调优我们是从JVM层面考虑,就不需要考虑代码层面诸如死锁之类的东西,那么具体怎么调优呢?我们可以按照以下步骤来:
- 选择合适的机器。在运行项目时,首先要考虑项目的特性去选择合适的机器,这一步主要是考虑机器的类型、CPU及内存等硬件设施;
- 根据项目特点选择适合的垃圾回收器及算法。每种垃圾回收器和算法都有各自的优缺点,没有绝对完美的垃圾回收器和算法,我们要依据项目的侧重点去选择合适的垃圾回收器和算法,比如项目时侧重吞吐量还是稳定性或者处理速度;
- 根据项目的特点选择合适的GC属性。在选择完垃圾回收器之后,我们需要根据项目大小去设置合适的GC属性,比如最大堆内存、最小堆内存、年轻代大小及回收触发机制、老年代大小及回收触发机制、永久代大小等参数;
- 选择其他参数。设置完GC相关信息之后就可以考虑一些其他信息了,诸如是否打印日志、日志是否写入文件等。
我们要知道做JVM调优目的主要是防止出现和解决OOM、让JVM做出合理的GC。
1.3、调优的依据
调优的结果不一定是好的,所以我们可以根据一些信息去判断调优的结果:
- 运行时日志
- 异常堆栈
- GC日志
- 线程快照
- 堆转储快照
1.4、程序性能优化的过程
1.4.1、性能监控
- GC频繁
- cpu load过高
- OOM
- 内存泄露
- 死锁
- 程序响应时间较长
1.4.2、性能分析
- 打印GC日志,通过 GCviewer 或者 GC easy 来分析异常信息
- 灵活运用命令行工具、jps、jstack、jmap、jinfo等
- dump出堆文件,使用内存分析工具分析文件
- 使用阿里Arthas、jconsole、JVisualVM来实时查看JVM状态
1.4.3、性能调优
- 适当增加内存,根据业务背景选择垃圾回收器
- 优化代码,控制内存使用
- 增加机器,分散节点压力
- 合理设置线程池线程数量
- 使用中间件提高程序效率,比如缓存、消息队列等
- 其他……
1.5、程序性能评价
调优的结果我们可以从以下几个方法去评价感知:
- 请求时间:用户请求时消耗(等待)时间;
- 停顿时间:GC时最大STW时间;
- 并发量:同一时间对服务器有实际交互的请求数;
- 吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间);
- 内存占用:JVM占用系统内存。
提交请求和返回该请求的响应之间使用的时间,一般比较关注平均响应时间。常用操作的响应时间列表:
操作 | 响应时间 |
打开一个站点 | 几秒 |
数据库查询一条记录(有索引) | 十几毫秒 |
机械磁盘一次寻址定位 | 4毫秒 |
从机械磁盘顺序读取1M数据 | 2毫秒 |
从SSD磁盘顺序读取1M数据 | 0.3毫秒 |
从远程分布式换成Redis 读取一个数据 | 0.5毫秒 |
从内存读取 1M数据 | 十几微妙 |
Java程序本地方法调用 | 几微妙 |
网络传输2Kb数据 | 1 微妙 |
二、常用的命令
2.1、top:监控linux系统状况命令
top命令经常用来监控linux的系统状况,是常用的性能分析工具,能够实时显示系统中各个进程的资源占用情况。
top的使用方式 top [选项]
详细参数如下:
参数 | 说明 |
-b | 以批处理模式操作 |
-c | 显示完整的命令 |
-d | 屏幕刷新间隔时间,默认5秒 |
-I | 忽略失效过程 |
-s | 保密模式 |
-S | 累积模式 |
-i <时间> | 设置刷新间隔时间 |
-u <用户名> | 指定用户名 |
-p <进程号> | 指定进程 |
-n <次数> | 循环显示的次数 |
结果展示如下:
2.1.1、结果页前五行信息说明
第一行 15:19:43 up 2 days, 23:17, 0 users, load average: 0.64, 0.62, 0.70
内容 | 含义 |
15:19:43 | 表示当前时间 |
up 2 days, 23:17 | 系统已运行时间 |
0 users | 当前登录用户数 |
load average: 0.64, 0.62, 0.70 | 系统负载,即任务队列的平均长度。 三个数值分别为 1分钟、5分钟、15分钟前到现在的平均值。 |
需要注意的是: 如果load average这个数值除以逻辑CPU的数量,结果高于5的时候就表明系统在超负荷运转了。
第二行 Tasks: 34 total, 1 running, 33 sleeping, 0 stopped, 0 zombie
第三行 %Cpu(s): 0.0 us, 0.0 sy, 0.0 ni, 99.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
内容 | 含义 |
34 total | 进程总数 |
1 running | 正在运行的进程数 |
33 sleeping | 睡眠的进程数 |
0 stopped | 停止的进程数 |
0 zombie | 僵尸进程数 |
0.0 us | 用户空间占用CPU百分比 |
0.0 sy | 内核空间占用CPU百分比 |
0.0 ni | 用户进程空间内改变过优先级的进程占用CPU百分比 |
99.9 id | 空闲CPU百分比 |
0.0 wa | 等待输入输出的CPU时间百分比 |
0.0 hi | 硬中断(Hardware IRQ)占用CPU的百分比 |
0.0 si | 软中断(Software Interrupts)占用CPU的百分比 |
0.0 st | 用于有虚拟cpu的情况,用来指示被虚拟机偷掉的cpu时间。 |
第四行 KiB Mem : 4194304 total, 793660 free, 2795212 used, 605432 buff/cache
第五行 KiB Swap: 0 total, 0 free, 0 used. 793660 avail Mem
内容 | 含义 |
4194304 total | 物理内存总量 |
793660 free | 空闲内存总量 |
2795212 used | 使用的物理内存总量 |
605432 buff/cache | 用作内核缓存的内存量 |
0 total | 交换区总量 |
0 free | 空闲交换区总量 |
0 used | 使用的交换区总量 |
793660 avail Mem | 代表可用于进程下一次分配的物理内存数量 |
2.1.2、结果页进程行说明
列名 | 含义 |
PID | 进程id |
PPID | 父进程id |
RUSER | 用户名 |
UID | 进程所有者的用户id |
USER | 进程所有者的用户名 |
GROUP | 进程所有者的组名 |
TTY | 启动进程的终端名。不是从终端启动的进程则显示为 |
PR | 优先级 |
NI | nice值。负值表示高优先级,正值表示低优先级 |
P | 最后使用的CPU,仅在多CPU环境下有意义 |
%CPU | 上次更新到现在的CPU时间占用百分比 |
TIME | 进程使用的CPU时间总计,单位秒 |
TIME+ | 进程使用的CPU时间总计,单位1/100秒 |
%MEM | 进程使用的物理内存百分比 |
VIRT | 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES |
SWAP | 进程使用的虚拟内存中,被换出的大小,单位kb |
RES | 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA |
CODE | 可执行代码占用的物理内存大小,单位kb |
DATA | 可执行代码以外的部分(数据段+栈)占用的物理内存大小,单位kb |
SHR | 共享内存大小,单位kb |
nFLT | 页面错误次数 |
nDRT | 最后一次写入到现在,被修改过的页面数。 |
S | 进程状态。D=不可中断的睡眠状态 |
COMMAND | 命令名/命令行 |
WCHAN | 若该进程在睡眠,则显示睡眠中的系统函数名 |
Flags | 任务标志 |
2.1.3、结果页快捷键补充
在使用top命令查看linux的系统使用情况之后,我们可以在结果页面使用一些命令帮助我们更加方便的收集查看自己想要信息,具体命令如下:
参数 | 含义 |
数字1 | 监控每个逻辑CPU状况 |