linux问题排查总结

  • CPU问题
    • 运行队列
    • 使用率/上下文切换
    • 排查步骤
      • 对象锁和类锁
      • 线程状态
      • 死锁和热锁&例子
    • 系统态例子
  • 内存问题
    • linux内存概念
    • 内存不够用
    • 内存溢出
  • io问题

线上机器的故障和性能,从最终报警的结果来看主要从三个方面去关注:CPU问题,内存问题,IO问题。

CPU问题

要解决CPU问题,先理解下运行队列和上下文切换的概念。

运行队列

看一下vmstat 1(每秒刷新一次)命令的结果。

linux问题排查总结_第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文件。


    linux问题排查总结_第2张图片

    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,详细过程看下图,贴一张别人的图。 ![](http://upload-images.jianshu.io/upload_images/3027201-fcb320f3b8d094d6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ① 当一个线程申请进入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文件

![](http://upload-images.jianshu.io/upload_images/3027201-552d218bbf596586.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

![](http://upload-images.jianshu.io/upload_images/3027201-b5e560d0d9380737.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


* 热锁
    多个线程对锁的竞争激烈,会导致性能瓶颈。

1)频繁的线程的上下文切换:线程在等待资源阻塞的时候,操作系统会把线程切换放到等待的队列,当线程获得资源之后,会将这个线程切换到执行队列中。

2)大量的系统调用:因为线程的上下文切换,或者临界区的频繁的进出,都可能导致大量的系统调用。有时候虽然系统很忙碌,但是 CPU用在 “用户态 ”的比例较小,应用程序得不到充分的 CPU资源。

3)随着 CPU数目的增多,系统的性能反而下降。因为 CPU数目多,同 时运行的线程就越多,可能就会造成更频繁的线程上下文切换和系统态的 CPU开销,从而导致更糟糕的性能。

举个例子:  
![](http://upload-images.jianshu.io/upload_images/3027201-23cdcff90d75adf9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

2)看线程:
![](http://upload-images.jianshu.io/upload_images/3027201-efc18fbc03b99f83.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
第一个pid:31360,16进制是7a80。

3)dump进程: jstack 18125 >~/jstack-18125.txt

4)找线程号:

![](http://upload-images.jianshu.io/upload_images/3027201-c85e5a71cbbcae3e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


发现代码线程创建过多,而且一直在run。

系统态例子

执行多个,也可以观察运行队列情况

!/bin/bash

while (true)
do
cd ;
done



内存问题

linux内存概念

先从free命令理解下linux内存。 ![](http://upload-images.jianshu.io/upload_images/3027201-e16f7356bea70104.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - `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出线程 ![](http://upload-images.jianshu.io/upload_images/3027201-66398b1017ca8bcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 发现Xobj占用大量资源,查了api原来是文件poi用的,所以初步锁定跟文件io有关。 3)看GC日志 ![](http://upload-images.jianshu.io/upload_images/3027201-3d6635a560af9d93.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 发现频繁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

你可能感兴趣的:(linux问题排查总结)