xiaobaoqiu Blog

Think More, Code Less

在线诊断工具

Java诊断我相信再日常开发中都十分需要.相信大家都使用过以下几种诊断方式:

1.肉眼诊断
就是出现问题时候企图通过肉眼来发现代码问题.通常很明显的问题有效;
2.日志诊断
通过给出问题的代码入参或者上下游加上日志,或者出问题的时候加上日志来诊断;这种方式的问题就是出现问题你需要重新拉分支,加日志,本地测试再上限这一系列流程.另外一个问题是过多的日志会影响应用性能;
3.监控诊断
通过完善的监控,通常包括完善且精确的异常划分.对不同的异常情况有独立的监控.加上完善的报警机制.这是最理想的情况.其优势是能快速定位问题点,问题是建立这一套完善的机制需要很长时间.并且监控只能发现问题类型,比如监控发现是数据错误,但是到底那条数据错误还是需要日志等手段来定位.
4.在线诊断工具
在不需要重新发布的情况下,在线诊断应用的问题所在.通常还可以用于定位接口瓶颈等问题;

这里简单介绍一下最近找到的几个在线诊断工具.它们都是非侵入式的,即不需要修改原始应用的代码再重新发布上线这个过程.

常用包括几个诊断工具包括,这里做一个简单的介绍:

1.BTrace
2.HouseMD
3.Greys-Anatomy

参考: http://www.infoq.com/cn/articles/java-profiling-with-open-source/

. 1.BTrace

