- CPU问题
- 运行队列
- 使用率/上下文切换
- 排查步骤
- 锁
- 对象锁和类锁
- 线程状态
- 死锁和热锁&例子
- 系统态例子
- 内存问题
- linux内存概念
- 内存不够用
- 内存溢出
- io问题
线上机器的故障和性能,从最终报警的结果来看主要从三个方面去关注:CPU问题,内存问题,IO问题。
CPU问题
要解决CPU问题,先理解下运行队列和上下文切换的概念。
运行队列
看一下vmstat 1(每秒刷新一次)命令的结果。
运行队列是等待队列和执行队列的集合。
1) 等待队列:从操作系统对线程的调度来看,当 线程在等待资源而阻塞的时候,操作系统会将之切换出来,放到等待的队列。
2) 执行队列:当线程获得资源之后,调度算法会将这个线程切换进去,放到执行队列中。
看参数r的值,如果运行队列的数超过系统能处理的上线,运行队列会很长,表明系统负载可能已经饱和。
当队列数超过了CPU个数,一般2倍左右,就会出现CPU瓶颈。
查看cpu核心数命令:cat /proc/cpuinfo|grep processor|wc -l
解决方法:
- 增加CPU
- 研究可以减少应用运行所需CPU周期的方法。如:减小垃圾回收频率,采用同样完成任务CPU指令少的算法。
- 调整大任务的开始执行时间,错开高峰。
使用率/上下文切换
使用 top(-H)或vmstat 查看。
1)CPU使用率分为用户态
(参数us)和系统态
(参数sy)CPU使用率。
系统态CPU高意味着共享资源有竞争或者I/O设备之间有大量的交互,所以提高性能,我们要降低系统态CPU使用率。
2)上下文切换看每秒上下文切换数
(参数cs)。
我们调用系统函数,线程的切换,都要进行上下文切换,cs这个值要越小越好,太大了,要考虑调低线程或者进程的数目。
解决方法:
- 调节进程数/线程数
并发测试时可以由进程或者线程的峰值一直下调,压测,直到cs到一个比较小的值,这个进程和线程数就是比较合适的值了。 - 减少系统调用数
每次调用系统函数,我们的代码就会进入内核空间,导致上下文切换,这个是很耗资源,也要尽量避免频繁调用系统函数。
排查步骤
有两种场景:一个是知道哪个PID有问题,一个是看不出来哪个PID有问题。
-
根据TOP 或者 TOP -H 发现有进程或者线程占用cpu很高。
1) 找到pid。比如线程pid:20739。
2) dump线程。jstack 20739 >~/Jstack-20739.txt。
3) 转化十六进制。20739的十六进制为5103。
4) 查看dump文件。
5)看线程状态。怎么看,下面讲
- 根据TOP 或者 TOP -H 找不到有进程或者线程异常,只能一个个dump进程,慢慢分析了,下面详细讲下怎么搞。
锁
对象锁和类锁
- 对象锁:java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁,如果已经有线程获取了这个对象的锁,那么当前线程会等待;synchronized方法正常返回或者抛异常而终止,JVM会自动释放对象锁。
//形式1
public synchronized void method1(){
。。。。
}
//形式2
public void objLockMethod2(){
synchronized (this){
。。。。。。
}
}```
* 类锁:控制静态方法之间的同步,其实是特殊的对象锁。java类可能会有很多个对象,但是只有1个Class对象,Class对象其实也仅仅是1个java对象,只不过有点特殊的对象。由于每个java对象都有1个互斥锁,而类的静态方法是需要Class对象。所以类锁,就是Class对象的锁。获取类的Class对象有好几种,最简单的就是MyClass.class的方式。
// 形式1
public static synchronized void method1()
// 形式2
public void objLockMethod2(){
synchronized (TestMethod.class){
。。。。。。
}
}
线程状态
Monitor是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor,线程需要争夺Monitor,详细过程看下图,贴一张别人的图。

① 当一个线程申请进入synchronized(obj)方法块的时候,它就进入了 “Entry Set”队列。
② 他会去争夺锁。
1)如果已经有线程占有了monitor被,他会继续在Entry Set队列中等待。线程处于**waiting for monitor entry**状态。
2)如果monitor没有被其它线程拥有, Entry Set里面也没有其它等待线程,他就会成为相应类或者对象的 Monitor的 Owner,执行方法快里面的代码 。线程处于**Runnable**状态。
③ 如果线程执行了wait() 方法,放弃了 Monitor,进入 “Wait Set”队列。线程处于 **in Object.wait()**状态。
④ 当别的线程在该对象上调用了 notify() 或者 notifyAll() , “ Wait Set”队列中线程才得到机会去竞争。
死锁和热锁&例子
* 死锁
以下面一段代码举例:
public class JStackDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLockclass(true));//建立一个线程
Thread t2 = new Thread(new DeadLockclass(false));//建立另一个线程
t1.start();//启动一个线程
t2.start();//启动另一个线程
}
}
class DeadLockclass implements Runnable {
public boolean falg;// 控制线程
DeadLockclass(boolean falg) {
this.falg = falg;
}
public void run() {
/** * 如果falg的值为true则调用t1线程 */
if (falg) {
while (true) {
synchronized (LockObject.o1) {
System.out.println("o1 " + Thread.currentThread().getName());
synchronized (LockObject.o2) {
System.out.println("o2 " +Thread.currentThread().getName());
}
}
}
}
/** * 如果falg的值为false则调用t2线程 */
else {
while (true) {
synchronized (LockObject.o2) {
System.out.println("o2 " + Thread.currentThread().getName());
synchronized (LockObject.o1) {
System.out.println("o1 " + Thread.currentThread().getName());
}
}
}
}
}
}
class LockObject {
static Object o1 = new Object();
static Object o2 = new Object();
}
查看dump文件

发现这两个线程处于block状态,有代码行数提示,往下翻提示死锁更明显。

* 热锁
多个线程对锁的竞争激烈,会导致性能瓶颈。
1)频繁的线程的上下文切换:线程在等待资源阻塞的时候,操作系统会把线程切换放到等待的队列,当线程获得资源之后,会将这个线程切换到执行队列中。
2)大量的系统调用:因为线程的上下文切换,或者临界区的频繁的进出,都可能导致大量的系统调用。有时候虽然系统很忙碌,但是 CPU用在 “用户态 ”的比例较小,应用程序得不到充分的 CPU资源。
3)随着 CPU数目的增多,系统的性能反而下降。因为 CPU数目多,同 时运行的线程就越多,可能就会造成更频繁的线程上下文切换和系统态的 CPU开销,从而导致更糟糕的性能。
举个例子:

1)看pid:从top来看,cpu占用非常高,pid是18125。
2)看线程:

第一个pid:31360,16进制是7a80。
3)dump进程: jstack 18125 >~/jstack-18125.txt
4)找线程号:

发现代码线程创建过多,而且一直在run。
系统态例子
执行多个,也可以观察运行队列情况
!/bin/bash
while (true)
do
cd ;
done
内存问题
linux内存概念
先从free命令理解下linux内存。

- `total`总内存=`used`已使用+`free`空闲。
- `buffers`:用来存储目录里面有什么内容。 比如执行了ls /etc之后 **buffers增长了146980**。
- `cached`:用来记忆我们打开的文件。 比如新建一个文件 **cached增长了258804**。
- `swap`:应用所需内存超过物理内存时,就会发生页面切换,通常要为系统配置swap空间。
他在一个独立磁盘分区上,物理内存耗尽后,会将应用最少运行的部分置换到swap空间里(活跃的不置换),如果要访问swap空间的部分,就必须将他置换进内存,对应用造成响应和吞吐量影响。
JVM垃圾回收在页面交换时性能很差,为了回收不可达对象所占用空间,需要访问大量内存,如果堆的一部分被置换出去了,就必须先置换进内存以便垃圾回收器扫描存活对象,增加垃圾收集的持续的时间,并且又stop the world,引起长时间停顿。
- `-buffers/cache` 的意思: 等于used-buffers-cached。
从OS系统的角度来看,buffers/cached 都是属于系统使用的,应用使用的要减掉他们,反应的是被程序实实在在吃掉的内存。
- `+buffers/cache` 的意思: 等于free + buffers + cached
从应用的角度来看,buffer/cached只是系统为了提高文件读取的性能而设置的,当自己应用需要用到这块的空间时,系统可以把它们回收掉,给应用程序使用,所以他们是应用可以使用的内存总数。
- `shared`: 据说是进程间共享内存,了解不多,因为这个参数出现的问题没有过。
下面用工具vmstat查问题,通常从下面两个方面看
内存不够用
* `swpd`:使用的虚拟内存大小。
* `free`:空闲的物理内存大小。
* `buff`:用作缓冲的内存大小。
* `cache`:用作缓存的内存大小。
* `si`:每秒从交换区写到内存的大小,由磁盘调入内存。
* `so`:每秒写入交换区的内存大小,由内存调入磁盘。
如果swpd,so,si比较高,说明内存资源不足,已经占用交换区资源。需要增加内存。
cat /proc/sys/vm/swappiness
找pe设置为0
内存溢出
取个线上发生的栗子:
1)依旧通过top去进程和线程号
2)jmap pid dump出线程

发现Xobj占用大量资源,查了api原来是文件poi用的,所以初步锁定跟文件io有关。
3)看GC日志

发现频繁FullGC,并且回收不明显,说明有大量对象持续进来,或者占着没有释放掉。
4)翻日志记录,action信息,果然有文件操作。
5)最终发现是文件上传功能,读取文件时会parse每一行,每一行都会关联查询多个表,性能非常低下。
io问题
主要讲文件IO,网路IO中间件用多了,经验比较少。
文件IO问题 主要通过iotop命令查看,其他提供几个日常经常排查的问题就可以解决大部分场景。
给几个命令看下
一次性往test.txt文件写100M
dd if=/dev/zero of=test.txt bs=100M count=1
查看各个进程打开的文件数据量:
lsof -n |awk '{print $2} " " $3'|sort|uniq -c |sort -nr|more
查看文件被哪个进程站着
lsof /data/program/logs/xxx.log
查看进程的IO
lsof -p 4978
删除但是没有释放文件句柄的文件列表
lsof|grep delete