NFS-RPC框架优化过程(从37k到168k)

NFS-RPC(目前改名为McQueenRPC,代码地址在:https://github.com/bluedavy/McQueenRPC)框架从编写之初,到现在为止(应该还会有些提升,不过估计不大),每秒支撑的请求数上升了好几倍,测试结果的演变为:
37k –> 56k –> 65k –> 88k –> 93k –> 143k –> 148k –> 153k –> 160k –> 163k –> 168k
以上测试结果为在100并发、100 request byte、100 response byte以及单连接下的背景下得出的,在这篇blog中来分享下这个框架所做的一些优化动作,希望能给编写rpc框架或使用mina/netty/grizzly的同学们一点点帮助,也希望得到高手们更多的指点。

1、37k –> 56k
由于目前大部分的NIO框架采用的均为1个socket io线程处理多个连接的io事件,如果io线程时间占用太长的话,就会导致收到的响应处理的比较慢的现象,这步优化就是针对反序列化过程占用io线程而做的,采用的方法即为在读取流时仅根据长度信息把所有的bytes都读好,然后直接作为收到的信息返回给业务线程,业务线程在进行处理前先做反序列化动作,感兴趣的同学可以看看NFS-RPC中:code.google.nfs.rpc.netty.serialize.NettyProtocolDecoder或code.google.nfs.rpc.mina.serialize.MinaProtocolDecoder,以及code.google.nfs.rpc.mina.server.MinaServerHandler或code.google.nfs.rpc.netty.server.NettyServerHandler。

2、56k –> 65k
在测试的过程中,发现YGC的耗时比较长,在咨询了sun的人后告诉我主要是由于有旧生代的数据结构引用了大量新生代对象造成的,经过对程序的分析,猜测是benchmark代码本身用于记录请求响应时间信息的ConcurrentLinkedQueue造成的,在某超级大牛的指示下,换成了在每个线程中用数组的方式,按区间来记录响应时间信息等,感兴趣的同学可以看看NFS-RPC中:code.google.nfs.rpc.benchmark.SimpleProcessorBenchmarkClientRunnable

3、65k –> 88k
在某超级大牛的分析下,发现目前的情况下io线程的上下文切换还是比较频繁,导致io线程处理效率不够高,默认情况下,NIO框架多数采用的均为接到一个包后,将这个包交由反序列化的处理器进行处理,对于包中有多个请求信息或响应信息的情况,则采用一个一个通知的方式,而rpc框架在接到一个请求或响应对象时的做法通常是唤醒等待的业务线程,因此对于一个包中有多个请求或响应的状况就会导致io线程需要多次唤醒业务线程,这个地方改造的方法是nio框架一次性的将包中所有的请求或响应对象通知给业务线程,然后由业务线程pipeline的去唤醒其他的业务线程,感兴趣的同学可以看看NFS-RPC中:code.google.nfs.rpc.netty.serialize.NettyProtocolDecoder或code.google.nfs.rpc.mina.serialize.MinaProtocolDecoder,以及code.google.nfs.rpc.netty.client.NettyClientHandler或code.google.nfs.rpc.mina.client.MinaClientProcessor。

4、88k –> 93k
这步没什么可说的,只是多支持了hessian序列化,然后这个结果是用hessian序列化测试得出的,注意的是hessian不要使用3.1.x或3.2.x版本,这两个系列的版本性能极差,建议使用hessian 4.0.x版本。

5、93k –> 143k
在到达93k时,看到测试的结果中有不少请求的响应时间会超过10ms,于是用btrace一步一步跟踪查找是什么地方会出现这种现象,后来发现是由于之前在确认写入os send buffer时采用的是await的方式,而这会导致需要等待io线程来唤醒,增加了线程上下文切换以及io线程的负担,但这个地方又不能不做处理,后来就改造成仅基于listener的方式进行处理,如写入失败会直接创建一个响应对象,这次改造后效果非常明显,感兴趣的同学可以看看nfs-rpc中:code.google.nfs.rpc.mina.client.MinaClient或code.google.nfs.rpc.netty.client.NettyClient。

6、143k –> 148k
这步是在@killme2008 的指点下,将tcpNoDelay设置为了false,但这个设置不适用于低压力的情况,在10个线程的情况下tps由3w降到了2k,因此tcpNoDelay这个建议还是设置成true。

7、148k –> 153k
这步没什么可说的,只是多支持了protobuf序列化,这个结果是用protobuf序列化测试得出的,之前还测试过下@bnu_chenshuo 写的一个protorpc,是基于protobuf的rpc加上netty实现的,结果也很强悍,测试出来是149k,看来protobuf的rpc也是有不少值得学习的地方的。

8、153k –> 160k
server接到消息的处理过程也修改成类似client的pipeline机制,同时将之前获取协议处理器和序列化/反序列化的处理器的地方由map改成了数组,具体可以参考NettyServerHandler、ProtocolFactory和Codecs。

9、160k –> 163k
Grizzly的leader对grizzly部分的代码做了很多的修改,结果创造了目前rpc benchmark的最高纪录:163k,更多优化的细节敬请大家期待后续的一篇grizzly rpc优化细节的blog…

10、163k –> 168k
@minzhou 对nfs-rpc的代码进行了优化,将之前在Decoder中做的构造String对象的部分挪到了业务线程中,于是TPS也有了一定的上升,感谢。

上面就是到目前为止的所有优化动作,其中的很多我估计高手的话是不会犯错的,我走的弯路多了些,总结来说rpc框架的优化动作为:
1、尽可能减少io线程的占用时间;
2、尽可能减少线程上下文切换;
3、尽可能使用高效的序列化/反序列化;

NFS-RPC框架在后面将继续做更多的测试和优化,例如采用aio、coroutine、测试CNK问题等,敬请关注:http://code.google.com/p/nfs-rpc

GC悲观策略之Serial GC篇

仍然是http://blog.bluedavy.com/?p=166这篇blog中的代码,换成-Xms30m -Xmx30m -Xmn10m -XX:+UseSerialGC后执行的结果为YGC、YGC、YGC、YGC、FGC。
原因就在于Serial GC的悲观策略是不同的,下面就来分析下。

仍然是http://bluedavy.me/?p=166这篇blog中的代码,换成-Xms30m -Xmx30m -Xmn10m -XX:+UseSerialGC后执行的结果为YGC、YGC、YGC、YGC、FGC。
原因就在于Serial GC的悲观策略是不同的,Serial GC在执行YGC时,首先进入如下代码片段进行检查:
[c]
void DefNewGeneration::collect(bool full,
bool clear_all_soft_refs,
size_t size,
bool is_tlab) {

if (!collection_attempt_is_safe()) {
gch->set_incremental_collection_will_fail();
return;
}

}
bool DefNewGeneration::collection_attempt_is_safe() {
if (!to()->is_empty()) {
return false;
}
if (_next_gen == NULL) {
GenCollectedHeap* gch = GenCollectedHeap::heap();
_next_gen = gch->next_gen(this);
assert(_next_gen != NULL,
“This must be the youngest gen, and not the only gen”);
}

const double evacuation_ratio = MaxLiveObjectEvacuationRatio / 100.0;

size_t worst_case_evacuation = (size_t)(used() * evacuation_ratio);
// 这里的_next_gen也就是旧生代了,下面贴出旧生代对应的代码
return _next_gen->promotion_attempt_is_safe(worst_case_evacuation,
HandlePromotionFailure);
}
bool TenuredGeneration::promotion_attempt_is_safe(
size_t max_promotion_in_bytes,
bool younger_handles_promotion_failure) const {
bool result = max_contiguous_available() >= max_promotion_in_bytes;
if (younger_handles_promotion_failure && !result) {
result = max_contiguous_available() >=
(size_t) gc_stats()->avg_promoted()->padded_average();
if (PrintGC && Verbose && result) {
gclog_or_tty->print_cr(“TenuredGeneration::promotion_attempt_is_safe”
” contiguous_available: ” SIZE_FORMAT
” avg_promoted: ” SIZE_FORMAT,
max_contiguous_available(),
gc_stats()->avg_promoted()->padded_average());
}
} else {
if (PrintGC && Verbose) {
gclog_or_tty->print_cr(“TenuredGeneration::promotion_attempt_is_safe”
” contiguous_available: ” SIZE_FORMAT
” promotion_in_bytes: ” SIZE_FORMAT,
max_contiguous_available(), max_promotion_in_bytes);
}
}
return result;
}
[/c]
这个检查首先是检查目前新生代中使用的空间是否大于了旧生代剩余的空间,如大于且HandlePromotionFailure为true(默认值),那么再检查旧生代剩余的空间是否大于之前平均晋升的old的大小,如大于则返回true,小于则返回false,在返回false的情况下,就不进行YGC的剩下的操作了。
按照这样的规则,在第九次循环的时候,应该执行的是FGC,而不是YGC,这里的原因是在Serial GC时,是先进行计数和时间的统计等,再调用DefNewGeneration的collect的,因此尽管这次没有真正的执行YGC的动作,但还是被计数和计入时间了,但这次为什么GC log中输出的不是Full GC呢,请看下面的代码片段:
[c]
void GenCollectedHeap::do_collection(bool full,
bool clear_all_soft_refs,
size_t size,
bool is_tlab,
int max_level) {

// 在当前场景下,传入的full为false,因此complete为false
bool complete = full && (max_level == (n_gens()-1));
const char* gc_cause_str = “GC “;
if (complete) {
GCCause::Cause cause = gc_cause();
if (cause == GCCause::_java_lang_system_gc) {
gc_cause_str = “Full GC (System) “;
} else {
gc_cause_str = “Full GC “;
}
}

for (int i = starting_level; i <= max_level; i++) { if (_gens[i]->should_collect(full, size, is_tlab)) {

// Determine if allocation request was met.
if (size > 0) {
if (!is_tlab || _gens[i]->supports_tlab_allocation()) {
if (size*HeapWordSize <= _gens[i]->unsafe_max_alloc_nogc()) {
size = 0;
}
}
}

}
}
[/c]
从上可看出,当YGC结束后,eden的空间可以满足分配的需求的话,需要分配的对象的大小size就被置为零了,而在第九次循环中,由于YGC提前结束,因此eden的空间是仍然不足的,此时需要分配的size大小会不变,上面的GC动作还将进入TenuredGeneration的should_allocate来进行检查了,该方法的代码片段如下:
[c]
bool TenuredGeneration::should_collect(bool full,
size_t size,
bool is_tlab) {
// This should be one big conditional or (||), but I want to be able to tell
// why it returns what it returns (without re-evaluating the conditionals
// in case they aren’t idempotent), so I’m doing it this way.
// DeMorgan says it’s okay.
bool result = false;
// 因为full是false,因此进入不了这里
if (!result && full) {
result = true;
if (PrintGC && Verbose) {
gclog_or_tty->print_cr(“TenuredGeneration::should_collect: because”
” full”);
}
}
if (!result && should_allocate(size, is_tlab)) {
result = true;
if (PrintGC && Verbose) {
gclog_or_tty->print_cr(“TenuredGeneration::should_collect: because”
” should_allocate(” SIZE_FORMAT “)”,
size);
}
}
// If we don’t have very much free space.
// XXX: 10000 should be a percentage of the capacity!!!
if (!result && free() < 10000) { result = true; if (PrintGC && Verbose) { gclog_or_tty->print_cr(“TenuredGeneration::should_collect: because”
” free(): ” SIZE_FORMAT,
free());
}
}
// If we had to expand to accomodate promotions from younger generations
if (!result && _capacity_at_prologue < capacity()) { result = true; if (PrintGC && Verbose) { gclog_or_tty->print_cr(“TenuredGeneration::should_collect: because”
“_capacity_at_prologue: ” SIZE_FORMAT ” < capacity(): " SIZE_FORMAT, _capacity_at_prologue, capacity()); } } return result; } virtual bool should_allocate(size_t word_size, bool is_tlab) { bool result = false; // 32 bit上BitsPerSize_t为32,64 bit上为64, LogHeapWordSize在32 bit为2,64 bit为3 size_t overflow_limit = (size_t)1 << (BitsPerSize_t - LogHeapWordSize); if (!is_tlab || supports_tlab_allocation()) { result = (word_size > 0) && (word_size < overflow_limit); } return result; } [/c] 由此可看出,should_allocate为true,因此触发了FGC。 这样就可以理解为什么在第九次循环的时候打印出来的日志是没有Full GC字样的,但又计算为执行了一次YGC和一次FGC的。 由于Concurrent GC是基于Serial GC实现的,因此悲观策略是相同的。 ps: 如大家想研究这些东西,一方面是下载源码,另一方面也可以下载一个debug版本的jdk,这样就可以通过打开一些日志,看到更多的hotspot运行的细节,另外,也可以看出,Parallel GC的实现在代码上就清晰多了。

GC悲观策略之Parallel GC篇

先来看段代码:
[java]
import java.util.*;
public class SummaryCase{
public static void main(String[] args) throws Exception{
List caches=new ArrayList();
for(int i=0;i<7;i++){ caches.add(new byte[1024*1024*3]); } caches.clear(); for(int i=0;i<2;i++){ caches.add(new byte[1024*1024*3]); } } } [/java] 当用-Xms30m -Xmx30m -Xmn10m -XX:+UseParallelGC执行上面的代码时会执行几次Minor GC和几次Full GC呢?

先来看段代码:
[java]
import java.util.*;
public class SummaryCase{
public static void main(String[] args) throws Exception{
List caches=new ArrayList();
for(int i=0;i<7;i++){ caches.add(new byte[1024*1024*3]); } caches.clear(); for(int i=0;i<2;i++){ caches.add(new byte[1024*1024*3]); } Thread.sleep(10000); } } [/java] 当用-Xms30m -Xmx30m -Xmn10m -XX:+UseParallelGC执行上面的代码时会执行几次Minor GC和几次Full GC呢? 按照eden空间不足时触发minor gc的规则,上面代码执行后的GC应为:M、M、M、M,但实际上上面代码执行后GC则为:M、M、M、F、F。 这里的原因就在于Parallel Scavenge GC时的悲观策略,当在eden上分配内存失败时且对象的大小尚不需要直接在old上分配时,会触发YGC,代码片段如下: [c] void PSScavenge::invoke(){ ... bool scavenge_was_done = PSScavenge::invoke_no_policy(); PSGCAdaptivePolicyCounters* counters = heap->gc_policy_counters(); if (UsePerfData) counters->update_full_follows_scavenge(0); if (!scavenge_was_done || policy->should_full_GC(heap->old_gen()->free_in_bytes())) { if (UsePerfData) counters->update_full_follows_scavenge(full_follows_scavenge);< GCCauseSetter gccs(heap, GCCause::_adaptive_size_policy); if (UseParallelOldGC) { PSParallelCompact::invoke_no_policy(false); } else { PSMarkSweep::invoke_no_policy(false); } } ... } PSScavenge::invoke_no_policy{ ... if (!should_attempt_scavenge()) { return false; } ... } bool PSScavenge::should_attempt_scavenge() { ... PSAdaptiveSizePolicy* policy = heap->size_policy(); size_t avg_promoted = (size_t) policy->padded_average_promoted_in_bytes(); size_t promotion_estimate = MIN2(avg_promoted, young_gen->used_in_bytes()); bool result = promotion_estimate < old_gen->free_in_bytes(); ... return result; } [/c] 在上面should_attempt_scavenge代码片段中,可以看到会比较之前YGC晋升到Old中的平均大小与当前新生代中已被使用的字节数大小,取更小的值与旧生代目前剩余空间大小对比,如更大,则返回false,就终止了YGC的执行了,当返回false时,PSScavenge::invoke就将触发Full GC了。 在PSScavenge:invoke中还有一个条件为:policy->should_full_GC(heap->old_gen()->free_in_bytes(),来看看这段代码片段: [c] bool PSAdaptiveSizePolicy::should_full_GC(size_t old_free_in_bytes) { bool result = padded_average_promoted_in_bytes() > (float) old_free_in_bytes; ... return result; } [/c] 可看到,这段代码检查的也是之前YGC时晋升到old的平均大小是否大于了旧生代的剩余空间,如大于,则触发full gc。 总结上面分析的策略,可以看到采用Parallel GC的情况下,当YGC触发时,会有两个检查: 1、在YGC执行前,min(目前新生代已使用的大小,之前平均晋升到old的大小中的较小值) > 旧生代剩余空间大小 ? 不执行YGC,直接执行Full GC : 执行YGC; 2、在YGC执行后,平均晋升到old的大小 > 旧生代剩余空间大小 ? 触发Full GC : 什么都不做。 按照这样的说明,再来看看上面代码的执行过程中eden和old大小的变化状况: 代码 eden old YGC FGC 第一次循环 3 0 0 0 第二次循环 6 0 0 0 第三次循环 3 6 1 0 第四次循环 6 6 1 0 第五次循环 3 12 2 0 第六次循环 6 12 2 0 第七次循环 3 18 3 1 第八次循环 6 18 3 1 第九次循环 3 3 3 2 在第7次循环时,YGC后旧生代剩余空间为2m,而之前平均晋级到old的对象大小为6m,因此在YGC后会触发一次FGC。 而第9次循环时,在YGC执行前,此时新生代已使用的大小为6m,之前晋级到old的平均大小为6m,这两者去最小值为6m,这个值已大于old的剩余空间,因此就不执行YGC,直接执行FGC了。 Sun JDK之所以要有悲观策略,我猜想理由是程序最终是会以一个较为稳态的状况执行的,此时每次YGC后晋升到old的对象大小应该是差不多的,在YGC时做好检查,避免等YGC后晋升到Old的对象导致old空间不足,因此还不如干脆就直接执行FGC,正因为悲观策略的存在,大家有些时候可能会看到old空间没满但full gc执行的状况。 埋个伏笔,大家将上面的执行参数换为-XX:+UseSerialGC执行看看,会发生什么呢? 🙂

JavaOne美国之行–硅谷公司交流篇

趁着去旧金山的机会,参观下硅谷的一些公司也是重要的行程,这次我们主要拜访了位于旧金山城内的Terracotta,位于硅谷的Google和Facebook,和这些公司的交流确实让我们学习到了更多的东西,分别来看看。

趁着去旧金山的机会,参观下硅谷的一些公司也是重要的行程,这次我们主要拜访了位于旧金山城内的Terracotta,位于硅谷的Google和Facebook,和这些公司的交流确实让我们学习到了更多的东西,分别来看看。

    Terracotta


Terracotta最近的风头很盛,很多地方都能看到关于他们的BigMemory的介绍,BigMemory集成到了Ehcache,从而为开发人员提供了一个很透明的多层cache,层次为jvm堆内的cache–>jvm堆外的cache–>disk–>分布式的cache server,随着层次的增加,访问的速度越来越慢,但可缓存的数据则越来越多,这个层次设计的确实非常不错,同时带来的好处也非常明显,可缓存的数据就可以变得非常大了,jvm堆外的cache貌似其采用的是Direct ByteBuffer实现,因此当访问堆外的cache时,需要进行序列化/反序列化,速度会有所下降。
说到工作气氛方面,到Terracotta的时候,就看到了中间有个巨大的桶,放了很多冰冻的啤酒,开会的同学们每人一瓶啤酒,吃着零食,:)

    Google

Google是一个非正式的交流,因此在技术上没有太大的东西,了解到的信息只有:google的fellow是很少的,大概只有4–5个;系统的发布只需要点击下reload即可实现;通常不需要登录到生产环境机器查找问题,问题主要是通过monitor来报告,通过一个http的页面查看程序的具体运行状况来发现问题,不过每个team可能会不一样;如需要使用bigtable这类基础的东西,只需要申请即可,环境、运维等均由bigtable team来处理。
在Google吃了午饭,确实名不虚传,好吃呀,另外google的工作环境好像和传说中的有点不一样,工作区域是由一个又一个的透明房间组成,每个房间大概坐4个人,免费的饮料、零食到处都是,也很好吃,:),可以带宠物上班,午餐后很多人在遛狗,打沙滩排球,上班时间很弹性,一堆人都在10点后才开始上班。
著名的google自行车

google园区一

google园区二

google园区内的沙滩排球

    Facebook

和Facebook的交流非常不错,能感觉到他们技术人员获得的巨大成就感和荣耀感,并且这也是一家很open的公司,先后和他们开放平台的人、MySQL DBA以及自建DataCenter的人进行了交流,和开放平台交流的部分cenwenchu同学(http://blog.csdn.net/cenwenchu79/)会介绍,我还是说说和他们MySQL DBA以及自建DataCenter的人的交流。

和MySQL DBA的交流
他们的团队才4–5个人,太让人惊讶了,果然是典型的精英文化呀,在没招google的那个mysql高人之前,他们从来就不修改mysql的代码,而是由他们clever的工程师绕过各种各样的bug,但自从招了google的这位高人后,他们就在mysql上提交了一堆的patch(详细见www.facebook.com/mysqlatfacebook),其中就有最近很火的online schema change tool,这也是他们的文化:hire for good people,and listen to them。
为什么他们4–5个人能维护这么多的MySQL数据库呢,他们保障网站的可用性采取的两层团队,第一层是直接维护网站可用性的,当出现问题时他们可以处理大部分的问题;处理不了的问题将流转到mysql DBA,应用的owner,对于这些处理不了的问题MySQL DBA,应用的owner在之后都会想办法写一堆的tools来帮助第一层维护团队在以后能自助解决,因此在Facebook有一堆的工具,twitter最近也写了一篇文章(http://engineering.twitter.com/2010/09/tool-legit.html)来说他们对于工具的重视,在说网站的可用性保障这件事是,他们说到的另外一句话让我很感动,就是说到每个开发人员的心里都认为网站的可用性保障是他们的职责,因此都会把这事看得非常重要,并为之付出努力。
MySQL采用M-S结构,通过replication实现对等复制,之所以不采用不对等复制是为了避免维护的复杂,美国东西海岸的datacenter的延时大概为70ms。
Facebook网站严重依赖cache,差不多95%的流量会走入cache,剩下的5%才会走到MySQL,没有什么流量保护措施,因此一旦cache挂了,就会非常严重,这也就是9月23日Facebook出现的故障(http://www.facebook.com/note.php?note_id=431441338919),cache都是访问同datacenter的,一旦当前datacenter的cache出现问题,并不会调度到其他的datacenter。
交流时说到Facebook的一个活动,挺好玩,就是每隔一段时间,会在一个晚上让大家来做自己感兴趣的事,做一个通宵,然后通过演示来向大家讲解,好的东西就入选成为产品。

和自建DataCenter的人的交流
Facebook从今年开始了自建datacenter(http://www.facebook.com/prinevilledatacenter),以前的也都是租的,这个自建的datacenter大概会在明年年初上线,方式和google的基本一样,也是自己设计服务器,我问了下datacenter建设的人才是如何培养或招聘的,他们说美国这类人才其实还是比较多的,而服务器设计方面的人才主要还是来源于google。
Facebook的人说到,当服务器多了后,自建几乎就是必须的了,而且能源问题将成为巨大的问题。
在datacenter上耗费时间最多的是选址,因为涉及到能源问题,政府支持的问题,:)
Facebook很重视机器的使用效率,据说他们现在机器的使用效率比google还高。

Facebook的工作环境是开放式的,显示屏很大,应该都是30寸以上了,CEO就坐在中间一个透明的房子里,:),免费的饮料和零食也一样到处都是。
低调的Facebook门牌

Facebook参观者留言板

Facebook的球,参观者贴在身上的纸条最终将贴回到这个球上,这个很有意义,看看到Facebook上市那天,这个球会变成多大。

JavaOne美国之行–大会组织篇

一场成功的大会,除了一堆强劲的Session和Speaker外,大会的组织也至关重要,这次OOW+JavaOne总共4.1w人参加,JavaOne有380+个Session,分为两个主要的场所,同时开始的Session通常会有10多个,要组织好实在不是件容易事,来给大家分享下自己感受到的大会的组织,总体感觉的话组织的还是非常不错的,细节方面分成四个方面来讲下:Session的形式,session的指引,活动的组织以及午餐。

一场成功的大会,除了一堆强劲的Session和Speaker外,大会的组织也至关重要,这次OOW+JavaOne总共4.1w人参加,JavaOne有380+个Session,分为两个主要的场所,同时开始的Session通常会有10多个,要组织好实在不是件容易事,来给大家分享下自己感受到的大会的组织,总体感觉的话组织的还是非常不错的,细节方面分成四个方面来讲下:Session的形式,session的指引,活动的组织以及午餐。

    Session的形式

Session主要是这么四种形式:讲的;Speaker讨论的;开放式讨论的;动手实验以及自己组织的Session。
有点奇怪的是讲的竟然通常是两个人,甚至三个人讲的,反倒一个人讲的不多,本来一直觉得多个人讲一个Session,而且只是一个小时的Session挺难安排的,但惊奇的听到有几场的搭配是非常好的,例如GC MythBusters那场,Tony和他的搭档配合的非常不错,一人说一个tip,然后另一个来总结这个tip。
Speaker讨论的有好几场,这种场次的形式通常是PPT上列出几个问题,然后几个Speaker来分别表达他们的观点,例如最经典的就是JRockit、J9和Hotspot的主要作者一起讨论的那场,大家可以很清晰的听出几个JVM不同的发展重点,:)。
开放式讨论的就是由参加的人来向台上的一堆人提问,这个通常要求参加的人有所准备,又准备的人收获会比较多。
动手实验这个很经典,就是给参与者电脑,直接在电脑上操作,然后学会到一些东西,由于是现场操作,通常这个是一个快速学会东西的时候,例如这次JRockit Mission Control的HOL,就可以让参加的人很快的学会如何用JRMC来查一些问题,当然,这对Speaker要求其实比较高,因为要设计好HOL。
自己组织的Session就是放了一块公告牌,然后向自己讲的人可以贴上想讲的话题,然后大家去参与就可以了,这个也非常的好,尽管我没去…

    session的指引

在会场周边的路上会有各种各样的指引,例如地上的,路灯牌等。
地上的指引

这么多的场所在举办Session,如何让参与者尽快的找到自己要参加的Session的地方,这是很重要的,Oracle主要是通过网站的schedule builder,让你知道你要参加的session的地点,然后当你走入相应的hotel的时候,都会看到一堆的指示牌,让你知道你要去的房间在哪里,而在每个房间的门口都有一个大显示屏,告诉你这个房间正在或将要进行的session,speaker,如你在网站上已选了这个session,那么你就可以直接扫描自己的与会卡,然后进去,如果没注册过,刷卡的时候会显示黄色,这个时候就只能等待了,通常要等待到开场后,看看人是否已满,如果满了的话就去不了了,我觉得这非常的好。
大显示屏

对于少数需要去较远场所参加的session,都有专车接送的,这个也是相当方便的,而且车的数量是很充足的。
不过这个唯一的不好就是schedule builder的那个网站访问的速度实在是太慢,而且还经常出现访问不了的现象,另外就是session以及地址总是变,还好是一般都会另外再发邮件告诉你你所选的session变更的信息。

    活动的组织

这次JavaOne除了Session以外,还组织了一些活动,例如在旁边一个街道上搭起的帐篷,大家可以进去聊聊天,玩玩google赞助的lego,或者是找java.net上的一些projects的人聊聊。
还有就是几乎每晚都有酒会,酒会的形式多数是自助餐,然后一些酒,最经典的是22号晚上,oracle给所有参会者的答谢party,在旧金山的金银岛上召开,里面有一堆的游乐设施,例如摩天轮等,然后就是自助餐,还有就是请了著名的黑眼豆豆乐队以及前老鹰乐队的灵魂人物来现场,可惜我不知道黑眼豆豆,后来知道感兴趣的同学是非常非常的兴奋,都觉得Oracle的这个party办的非常的好,尽管据之前在美国呆了挺长时间的同事说美国公司的年会差不多都这样,但对于我这种土包子这个party还是很震撼的。
party 娱乐设施

party 乐队现场

    午餐

午餐对参会者很重要,而且多数情况下中午都只有半小时吃饭的时间,还好国外的东西通常几分钟也就吃完了,这次午餐感觉组织的一般,经常会出现一个会场排队排了一会后被告知没有午餐的现象。

最后,就是会场的无线网络还是非常不错的,这个太重要了,:)

JavaOne美国之行–Session篇

Session的总结是重头戏,在这篇blog中,来分享下我参与过的Session,以及听完后我对Session的评价和对于有收获的Session制定的一些Action Plan。

本届JavaOne我总共参加了24个Session,主要集中在JVM方面以及Experience Talk方面,5分为最高分的话,我给这些Session的评分状况如下所示:

从参加的Session来看,JavaOne中标题党的Session也是有一些的,但能够得到收获的Session还是有不少的,根据内容以及Speaker挑选非常重要,通常,水平较高的Speaker总是不会让你失望的,就算讲的东西可能不能让你满意,但会后的交流一定会让你受益匪浅,总体来说,我觉得参加这次JavaOne收获的东西还是足够多了,应该算得上值回票价了,:)
Session的Speaker有不少是来自印度的,看来现在印度已经从以前只是做技术含量低的外包工作,到现在成为core,领导技术的方向了,真是强悍呀。

Session的总结是重头戏,在这篇blog中,来分享下我参与过的Session,以及听完后我对Session的评价和对于有收获的Session制定的一些Action Plan。

本届JavaOne我总共参加了24个Session,主要集中在JVM方面以及Experience Talk方面,5分为最高分的话,我给这些Session的评分状况如下所示:

从参加的Session来看,JavaOne中标题党的Session也是有一些的,但能够得到收获的Session还是有不少的,根据内容以及Speaker挑选非常重要,通常,水平较高的Speaker总是不会让你失望的,就算讲的东西可能不能让你满意,但会后的交流一定会让你受益匪浅,总体来说,我觉得参加这次JavaOne收获的东西还是足够多了,应该算得上值回票价了,:)
Session的Speaker有不少是来自印度的,看来现在印度已经从以前只是做技术含量低的外包工作,到现在成为core,领导技术的方向了,真是强悍呀。

我参加的Session以及打分如下:

接下来对参加的每个Session做一些介绍和总结。
9月20日
1. OSGi and Java EE: A Hybrid Approach to Enterprise Java Application Development
评分 ★☆☆☆☆
总结
本来这场是打算去听JDK 7的,但同行的好几个同学都感兴趣,我就转为听这场OSGi的了,Speaker是两个印度人,听的我那个痛苦呀,还好对OSGi本来就有些基础,呵呵,整场停下来没有太多的收获,基础性的东西介绍的比较多,另外就是介绍了下OSGi在对Java EE方面支持的增强,例如JNDI等,这些其实我也不怎么感兴趣,听到的唯一一个有点意思的东西是OSGi对web application的支持,看来在这么久没关心后,这个东西还是有所增强,现在折腾出了一个Web Application Bundle,因此可实现和WAR直接的集成,这个值得看看。
行动计划
具体的看看Web Application Bundle。

2. Where Does All the Native Memory Go?
评分 ★☆☆☆☆
总结
这场是我期望非常高的一场,因为之前已经碰到了一些Native Memory消耗,又不是很好查的问题,但整场听下来,并没有给出什么好的解决方案,他给的解决方案还不如直接用google perftools,但不得不说,PPT真的写的非常系统化,这好像也是国外工程师不太一样的地方,例如PPT中会先讲到通常内存是如何申请、使用和回收的,以及JRE中哪些部分是会使用native memory,哪些是使用jvm heap的,这个是值得学习的,这也是JavaOne各场PPT给我的印象。
行动计划
修改下我自己的那个Sun JDK 1.6 GC的PPT,也更加系统化一点,同时也增加Native Memory这部分消耗更细致的讲解。

3. Step by Step: GC Tuning in the HotSpot Java Virtual Machine
评分 ★★★★☆
总结
这场是Rockstar: Tony Printezis同学几乎每届JavaOne都讲的Topic,说实话,内容没太多新颖的地方,估计大家都是冲着人去的,但讲的确实还是很不错,给我的收获主要是:优先选用ParallelOldGC,如果暂停时间过长,则考虑CMS;CMS的一些调优建议,例如和我之前经历过的尽量减少对象从新生代晋升到旧生代这点,还有开启ParallelRefProcEnabled。
交流
Session结束后向两个Speaker提了几个问题,一是关于jmap -histo能否推出一个加强版,直接看到对象的引用关系,回答是这是NetBeans Profiler team的事情,他们没办法控制;第二个问题是我测试了下G1,效果不是很好,是否有什么其他的tuning参数,Tony给了我张名片,让我email具体的日志信息给他,我想这是最大的收获,哈哈。
行动计划
修改我自己的那个Sun JDK 1.6 GC的PPT,加上这里的一些调优建议;
尝试开启ParallelRefProcEnabled,以及跟踪下实际的采用CMS系统的内存碎片的状况。

4. The Next Big Java Virtual Machine Language
评分 ★☆☆☆☆
总结
这场一个是去的晚了点,坐在很后面,前面的人直接把PPT挡住了,后面一堆人在摇头晃脑的找空隙点看PPT,另一方面是他说到的这些JDK中的弱点其实都是已知的,而且其实很多应用是历史原因,要切换到另一种语言成本是非常非常高的,因此还是更多的想想如何在现有的语言下做出更多的改进比较靠谱。

5. Project Lambda: To Multicore and Beyond
评分 ★★★★☆
总结
大牛Brian的Session,对于我这种之前对Lambda几乎没什么了解的人来说,还是非常不错的,但其实Session结束后,我很想问一个问题:Project Lambda要到JDK 8才发布,是不是等的时间有点长了,呵呵。
行动计划
看下lambda里面并行计算时线程数量是如何控制的,或者有知道的同学直接说下?

6. JavaOne Keynote
评分 ★★★☆☆
总结
貌似没得到什么非常有价值的信息,更多的还是一些高层次的走向还有一些小的信息,例如hotspot将去掉permgen,要实现large heap with low pause GC等,很多是没时间点的,这太要命了。

7. Advanced Monitoring and Troubleshooting with VisualVM
评分 ★★★☆☆
总结
由于自己之前对VisualVM还算有所了解和试用,介绍的东西有些太基础了,唯一让我眼前一亮的是原来现在已经有这么多的visualvm plugins了,其中的tracker plugin很有意思,可以直接显示类的方法执行了多少次,耗时多少,我很好奇莫非jvm内部之前是已经有这样的MBean可以获取到的,如果是的话那岂不是完全不需要自己写代码来记录这样的信息了。
行动计划
仔细看看Tracker Plugin。

9月21日
1. New Java Virtual Machine Tricks: Enhanced Hot Code Replace and Mixin Generation
评分 ★★★★★
总结
这场听的我很兴奋,:),一方面是PPT的系统化,让我掌握了更多的code generation和code replace可采用的技术,以及他们的问题,目前已有的解决方案,以及最后最让人兴奋的spring loaded的演示,听完后让我觉得如果可以采用spring loaded,那对于开发效率的提升是可以起到很大很大的帮助的。
交流
问了下speaker,spring loaded是纯java写的吗,speaker说是的,run as an agent.
行动计划
找到Spring loaded,进行试用,可惜杯具的是,我到现在为止都没找到spring loaded,莫非…这东西是商业的。

2. How to Tune and Write Low-Latency Applications on the Java Virtual Machine
评分 ★★★★★
总结
很精彩的一场Session,Hotspot和JRockit的同学轮番讲,告诉大家一些编写低延时Java应用的tips,:),得到的收获主要有:知道了原来JRockit也是generational gc,只是会做partial compaction,另外说到了JRockit的realtime版本中的很猛的GC:Deterministic GC, allowing guarantees of SLAs.,只有G1是打破generational这个传统的,不过至少从论文来看,G1还是很靠谱的,尽管现在实现出来的效果还不好;应该合理的设置Tenuring Threshold,我之前一直觉得调这个太麻烦,就没去做,看来还是值得尝试下;理解你所使用的数据结构,避免expanding带来的浪费,这个不错,算是从写代码角度来看的一个pratice;避免使用Finalizers,这个在之前的blog中写到的Deflater/Inflater内存泄露就是因为使用Finalizers造成的。
行动计划
试试合理设置Tenuring Threshold,看来能带来的效果如何。

3. JRockit mission control hol
评分 ★★★★★
总结
我去JavaOne之前,@rednaxelafx就一直强烈建议我一定要去参加hol,所谓hol就是指动手实验,因此这是一个关于JRMC(JRockit Misson Control)的动手实验,进去会场后就看到一堆的电脑,然后有篇文档告诉你怎么做,通过做一些练习,让你掌握如何通过JRMC来查找、分析一些问题,例如如何寻找到hotmethods,如何查看系统执行慢的原因,如何分析内存的分配等,做完练习后,发现确实非常猛,完全满足我现在希望查找的一些问题,可惜呀,这东西目前还不支持hotspot,而JRockit是收费的,so…
另外一个不错的地方是他给的这些练习的代码写的真的非常经典,印象深刻的有两个例子,其中一个你只需要改一行代码,就可以让运行效率提升10倍,另外一个例子只需要改两行代码,就可以让系统不做GC,这两个例子太经典了。
交流
问了下Marcus Hirt(也就是Oracle JRockit: The Defenitive Guide的作者,JRMC的leader)两个问题,一是是否可不通过JRMC来触发server进行flight recorder,回答是of course,并演示了下,太TM简单好用了;二是是否可跟踪两次gc之间allocation的状况,回答让我觉得我自己很土,其实只用在memory视图上将查看的范围缩为两次gc之间就行了,哎,这可是我们梦寐以求的功能呀。
行动计划
专门再写篇文章结合hol的练习来向大家介绍下JRMC,顺带把这些练习的代码提供给大家下载;
在自己的GC PPT中增加write friendly code to gc的部分。

4. JavaOne general technical sessions
评分 ★☆☆☆☆
总结
没得到任何有价值的信息。

5. Performance and Debugging Advancements in OpenJDK
评分 ★★★☆☆
总结
这场听到的就是OpenJDK新的版本中对Compile部分的一个优化,NMethod Sweeper,据说能提升不少。
行动计划
具体再看下这块,OK的话可以再写篇blog来介绍下这块优化的思想。

6. OpenJDK BOF
评分 ★★★☆☆
总结
这个Session的形式是由大家开放式的问些问题,OpenJDK的一伙commiter会来回答,由于没准备好,也没去问问题,因此收获很小。

7. 7 Deadly Sins of Enterprise Java Programming and Deployment in the Multicore Era
评分 ★☆☆☆☆
总结
其中一个Speaker是来自eBay的同学,可惜由于现场突然出现PPT播放的问题,导致最后没讲完,而我又急着去下场,因此最终这场我几乎没听到什么东西。

8. OSGi at a Large-Scale Enterprise: Lessons from eBay
评分 ★★☆☆☆
总结
简单说就是期望太高,失望越大,这个Session中只讲到了移植到OSGi是多么的痛苦,但并没讲好处是什么,eBay目前使用OSGi的状况是什么。
交流
根据后面的交流,才终于更多的知道了一些,其实eBay目前只是把OSGi用到他们的tools里,并没有应用到主站系统上,好处方面之所以没讲什么,是因为Speaker认为来听的人都知道有什么好处,这个…

9月22日
1. Experience Talk: Understanding Adaptive Runtimes
评分 ★★★★☆
总结
这场可是《Oracle JRockit: The Defenite Guide》两个作者开讲,因此期望也很高,不过说实话,还真没讲太多东西,更多的东西其实都写在书上了,反而是讲了很多的JRMC。
花絮就是对提问的同学会现场送书,于是我也去问问题了,可惜等我问的时候,书已经送完了,郁闷,我问的两个问题:一个是关于G1的,我想听听JRockit的人怎么看G1,他们觉得G1目前尚未成熟,不好评价;第二个问题是什么时候JRMC会支持Hotspot,听到的回答很杯具:about after two years,好吧…

2. Too Big to Fail: Top Tips for Massive, Mission-Critical Enterprise Applications
评分 ★★★★★
总结
这场比我期望高太多,如果要评听过的最佳session,我想我会选这场,speaker有20年的工作经验,10年jvm compiler的经验,10年银行系统方面的经验,他其实只是简单的给出了7个对于大型系统有帮助的tips,但可谓是个个击中要害,并且还给你讲明白为什么,明显是经验丰富的人才能做到的,例如NUMA、share readonly data、use hprof、beaware identity hashcode,其中有些是知道的,并且在用的,有些还真不知道…
花絮是讲到CompressedOOPS、NUMA的时候,speaker问有多少人知道或用过,下面竟然只有三、四个人,我很汗呀…
行动计划
share readonly data那个可以看看,方法不太一样,竟然是jni上的NewDirectByteBuffer;
use hprof,我记得我之前用过,是因为效果不好,但看他的演示,我觉得可以试试,只是郁闷的是要把inline关掉,这个影响不知道会有多大;
测试identity hashcode的影响,现场的demo来看对GC的影响很大。

3. Top 10 Causes for Java Issues in Production and What to Do When Things Go Wrong
评分 ★★★★☆
总结
还不错,分享的几个注意点都非常实在,不过基本都知道,这个session过程中有个环节挺好玩,就是说到为什么要注意GC呢,如果jvm实现商能提供一个flag就设置完内存管理,那就完美了,:)