Btrace(bytecode trace)是Kenai在2009年开发的一个开源项目(https://kenai.com/projects/btrace/),是一个动态跟踪分析JAVA源代码的工具。 它可以用来帮我们做运行时的JAVA程序分析,追踪,监控等.

官网: https://kenai.com/projects/btrace/

用户手册: https://kenai.com/projects/btrace/pages/UserGuide

samples下有很多使用例子.参考网上的一个例子: http://mgoann.iteye.com/blog/1409685

参考: http://wiki.corp.qunar.com/pages/viewpage.action?pageId=56198121

1.1使用场景:

1.数据采样 比如我们要优化某块代码,就可以先写一个btrace脚本,丢到线上服务器上跑一段时间; 这样可以收集到最真实的数据,而且不用发布之类的; 然后根据收集到的数据,做有目的的优化,优化之后,还可以对比出优化的效果。 2.排查问题 3.性能监控

1.2优点

1.跟踪任何java代码的调用(包括第三方类库中的,甚至jdk中的),比如统计某方法的调用次数,耗时。 2.可以监控的维度有很多,比如异常,同步块,内存申请,数组声明,甚至可以精确到行 3.完全不侵入业务代码,不需要重启服务 4.安全,对于应用内的数据来说是只读的。

1.3缺点

  1. 有较多的使用限制,如只能调用用BtraceUtils中的方法、不能写实例变量; (见docs/usersguide.html中的BTrace Restrictions条目) 所以刚编写脚本的时候,可能会有各种不习惯,但其实大多会用到的方法,BtraceUtils里都有封装 (其实个人感觉这个不算缺点,只是这个工具定位在追踪和监控,所以出于安全考虑,做了这个限制)
  2. 监控基本上只能单点,集群环境没法玩
  3. 被监控的服务重启,btrace也需要跟着重启 4.有影响原始应用的风险,严重可能导致JVM Crash.

1.4使用方式

  • Boot方式启动 追踪已启动的jvm进程 命令:btrace/bin/btrace 12345 SomeScript.java 注意,下载下来后bin下的文件都不可执行,需要chmod u+x,并且需要和目标进程是同一个用户来启动

  • Agent方式启动 与目标jvm同时启动 配置jvm参数:-javaagent:btrace-agent.jar=script=SqlMapTracer.class 注意:agent方式启动只能使用编译过的脚本 使用btrace/bin/btracec SqlMapTracer.java 编译 agent方式启动的btrace的标准输出位于你的SqlMapTracer.class所在目录下的SqlMapTracer.class.btrace文件

boot方式灵活,但是当要求追踪结果精确时,就不太好用了。因为不知道btrace启动之前,目标进程已经执行了多少代码。agent方式是和目标进程同时启动的,可以保证结果精确。

1.5原理

主要就是使用ASM来实现对类的修改,再使用Instumentation实现类的内存替换.

btrace原理: http://victorzhzh.iteye.com/blog/965789

btrace源码分析: http://agapple.iteye.com/blog/1005918

. 2.HouseMD

高负载服务器端Java程序 诊断工具的特性要求有:

1.命令行接口, 能够方便在服务器环境中运行;
2.支持常用诊断调式手段, 能够在其中快速来回切换;
3.容易定位跟踪目标, 且不易出错;
4.弱侵入, 目标Java程序无需任何修改, 不用重新部署或重启;
5.有效控制给目标进程带来的资源消耗;
6.不遗留任何"代码垃圾"等后遗症.

使用手册: https://github.com/CSUG/HouseMD/wiki/UserGuideCN

. 2.1安装

首先安装jenv:

1
2
3
4
5
6
xiaobaoqiu@xiaobaoqiu:~/Tools/HouseMD$ curl -L -s get.jenv.io | bash
...
xiaobaoqiu@xiaobaoqiu:~/Tools/HouseMD$ source /home/xiaobaoqiu/.jenv/bin/jenv-init.sh
...
xiaobaoqiu@xiaobaoqiu:~/Tools/HouseMD$ jenv help
...

再安装HouseMD就很简单:

1
2
3
4
5
6
xiaobaoqiu@xiaobaoqiu:~/Tools/HouseMD$ jenv install housemd
Installing: housemd 0.2.7
Parsing http://jenv.mvnsearch.org/candidate/housemd/download/0.2.7/Linux/x86_64

Downloading: housemd 0.2.7
...

. 3.Greys-Anatomy

Greys是一个java进程执行过程中的异常诊断工具。 在不中断程序执行的情况下轻松完成问题排查工作.

源码: https://github.com/oldmanpushcart/greys-anatomy

使用范例: https://github.com/oldmanpushcart/greys-anatomy/wiki/Getting-Start

交互方式: 命令行交互

功能:

1.查看加载类,方法信息
2.方法执行监控(调用量,成功失败率,响应时间)
3.方法执行数据观测(参数,返回结果,异常信息等)
4.方法执行数据记录
5.性能开销渲染
6.方法执行数据自定义观测(js脚本)
7.查看方法调用堆栈

处理sql超时

最近组内又出现一次因为SQL慢查询导致业务整体雪崩的情况.很惭愧的是,那个慢查询SQL是我大概一年之前写的(一个统计的SQL,数据量又比较大),那时还很稚嫩.

之前组内出现这种情况一般是让DBA上一个慢查询脚本,直接暴力kill那些慢查询.

其实各个JDBC driver实现java.sql.Statement的时候都需要实现setQueryTimeout这个函数,其作用就是设置SQL的执行超时时间,当SQL超时会直接被取消掉并且抛出QLTimeoutException异常.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
     * Sets the number of seconds the driver will wait for a
     * <code>Statement</code> object to execute to the given number of seconds.
     *By default there is no limit on the amount of time allowed for a running
     * statement to complete. If the limit is exceeded, an
     * <code>SQLTimeoutException</code> is thrown.
     * A JDBC driver must apply this limit to the <code>execute</code>,
     * <code>executeQuery</code> and <code>executeUpdate</code> methods.
     * <p>
     * <strong>Note:</strong> JDBC driver implementations may also apply this
     * limit to {@code ResultSet} methods
     * (consult your driver vendor documentation for details).
     * <p>
     * <strong>Note:</strong> In the case of {@code Statement} batching, it is
     * implementation defined as to whether the time-out is applied to
     * individual SQL commands added via the {@code addBatch} method or to
     * the entire batch of SQL commands invoked by the {@code executeBatch}
     * method (consult your driver vendor documentation for details).
     *
     * @param seconds the new query timeout limit in seconds; zero means
     *        there is no limit
     * @exception SQLException if a database access error occurs,
     * this method is called on a closed <code>Statement</code>
     *            or the condition seconds >= 0 is not satisfied
     * @see #getQueryTimeout
     */
    void setQueryTimeout(int seconds) throws SQLException;

默认情况下都不设置超时时间(即默认值0).即永远不超时.Druid的Filter机制很容易的让我们设置这个查询超时时间.

1.SQL超时时间Filter实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
 * 设置SQL执行时间
 * 如果超时,查询被cancel,并抛出SQLTimeoutException异常
 *
 * @author: xiaobaoqiu  Date: 15-8-22 Time: 下午1:55
 */
public class SqlTimeOutFilter extends FilterEventAdapter {

    /**
     * 默认超时时间,单位秒
     * 注意不要设置太小
     */
    private static final int QUERY_TIMEOUT_THRESHOLD_SECOND = 100;

    /**
     * 超时时间,默认为QUERY_TIMEOUT_THRESHOLD_SECOND
     */
    private int timeoutThreshold = QUERY_TIMEOUT_THRESHOLD_SECOND;

    @Override
    protected void statementExecuteBefore(StatementProxy statement, String sql) {
        setQueryTimeout(statement);
        super.statementExecuteBefore(statement, sql);
    }

    @Override
    protected void statementExecuteBatchBefore(StatementProxy statement) {
        setQueryTimeout(statement);
        super.statementExecuteBatchBefore(statement);
    }

    @Override
    protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {
        setQueryTimeout(statement);
        super.statementExecuteUpdateBefore(statement, sql);
    }

    @Override
    protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
        setQueryTimeout(statement);
        super.statementExecuteQueryBefore(statement, sql);
    }

    /**
     * 设置Statement超时时间,
     * statement.setQueryTimeout单位是秒,0表示没有限制.这个函数可能会抛出SQLException异常,场景:
     *      1.数据库访问错误
     *      2.在一个已经关闭的Statement上调用这个方法
     *      3.超时时间不满足seconds >= 0的条件
     *
     * @param statement
     */
    private void setQueryTimeout(StatementProxy statement) {
        try {
            statement.setQueryTimeout(timeoutThreshold);
        }catch (SQLException se) {
            //TODO: do something
        }
    }

    public int getTimeoutThreshold() {
        return timeoutThreshold;
    }

    public void setTimeoutThreshold(int timeoutThreshold) {
        this.timeoutThreshold = timeoutThreshold;
    }
}

