从Integer比较引发的一起血案.

某日,javashop的小李在检查代码时,发现了一个比较严重的问题.

笔者看完之后很纳闷,为啥Integer的比较不能用==呢?

我们平时声明一个int类型的a 和一个int类型的b,完全可以使用==来比较,虽然Integer是引用类型,但是他一定是做了什么处理,不然我在比较的时候,为啥得到的结果和预期的时候一致呢?

带着这个疑问,笔者做了一个实验:

两次比较的结果竟然不一样,笔者瞬间就惊了个呆,原来自己的理解一直是错的,实在是愧对老司机这个称号!

但是为什么第一次比较的结果是一样的呢?笔者又做了一次实验,发现在-128到127这个范围之间,使用==比较时结果是相等的,而超出了这个范围,使用==比较往往得不到预期的结果.

一定是做了什么特殊的处理,Integer和其他妖艳贱货的引用类型不一样!

于是笔者仔细查看了Integer源码,发现了这样一段代码:

原来在对Integer进行赋值操作的时候 即Integer a=1时,进行了装箱操作,使用的是上图中的方法.当传入的值在-128-127之间是,会从java的缓冲池获取一个对象.而==符号比较的是引用类型的地址,返回缓冲池对象时,地址相同,所以==会生效.

同样的道理,我们在使用String类型比较的时候

String a=”abc”;

String b=”abc”;

a==b返回的结果同样是true,因为abc也是放入了字符串常量池,使用上述方法赋值时引用地址相同;反之,如果在对Integer赋值时使用

Integer a=new Integer(1);

Integer b=new Integer(1);

a==b的返回结果就变成false啦!

所以在对引用类型进行比较时,应使用equals()方法,而不要使用==符号

服务器tomcat自动停止问题

Linux下面有个机制叫OOM killer(Out Of Memory killer,这个东西会在系统内存耗尽或者即将耗尽的情况下跳出来执行,选择性的干掉一些进程以求释放一些内存。

可能会出现的问题就是,某天tomcat无缘无故停掉了,或者是 某天机器突然登不上了,能ping通,但是ssh死活连不了。原因是tomcat/sshd进程被OOM killer干掉了。在系统的日志中通常会有下面的打印日志:Out of memory: kill process 959 (sshd) score 55 or a child。 

1.       OOM什么时候出现?

我们在用户空间申请内存时,一般使用的是malloc,是不是当malloc返回为空时,没有可以申请的内存空间了就会返回呢?答案是否定的。在关于malloc的申请内存的机制中有下面的一段描述:

By default, Linux follows an optimistic memory allocation strategy. This means that when malloc() returns non-NULL there is no guarantee that the memory really is available. This is a really bad bug. In case it turns out that the system is out of memory, one or more processes will be killed by the infamous OOM killer. In case Linux is employed under circumstances where it would be less desirable to suddenly lose some randomly picked processes, and moreover the kernel version is sufficiently recent, one can switch off this overcommitting behavior using a command like:

上面的描述中说明了在Linux中当malloc返回的是非空时,并不代表有可以使用的内存空间。Linux系统允许程序申请比系统可用内存更多的内存空间,这个特性叫做overcommit特性,这样做可能是为了系统的优化,因为不是所有的程序申请了内存就会立刻使用,当真正的使用时,系统可能已经回收了一下内存。但是,当你使用时Linux系统没有内存可以使用时,OOM Killer就会出来让一些进程退出。

Linux下有3种Overcommit的策略(参考内核文档:vm/overcommit-accounting),可以在/proc/sys/vm/overcommit_memory配置(取0,1和2三个值,默认是0)。

(1)0:启发式策略,比较严重的Overcommit将不能得逞,比如你突然申请了128TB的内存。而轻微的overcommit将被允许。另外,root能Overcommit的值比普通用户要稍微多

(2)永远允许overcommit,这种策略适合那些不能承受内存分配失败的应用,比如某些科学计算应用。

(3)永远禁止overcommit,在这个情况下,系统所能分配的内存不会超过swap+RAM*系数(/proc/sys/vm/overcmmit_ratio,默认50%,你可以调整),如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序。

/proc/sys/vm # cat overcommit_ratio

50

当然我可以修改proc//oom_adj的值,这里的默认值为0,当我们设置为-17时,对于该进程来说,就不会触发OOM机制,被杀掉。

echo -17 > /proc/$(pidof sshd)/oom_adj

这里为什么是-17呢?这和Linux的实现有关系。在Linux内核中的oom.h文件中,可以看到下面的定义:

/* /proc//oom_adj set to -17 protects from the oom-killer */

#define OOM_DISABLE (-17)

/* inclusive */

#define OOM_ADJUST_MIN (-16)

#define OOM_ADJUST_MAX 15

这个oom_adj中的变量的范围为15到-16之间。越大越容易被kill。oom_score就是它计算出来的一个值,就是根据这个值来选择哪些进程被kill掉的。

总之,通过上面的分析可知,满足下面的条件后,就是启动OOM机制。

1) VM里面分配不出更多的page(注意linux kernel是延迟分配page策略,及用到的时候才alloc;所以malloc + memset才有效)。

2) 用户地址空间不足,这种情况在32bit机器上及user space超过了3GB,在64bit机器上不太可能发生。

2     当该机制被触发后,会让什么样的进程退出?

只要存在overcommit,就可能会有OOM killer。 Linux系统的选择策略也一直在不断的演化。我们可以通过设置一些值来影响OOM killer做出决策。Linux下每个进程都有个OOM权重,在/proc//oom_adj里面,取值是-17到+15,取值越高,越容易被干掉。  最终OOM killer是通过/proc//oom_score这个值来决定哪个进程被干掉的。这个值是系统综合进程的内存消耗量、CPU时间(utime + stime)、存活时间(uptime – start time)和oom_adj计算出的,消耗内存越多分越高,存活时间越长分越低。总之,总的策略是:损失最少的工作,释放最大的内存同时不伤及无辜的用了很大内存的进程,并且杀掉的进程数尽量少。  另外,Linux在计算进程的内存消耗的时候,会将子进程所耗内存的一半同时算到父进程中。

最后还是说一下使用需要注意的事项:

1、内存首先要足够基本进程的正常运行,以防被无缘无故kill

2、要对tomcat的设置进行优化,尤其是对内存的控制