4. Apache Harmony: An Open Innovation
评分 ★★★☆☆
总结
听完后发现没看到Harmony相对现在的几个JVM,有任何的优势…

5. Showdown at the Java Virtual Machine Corral
评分 ★★★★☆
总结
这场其实很有意思,可惜得听录音才行,这场其实是三个使用面最广的JVM(JRockit、J9、Hotspot)的主要作者们在上面回答PPT上的一些问题,挺有意思,:),可以听得出各个JVM的不同发展重点。

9月23日
1. The Garbage Collection Mythbusters
评分 ★★★★★
总结
还是Tony的Session,这场精彩很多,他和另外一个GC Group的人一起讲的,讲的非常好,提到了大家对于GC的一些误解,挺有意思的,例如gc是否能避免所有的内存泄露,给了个很有趣的简单例子;不要使用finalizers来释放资源等。
交流
这场后问了Tony一个问题,就是是否能支持将对象放到一块gc不扫描的区域(请注意,这个方式和Terracotta的off heap是很不一样的),提升系统的性能;Tony说,我们也有考虑这个,但需要Oracle确认。
听完Tony的回答,我懂了…
行动计划
提炼一些加到自己的GC PPT中。

2. Performance tuning from the pros
评分 ★☆☆☆☆
总结
非常非常一般。