2.SQL超时时间Filter使用

默认的DruidDataSource就支持设置设置Filter:

1
2
3
4
5
public void setProxyFilters(List<Filter> filters) {
        if (filters != null) {
            this.filters.addAll(filters);
        }
    }

我们可以实现DataSourceFactory(参考默认实现DefaultDataSourceFactory),在创建DataSource的时候调用setProxyFilters加上我们自己写的Filter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DruidDataSourceFactory implements DataSourceFactory {

    @Override
    public DataSource create(String host, int port, String username, String password, String dbName, int corePoolSize,
                             int maxPoolSize, String jdbcUrlOption, boolean autoCommit) throws ResourceException {
        DruidDataSource dataSource = new DruidDataSource();//使用默认实现DruidDataSource
        //设置dataSource参数, username等

        //设置dataSource的Filter
        List<Filter> filters = Lists.newArrayList();
        SqlTimeOutFilter timeOutFilter = new SqlTimeOutFilter();
        filters.add(timeOutFilter);
        dataSource.setProxyFilters(filters);
        
        //dataSource初始化等操作
        ...

        return dataSource;
    }
}

关于超时时间设置的一篇博文,很全面:

http://www.importnew.com/2466.html

MyBatis空where拦截器

最近项目中出现了至少两次因为Mybatis的动态where条件不满足导致实际sql语句的where条件为空,进而查询全表,当数据量比较大的时候,导致OOM的情况.

