在產(chǎn)品中有碰到過使用LinkedBlockingQueue.poll時(shí)超時(shí)很不準(zhǔn)的現(xiàn)象,關(guān)鍵是這不是一般的不準(zhǔn),如果只是一點(diǎn)點(diǎn)不準(zhǔn)的話也就勉強(qiáng)接受了,例如指定poll的超時(shí)時(shí)間為100ms,但最終執(zhí)行.poll這段代碼就花費(fèi)了8000ms的現(xiàn)象,這篇blog就是展示下通過一段小小的代碼來重現(xiàn)這樣的現(xiàn)象,畢竟沒有重現(xiàn)是無法證明為什么會(huì)出現(xiàn)這樣的現(xiàn)象的。
由于在出現(xiàn)這個(gè)現(xiàn)象的時(shí)候有看到過OutOfMemory的錯(cuò),雖然java進(jìn)程沒退出,但猜想可能是這個(gè)原因造成的,于是寫了下面這段代碼:
public static void main(String[] args) throws Exception{
long beginTime=System.currentTimeMillis();
Map<String, byte[]> cache=new HashMap<String, byte[]>();
for (int i = 0; i < 1000000; i++) {
cache.put(String.valueOf(i),new byte[500]);
}
CountDownLatch latch=new CountDownLatch(25);
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
Thread thread=new Thread(new TestThread(latch));
thread.start();
}
}
latch.await();
long endTime=System.currentTimeMillis();
System.out.println("總共執(zhí)行時(shí)間:"+(endTime-beginTime));
}
static class TestThread implements Runnable{
private CountDownLatch latch;
public TestThread(CountDownLatch latch){
this.latch=latch;
}
public void run(){
BlockingQueue<String> queue=new LinkedBlockingQueue<String>(1);
try{
for (int i = 0; i < 5; i++) {
long beginTime=System.currentTimeMillis();
queue.poll(100, TimeUnit.MILLISECONDS);
long endTime=System.currentTimeMillis();
long consumeTime=endTime-beginTime;
if(consumeTime>200)
System.out.println("獲取queue的時(shí)間為:"+consumeTime);
@SuppressWarnings("unused")
byte[] bytesNew=new byte[25506000];
}
}
catch(Exception e){
;
}
latch.countDown();
}
}
啟動(dòng)上面程序時(shí),將jvm參數(shù)設(shè)置為-Xms640M -Xmx640M,運(yùn)行后,應(yīng)該會(huì)看到有很多queue.poll執(zhí)行超過200ms的現(xiàn)象,甚至?xí)霈F(xiàn)8000ms的現(xiàn)象。
上面整段程序的寫法就是用大量的小對象占據(jù)old generation,然后啟動(dòng)多個(gè)線程,每個(gè)線程運(yùn)行的時(shí)候new一個(gè)大的對象,讓其產(chǎn)生有可能直接從new generation分配到old generation,從而導(dǎo)致Full GC頻繁執(zhí)行,里面的byte數(shù)組的大小的原則為:塞滿內(nèi)存,產(chǎn)生Full GC,但又盡量不讓應(yīng)用出現(xiàn)OutOfMemory,或者說不出現(xiàn)過于頻繁的OutOfMemory,避免jvm crash。
從上面程序的運(yùn)行效果來看,當(dāng)jvm內(nèi)存消耗的很嚴(yán)重的情況下,poll的超時(shí)是沒法準(zhǔn)的,其實(shí)分析下原因,還是挺正常的:
1、jvm內(nèi)存消耗嚴(yán)重的情況下,Minor GC和Full GC瘋狂執(zhí)行,導(dǎo)致了應(yīng)用的執(zhí)行不斷的被暫停,因此當(dāng)線程已經(jīng)進(jìn)入poll等待后,這個(gè)線程沒有機(jī)會(huì)被執(zhí)行,導(dǎo)致當(dāng)其有機(jī)會(huì)執(zhí)行時(shí),早就超過了指定的超時(shí)時(shí)間,因此其超時(shí)不準(zhǔn)并不是本身的機(jī)制導(dǎo)致的,而是jvm GC造成的多次暫停;
2、多次暫停的情況下,jvm需要不斷的調(diào)度恢復(fù)線程,這需要消耗很多的資源,而且切換多了Swap空間很容易不夠用,最終導(dǎo)致jvm crash。