3. What’s happening in my app:jvm monitoring tools
评分 ★☆☆☆☆
总结
又一个商业的jvm监控工具,不感兴趣,尽管其号称帮助jdk团队解决了一些问题。

4. Concurrency grabs from eBay
评分 ★☆☆☆☆
总结
太基础了点,尽管总结的还不错,现场很多人尽管连copy-on-write这样的pattern都不知道,而且还有很多人不知道线程池的类是不是在j.u.c里,好吧…

JavaOne美国之行–走势篇

JavaOne已经结束几天了,在走势方面,其实外部消息已经非常非常多了,毕竟现在Oracle掌控了Oracle、MySQL、两个使用面最广的JVM(JRockit、Hotspot)的发展方向,再结合我和jrockit、hotspot一些人聊后对JVM发展的更多感想吧。

JavaOne已经结束几天了,在走势方面,其实外部消息已经非常非常多了,毕竟现在Oracle掌控了Oracle、MySQL、两个使用面最广的JVM(JRockit、Hotspot)的发展方向,再结合我和jrockit、hotspot一些人聊后对JVM发展的更多感想吧。

  • Oracle Keynote
  • Oracle的keynote万众期待,现场发布的exalogic应该也算是比较隐蔽的消息了,之前貌似没太听说过,由于不太感兴趣,本来之前我是不打算听的,只是看到twitter上oracle的java架构师说larry会宣布重量级的消息,就仍然去听了,larry滔滔不绝的说着ExaLogic,但其实对于互联网行业而言,并不喜欢这种power server,毕竟power server就意味着,一旦它挂了,那影响就大了,对于企业应用到底是否划算,这个就不好评价了,只能说价格还是比较贵的,不过这反应出的就一个味道,就是oracle也像IBM一样,开始做整体解决方案了,也许企业应用比较喜欢这样的方式吧,这块更多的消息大家可以从网上看到很多,就只说到这了,上三张ExaLogic的图,:)
    Larry and Exalogic

    Exalogic

    Exalogic内的机器

    可惜在MySQL的发展方向上Oracle没说什么,大家还是很迷糊。

  • JavaOne Keynote & Technical General Session
  • JavaOne的keynote对于Java开发人员而言更是非常的关注,因为之前oracle已经说了在这场将宣布oracle对Java的发展策略,但说实话,听下来真的没听到什么,反倒是后面和一些jvm speaker交流的时候感觉到了更多,从keynote和technical general session得到的关键信息只有这些:
    1. JRockit和Hotspot将合并,且以hotspot为base,移植jrockit的feature到hotspot上;
    2. 将从Hotspot中移除PermGen,这算是向JRockit和J9靠拢了;
    3. 低延时的gc;
    4. JRockit Mission Control将支持Hotspot;
    5. JDK 7以及8的发布时间,包含的特性,这个其实之前Mark Reinhold在blog上已经写了,只是宣布后让之前没看过blog的更多人再失望下;
    6. Java在client端的发展,例如Java FX要更好的支持3D,支持html 5等;

    JavaOne keynote现场,场地那个大呀

    JavaOne General Technical Sessions现场

  • 和tony printezis & marcus hirt的交流
  • 在听Tony Printezis和Marcus Hirt的两场session后,问了他们几个问题,从中可以管窥到一些Oracle对Java的策略,恐怕大家会更失望了。
    问Tony的问题主要是两个:
    1. jmap -histo [pid]很多时候只能看到是什么类型占用了内存,通常看到的最多就是[C的占用,但无法知道是谁创建了这个[C,只能通过dump来看,这一方面是比较麻烦,另一方面是有些时候dump就来不及了,是否可以考虑在jmap上实现直接查看对象的引用关系;
    Tony answer:
    这块现在主要是netbean profiler team在control,我们不好去control.
    2. 有些时候我们需要在应用启动后就加载一些数据到内存中,而这些数据是会一直存在的,如果能将这些数据放到一块gc不会扫描到的区域,而又可以以直接对象的方式访问,那对于提升系统的性能可能会有不小帮助,你怎么看呢?
    Tony answer:
    是的,我们也有考虑过提供这块的支持,但这需要Oracle的确认。
    问Marcus Hirt的问题主要是:
    1. 你对G1怎么看,是否比现在JRockit的gencon gc更强?
    Marcus Hirt answer:
    目前G1还处于开发阶段,不好评价,需要等到其到了production mode才好评价。
    2. JRockit Mission Control什么时候能支持Hotspot呢?
    Marcus Hirt answer:
    大概需要两年,因为有很多难点。

    大家从上面的这些交流能看出什么呢,呵呵,我自己的感觉是Oracle对Java的Control确实会更…James Gosling离开的主要原因呀,这对Java的发展可能会不利,只能寄希望于有越来越多的人参与到OpenJDK,由社区来决定和加速Java的发展,另外能感觉到的是Hotspot和JRockit融合绝对没有想象中那么简单、顺利,而且Oracle主要还是着重商业上的发展。

    Deflater/Inflater可能造成Native Memory Leak

    Deflater/Inflater如使用不当,将有可能造成native memory leak,下面是一段示例的代码以及Sun官方网站的Bug记录:
    http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6734186
    http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4797189

    Deflater/Inflater如使用不当,将有可能造成native memory leak,下面是一段示例的代码:

    import java.util.zip.*;

    public class Bug {
    public static void main( String args[] ) throws Exception{
    for(int i=0;i<100;i++){ new Thread(new Runnable(){ public void run(){ for(int i=0;i<200;i++){ Deflater deflater = new Deflater( 9, true ); //deflater.end(); } byte[] bytes1=new byte[1024*512]; byte[] bytes2=new byte[1024*512]; byte[] bytes3=new byte[1024*512]; byte[] bytes4=new byte[1024*512]; byte[] bytes5=new byte[1024*512]; byte[] bytes6=new byte[1024*512]; byte[] bytes7=new byte[1024*512]; byte[] bytes8=new byte[1024*512]; } }).start(); Thread.sleep(1); } Thread.sleep(30000); } }

    用-Xmn10m运行上面的代码,可以看到即使在触发了minor gc和full gc后,Java进程占用的地址空间也不会降下去,而当主动调用deflater.end后,再次运行上面的代码,则可看到Java进程占用的地址空间就比较少了,因此在使用Deflater/Inflater时,一定要记得在不需要用了时主动的调用下end方法,就像使用FileInputStream之类的一样。
    在Sun官方网站上报告的bug如下:
    http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6734186
    http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4797189

    Sun JDK OOM

    Java的自动内存管理机制给开发人员带来了很多的便利,在设计、开发时可以完全不用考虑要分配多少内存,要记得回收内存等,但同时也带来了各种各样的问题,其中最典型的问题就是OOM,大部分Java开发人员估计都看到过java.lang.OutOfMemoryError这样的错误信息,在这篇文章中,就来介绍下Sun JDK中有哪几种OOM、OOM示例、造成OOM的原因的查找、解决以及Sun JDK代码中处理OOM的方式。

    PDF版本请从此下载:http://blog.bluedavy.com/open/Sun-JDK-OOM.pdf

    Java的自动内存管理机制给开发人员带来了很多的便利,在设计、开发时可以完全不用考虑要分配多少内存,要记得回收内存等,但同时也带来了各种各样的问题,其中最典型的问题就是OOM,大部分Java开发人员估计都看到过java.lang.OutOfMemoryError这样的错误信息,在这篇文章中,就来介绍下Sun JDK中有哪几种OOM、OOM示例、造成OOM的原因的查找、解决以及Sun JDK代码中处理OOM的方式。

    PDF版本请从此下载:http://blog.bluedavy.com/open/Sun-JDK-OOM.pdf

    1 OOM的种类
    在Sun JDK中运行时,Java程序有可能出现如下几种OOM错误:
     java.lang.OutOfMemoryError: unable to create new native thread
    当调用new Thread时,如已创建不了线程了,则会抛出此错误,如果是JDK内部必须创建成功的线程,那么会造成Java进程退出,如果是用户线程,则仅抛出OOM,创建不了的原因通常是创建了太多线程,耗尽了内存,通常可通过减少创建的线程数,或通过-Xss调小线程所占用的栈大小来减少对Java 对外内存的消耗。

     java.lang.OutOfMemoryError: request bytes for . Out of swap space?
    当JNI模块或JVM内部进行malloc操作(例如GC时做mark)时,需要消耗堆外的内存,如此时Java进程所占用的地址空间超过限制(例如windows: 2G,linux: 3G),或物理内存、swap区均使用完毕,那么则会出现此错误,当出现此错误时,Java进程将会退出。

     java.lang.OutOfMemoryError: Java heap space
    这是最常见的OOM错误,当通过new创建对象或数组时,如Java Heap空间不足(新生代不足,触发minor GC,还是不够,触发Full GC,还是不够),则抛出此错误。

     java.lang.OutOfMemoryError: GC overhead limit execeeded
    当通过new创建对象或数组时,如Java Heap空间不足,且GC所使用的时间占了程序总时间的98%,且Heap剩余空间小于2%,则抛出此错误,以避免Full GC一直执行,可通过UseGCOverheadLimit来决定是否开启这种策略,可通过GCTimeLimit和GCHeapFreeLimit来控制百分比。

     java.lang.OutOfMemoryError: PermGen space
    当加载class时,在进行了Full GC后如PermGen空间仍然不足,则抛出此错误。
    对于以上几种OOM错误,其中容易造成严重后果的是Out of swap space这种,因为这种会造成Java进程退出,而其他几种只要不是在main线程抛出的,就不会造成Java进程退出。

    2 OOM示例、原因查找和解决
    这些示例的class以及源码请从http://blog.bluedavy.com/jvm/cases/oom/OOMCases.zip下载,建议在运行前不要看源码,毕竟源码是简单的例子,如果直接看源码的话,可能会少了查找原因的乐趣。

    当Java程序运行时,会有很多种造成OOM的现象,这里面有些会比较容易查找出原因,而有些会非常困难,下面是来看一些OOM的Example。
     Example 1
    以-Xms20m -Xmx20m -Xmn10m -XX:+UseParallelGC参数运行com.bluedavy.oom.JavaHeapSpaceCase1
    运行后在输出的日志中可看到大量的Full GC信息,以及:
    java.lang.OutOfMemoryError: GC overhead limit exceeded
    和java.lang.OutOfMemoryError: Java heap space
    根据上面所说的OOM种类,可以知道这是在new对象或数组时Java Heap Space不足造成的,对于这种OOM,需要知道的是程序中哪些部分占用了Java Heap。
    要知道程序中哪些部分占用了Java Heap,首先必须拿到Java Heap中的信息,尤其是OOM时的内存信息,在Sun JDK中可通过在启动参数上加上-XX:+ HeapDumpOnOutOfMemoryError来获得OOM时的Java Heap中的信息,当出现OOM时,会自动生成一个java_pid[pid].hprof的文件。
    于是在启动参数上加上上面的参数,再次运行JavaHeapSpaceCase1,可看到在当前运行的路径下生成了一个java_pid10852.hprof(由于pid不同,你看到的可能是不一样的文件名)的文件,在拿到这个文件后,就可通过mat(http://www.eclipse.org/mat/)来进行分析了。
    用mat打开上面的文件(默认情况下mat认为heap dump文件应以.bin结尾,因此请改名或以open file方式打开),打开后点击dominator_tree,可看到sun.misc.Launcher$AppClassLoader占据了大部分的内存,继续点开看,则可看到是由于com.bluedavy.oom.Caches中有一个ArrayList,其中存放的对象占据了大部分的内存,因此解决这个OOM的办法是,让Caches类中放的对象总大小是有限制的,或者限制放入Caches的ArrayList中的对象个数。
    这种情况造成的OOM,在实际的场景中当使用缓存时很容易产生,对于所有的缓存、集合大小都应给定限制的最大大小,以避免出现缓存或集合太大,导致消耗了过多的内存,从而导致OOM。

     Example 2
    以-Xms20m -Xmx20m -Xmn10m -XX:+HeapDumpOnOutOfMemoryError执行com.bluedavy.oom.JavaHeapSpaceCase2
    运行后在输出的日志中可看到大量的Full GC和java.lang.OutOfMemoryError: Java heap space。
    同样,首先用mat打开需要分析的hprof文件,很惊讶的发现什么都看不出来,Java Heap Space还很充足,这就奇怪了,还好除了能在OOM时自动dump出Heap的信息外,还可通过jmap命令手工dump,于是,在运行期出现频繁Full GC、OOM的时候,手工通过jmap –dump:file=heap.bin,format=b [pid]生成一个heap.bin文件,把这个heap.bin文件也拿到mat中分析,杯具,还是什么都看不出来,还好,还有一招,就是直接用jmap –histo看看到底什么对象占用了大多数的内存,执行一次,看到[I占用了最多的内存,没用,因为没法知道这个[I到底是代码中哪个部分创建的,不甘心,多执行几次,很幸运,突然看到com.bluedavy.oom.Case2Object占据了最大的内存,于是查找代码中哪些地方创建了这个对象,发现了代码中有一个线程创建了大量的Case2Object,修改即可。
    从这个例子中,可以看到,在分析OOM问题时,一方面是依赖OOM时dump出来的文件,但这个文件其实只会在Java进程中第一次出现OOM时生成,之后再OOM就不会生成了,这有可能出现真实的OOM的原因被假的OOM原因给掩盖掉;另一方面是依赖在出现OOM时的人工操作,这种人肉方式其实比较杯具,因为只能先等到频繁Full GC、OOM,首先通过jmap –histo来看看到底什么对象占用了大部分的内存(需要多执行几次,以确保正确性),上面的例子比较幸运,因为刚好是自定义的对象,如果是原生的类型,那就只能借助dump文件来分析了,通过jmap –dump手工dump出Heap文件,然后用MAT分析,但有可能会出现上面例子中的状况,就是mat分析上也看不出什么,顶多只能看到unreachable objects里某些对象占用了大部分的内存,而通常情况看到的可能都是原生类型,一旦真的碰到jmap –histo看到的是原生类型占用较多,jmap dump看到的是Java Heap Space也不满的话,那只能说杯具了,在这种情况下,唯一能做的是捕捉所有的异常,然后打印,从而判断OOM是在哪行代码抛出的。

     Example 3
    以-Xms20m -Xmx20m -Xmn10m -XX:+HeapDumpOnOutOfMemoryError执行com.bluedavy.oom.JavaHeapSpaceCase3
    在控制台中可看到大量的java.lang.OutOfMemoryError: Java heap space,把生成的hprof文件放入MAT中进行分析,还好看到确实是Java Heap Space满了,这就好办了,点开Dominator Tree视图,可看到有一堆的线程,每个都占用了一定的内存,从而导致了OOM,要解决这个例子中的OOM,有四种办法:一是减少处理的线程数;二是处理线程需要消耗的内存;三是提升线程的处理速度;四是增加机器,减少单台机器所需承担的请求量。
    上面这种状况在系统处理慢的时候比较容易出现。

     Example 4
    以-Xms20m -Xmx20m -Xmn10m -XX:+HeapDumpOnOutOfMemoryError执行com.bluedavy.oom.JavaHeapSpaceCase4
    在控制台可看到大量的java.lang.OutOfMemoryError: Java heap space,把生成的hprof文件放入MAT中进行分析,可看到TaskExecutor中的handlers占据了大量的内存,分析代码,发现是由于在task处理完后没有清除掉对应的handler造成的,修改即可解决此OOM问题。
    这是个典型的内存泄露的例子,如果不小心持有了本应释放引用的对象,那么就会造成内存泄露,这是在编写Java程序时需要特别注意的地方。

     Example 5
    以-Xms1536m -Xmx1536m -Xss100m执行com.bluedavy.oom.CannotCreateThreadCase
    在控制台可看到java.lang.OutOfMemoryError: unable to create new native thread。
    对于这种情况,一需要查看目前-Xss的大小,例如在这个例子中-Xss太大,导致连20个线程都无法创建,因此可解决的方法是降低-Xss的大小;如果Xss使用的是默认值,那么可通过jstack来查看下目前Java进程中是不是创建了过多的线程,或者是java heap太大,导致os没剩多少内存,从而创建不出线程。

     Example 6
    Out of swap的例子实在太难举了,就没在此列出了,对于Out of swap的OOM,需要首先观察是否因为Java Heap设置太大,导致物理内存+swap区不够用;如果不是的话,则需关注到底是哪些地方占用了堆外的内存,可通过google-perftools来跟踪是哪些代码在调用malloc,以及所耗的内存比例,在跟踪到后可继续查找原因。
    总体来说,Out of swap通常是最难查的OOM,由于其是直接退出java进程的,因此需要结合core dump文件和hs_err_pid[pid].log进行分析,最关键的还是像查java heap那样,要查出到底是什么代码造成了native heap的消耗。

     Example 7
    PermGen空间满造成OOM的情况通常采取的解决方法是简单的扩大PermSize。

    总结上面的例子来看,对于OOM的情况,最重要的是根据OOM的种类查找到底是代码中的什么部分造成的消耗。

    对于Java Heap Space OOM和GC overhead limit exceeded这两种类型,可通过heap dump文件以及jmap –histo来进行分析,多数情况下可通过heap dump分析出原因,但也有少数情况会出现heap dump分析不出原因,而jmap –histo看到某原生类型占据了大部分的内存,这种情况就非常复杂了,只能是仔细查看代码,并捕捉OutOfMemoryError,从而来逐渐定位到代码中哪部分抛出的。

    对于Out of swap这种类型,其主要是地址空间超过了限制或者对外内存不够用了造成的,首先需要查看Java Heap设置是否过大,然后可结合google-perftools来查看到底是哪些代码调用了malloc,在堆外分配内存。

    3 Sun JDK代码中处理OOM的方式
    在Sun JDK代码中,在不同的OOM时,会调用不同的处理方式来进行处理,下面就来看看JDK中几个典型的处理OOM的代码。
     创建线程失败
    compiler_thread = new CompilerThread(queue, counters);
    if (compiler_thread == NULL || compiler_thread->osthread() == NULL){
    vm_exit_during_initialization(“java.lang.OutOfMemoryError”,
    “unable to create new native thread”);
    }
    对于JDK中必须创建成功的线程,如失败会通过调用vm_exit_during_initialization打印出OOM错误,并退出Java进程。
    对于非必须创建成功的线程,通常会调用
    THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
    “unable to create new native thread”);
    抛出OOM错误信息。

     调用os:malloc失败
    void *p = os::malloc(bytes);
    if (p == NULL)
    vm_exit_out_of_memory(bytes, “Chunk::new”);
    return p;
    当os:malloc或os:commit_memory失败时,会直接输出错误信息,并退出Java进程。

     Java Heap上分配失败后
    report_java_out_of_memory(“Java heap space”);
    调用这个就表明了不会退出vm,而只是抛出OOM错误。
    例如PermGen分配失败时的代码更为直观:
    HeapWord* result = Universe::heap()->permanent_mem_allocate(size);
    if (result != NULL) {
    NOT_PRODUCT(Universe::heap()->
    check_for_non_bad_heap_word_value(result, size));
    assert(!HAS_PENDING_EXCEPTION,
    “Unexpected exception, will result in uninitialized storage”);
    return result;
    }
    // -XX:+HeapDumpOnOutOfMemoryError and -XX:OnOutOfMemoryError support
    report_java_out_of_memory(“PermGen space”);

    总体而言,对于一个大型系统而言,通常OOM是难以避免的现象,最重要的还是一旦出现OOM,要掌握排查的方法,另外就是,随着现在内存越来越便宜,CMS GC越来越成熟,采用64 bit操作系统,开启大内存也是一种可选方式,基本上可以避免内存成为大问题,毕竟在Java中完全可能随便写几行代码就不知不觉消耗了很多内存。

    ps: 感兴趣的同学还可参考sun官方的这篇关于OOM的文章:
    http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/memleaks.html

    杭州程序员圆桌交流第三期:GC Tuning Case

    在这次的交流中我分享了两个gc tuning的case,内容已纳入到了我blog上GC的PPT中,之后会更新此PPT,先公布视频的下载,感兴趣的同学可下载:

    视频part I

    视频part II

    视频part III

    在这次的交流中我分享了两个gc tuning的case,内容已纳入到了我blog上GC的PPT中,之后会更新此PPT,先公布视频的下载,感兴趣的同学可下载:

    视频part I

    视频part II

    视频part III