如何禁止这种情况,个人觉得三种措施:

  • 1.在逻辑层面加充分的参数有效性检查;
  • 2.在where条件中如果索引条件都不满足,加上1=2这种必然失败的条件;
1
2
3
4
5
6
7
8
9
10
11
<where>
    <choose>
        <when test="id != null">
            ...
        </when>
        ...
        <otherwise>
        and 1=2
        </otherwise>
    </choose>
</where>
  • 3.Mybatis拦截器;

前两种措施都是依赖人,从这个层面讲,是不靠谱的,即一个策略不是强制的,就是不靠谱的.相对而言,第三种是不依赖程序员的自觉性,是最靠谱的.乘周六有时间,实现一个简单的Mybatis拦截器来拦截where条件为空的SQL语句.

1.实现

先上代码,这里拦截了SqlCommandType为select,update,delete这三种类型.出现情况目前只是打日志.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
 * Mybatis拦截器, 拦截:
 *      select语句where条件为空
 *      update语句where条件为空
 *      delete语句where条件为空
 *
 * 处理: 目前只是打warning日志,因为项目中存在数据量很少的表读全表的情况(比如加载数据进缓存)
 *      TODO:后续考虑设置拦截器的白名单,不在白名单的触发where条件为空的直接抛出异常.
 *
 * @author: xiaobaoqiu  Date: 15-8-22 Time: 上午10:26
 */
@Intercepts({ @Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = { Connection.class }) })
public class EmptyWhereInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(EmptyWhereInterceptor.class);

    /**
     * 拦截的 COMMAND 类型
     */
    private static final Set<String> INTERCEPTOR_COMMAND = Sets.newHashSet("select", "update", "delete");

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler handler = (StatementHandler) invocation.getTarget();

        //获取实际的StatementHandler
        if (handler instanceof RoutingStatementHandler) {
            handler = (BaseStatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
        }

        //获取SqlCommandType
        String commandType = getCommandType(handler);

        if (INTERCEPTOR_COMMAND.contains(commandType)) {
            String originSql = handler.getBoundSql().getSql().toLowerCase();  //获取sql
            if (!originSql.contains("where")) {
                logger.warn("禁止使用不带where条件的SQL语句.原始SQL={}", originSql);
            }
        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * 获取Command类型,小写化返回
     *
     * @param handler
     * @return
     */
    private String getCommandType(StatementHandler handler) {
        MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(handler, "mappedStatement");
        return mappedStatement.getSqlCommandType().toString().toLowerCase();
    }
}

包括一个简单反射工具的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
 * 反射工具
 *
 * @author: xiaobaoqiu  Date: 15-8-22 Time: 上午11:52
 */
public class ReflectUtil {
    public ReflectUtil() {
    }

    /**
     * 改变 Accessible,便于访问private等属性
     * @param field
     */
    private static void makeAccessible(Field field) {
        if(!Modifier.isPublic(field.getModifiers())) {
            field.setAccessible(true);
        }
    }

    /**
     * 获取 object 的字段,字段名称为filedName,获取不到返回null
     * @param object
     * @param filedName
     * @return
     */
    private static Field getDeclaredField(Object object, String filedName) {
        Class superClass = object.getClass();

        while(superClass != Object.class) {
            try {
                return superClass.getDeclaredField(filedName);
            } catch (NoSuchFieldException var4) {
                superClass = superClass.getSuperclass();
            }
        }

        return null;
    }

    /**
     * 获取object字段fieldName的值,如果字段不存在直接抛异常
     *
     * @param object
     * @param fieldName
     * @return
     */
    public static Object getFieldValue(Object object, String fieldName) {
        Field field = getDeclaredField(object, fieldName);
        if(field == null) {
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
        } else {
            makeAccessible(field);
            Object result = null;

            try {
                result = field.get(object);
            } catch (IllegalAccessException var5) {
                var5.printStackTrace();
            }

            return result;
        }
    }

    /**
     * 设置object字段fieldName的值,如果字段不存在直接抛异常
     *
     * @param object
     * @param fieldName
     * @param value
     */
    public static void setFieldValue(Object object, String fieldName, Object value) {
        Field field = getDeclaredField(object, fieldName);
        if(field == null) {
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
        } else {
            makeAccessible(field);

            try {
                field.set(object, value);
            } catch (IllegalAccessException var5) {
                var5.printStackTrace();
            }

        }
    }
}

2.Mybatis的Interceptor原理

这篇文章很好的解释了Mybatis的Interceptor机制.

mybatis读取配置再xml文件中Interceptor,通过反射构造其实例,将所有的Interceptor保存到InterceptorChain中。

mybatis的拦截器只能代理指定的四个类:ParameterHandler、ResultSetHandler、StatementHandler以及Executor。

参考:

http://www.tuicool.com/articles/RbyUfu

http://blog.csdn.net/hupanfeng/article/details/9247379

Mysql PacketTooBigException

最近做一个同步线上数据导dev或者beta环境的小工具.今天遇到一个小异常:

1
Caused by: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (17610152 > 16777216). You can change this value on the server by setting the max_allowed_packet' variable.

原因就是一次插入的数据太大了.Mysql存在数据包最大大小的配置,默认是1M,这里我们的dev环境是16M,但是我这里出现了大于16M的数据packet:

1
2
3
4
5
6
7
mysql> show VARIABLES like '%max_allowed_packet%';
+--------------------------+------------+
| Variable_name            | Value      |
+--------------------------+------------+
| max_allowed_packet       | 16777216   |
| slave_max_allowed_packet | 1073741824 |
+--------------------------+------------+

修改max_allowed_packet:

  • 1.set global max_allowed_packet = 210241024*10
  • 2.修改my.conf文件(windows为my.ini)
1
max_allowed_packet      = 16M

注意点:

  • 1.保证Master和Slave的max_allowed_packet参数一致(一般都要求所以参数一致);

Oomkiller

最近dev环境的Tomcat服务,还有dev环境的crate服务老是出现问题,现象就是Tomcat应用自己停了.导致测试中老是感觉莫名其妙.跟了一下原因,发现背后就是著名的oom-killer.

其实出现这个问题早就预料到了,8G内存的虚拟机器上面跑了接近20个Tomcat应用,每个Tomcat至少要求了0.5G的内存,很多要求的是默认的2G的内存.

这里主要简单介绍一下oomkiller是什么东西,原理是什么,以及对我们的影响.

1.oomkiller简介

简单的说,Linux 内核有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程(为什么后面会讲到),为了防止内存耗尽而内核会把该进程杀掉,从而腾出内存留给系统用。

现象就是相关的日志文件(/var/log/messages)里面会看到下面类似的 Out of memory: Kill process 信息(日志里面把机器host去掉了),还包括一些如pid,process name,cpu mask,trace等信息:

1
2
3
4
5
6
Aug  4 22:03:18  kernel: Out of memory: Kill process 21447 (java) score 81 or sacrifice child
Aug  4 22:03:18  kernel: Killed process 21447, UID 40001, (java) total-vm:4242176kB, anon-rss:711240kB, file-rss:1832kB
Aug  5 01:17:21  ntpd[1195]: synchronized to Xx.xx.xxx.xx, stratum 3
Aug  5 01:35:52  kernel: java invoked oom-killer: gfp_mask=0xd0, order=0, oom_adj=0, oom_score_adj=0
Aug  5 01:35:52  kernel: java cpuset=/ mems_allowed=0
Aug  5 01:35:52  kernel: Pid: 17391, comm: java Not tainted 2.6.32-358.23.2.el6.x86_64 #1

2.oomkiller原理

Linux 内核根据应用程序的要求分配内存,通常来说应用程序分配了内存但是并没有实际全部使用,为了提高性能,这部分没用的内存可以留作它用,这部分内存是属于每个进程的,内核直接回收利用的话比较麻烦,所以内核采用一种过度分配内存(over-commit memory)的办法来间接利用这部分 “空闲” 的内存,提高整体内存的使用效率。

所谓的过度分配内存,可以理解为"打白条",进程A告诉系统我需要2G内存,系统只会答应不会拒绝,因为系统认为任何一个进程不可能每时每刻都全部占用其申请的内存.所以实际上系统并没有给进程A完整的2G内存,而是需要使用的时候才临时分配.这就好像银行现金肯定远远小于所以储户的总值,因为银行认为不可能所以储户同一时间把所以的钱都取出来.

一般来说这样做没有问题,但当大多数应用程序都消耗完自己的内存的时候麻烦就来了,因为这些应用程序的内存需求加起来超出了物理内存(包括 swap)的容量,内核(OOM killer)必须杀掉一些进程才能腾出空间保障系统正常运行。

用银行的例子来讲可能更容易懂一些,部分储户取钱的时候银行不怕,银行有足够的存款应付,当全国所以的储户(或者绝大多数储户)都取钱而且每个人都想把自己钱取完的时候银行的麻烦就来了,银行实际上是没有这么多钱给大家取的。

2.1 根据什么kill

内核检测到系统内存不足,然后挑选并杀掉某个进程的过程可以参考内核源代码 linux/mm/oom_kill.c,当系统内存不足的时候,oom_kill被触发,然后选择一个进程杀掉,如何判断和选择一个进程呢,总不能随机选吧?系统会根据进程使用内存的情况给进程打分,选择进程kill的时候主要根据这个分数,打分的机制很朴素:最占用内存的进程。

每个进程的分数都由进程的oom_score记录:

1
2
3
4
[baoqiu.xiao@Xxx ~]$cat /proc/26893/oom_score
76
[baoqiu.xiao@Xxx ~]$ cat /proc/14969/oom_score
82

另外一个重要的参数就是进程的oom_adj,表示该pid进程被oom killer杀掉的权重,介于 [-17,15]之间,越高的权重,意味着更可能被oom killer选中,-17表示禁止被kill掉。默认为0.

一些其他的细节:

1.子进程会继承父进程的oom_adj;
2.OOM不适合于解决内存泄漏(Memory leak)的问题;
3.有时free查看还有充足的内存,但还是会触发OOM,是因为该进程可能占用了特殊的内存地址空间;

2.2 如何关闭oomkiller

有几种方法关闭oomkiller机制:

  • 1.调整oom_adj 将进程的oom_adj设置为17即禁止了该进程被kill.

比如系统的ssh一般是禁止oom的:

1
2
3
4
[baoqiu.xiao@ ~]$ ps aux | grep sshd
root      1208  0.0  0.0  64116   296 ?        Ss   May28   1:47 /usr/sbin/sshd
[baoqiu.xiao@ ~]$ cat /proc/1208/oom_adj 
-17

其他需要禁用,可以如下:

1
pgrep -f "sshd" | while read PID; do echo -17 > /proc/$PID/oom_adj;done

其实在oom-killer的日志中我们是可以看到未-17的一些应用:

1
2
3
4
Aug  5 01:35:52 l-crm4.des.dev.cn0 kernel: [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name
...
Aug  5 01:35:52 l-crm4.des.dev.cn0 kernel: [ 1187]     0  1187    16029       73   2     -17         -1000 sshd
...
  • 2.修改内核参数 sysctl 下有2个可配置选项:

    vm.panic_on_oom = 0 #内存不够时内核是否直接panic,0表示开启 vm.oom_kill_allocating_task = 1 #oom-killer是否选择当前正在申请内存的进程进行kill

只要设置/etc/sysctl.conf文件中vm.panic_on_oom=1就表示关闭oom-killer(线上环境一般不推荐):

1
2
3
# sysctl -w vm.panic_on_oom=1
vm.panic_on_oom = 1   //1表示关闭,默认为0表示开启OOM
# sysctl -p

参考: http://lwn.net/Articles/317814/