一個效能瓶頸分析的過程。

來源:互聯網
上載者:User

前段時間公司打造了自己的WEB開發架構新版,效能比以前的兩個版有很大提高。在效能基準測試時,某個測試的業務情境為

18000個TPS左右。

但是後來增加了session序列化模組後,一下子下降低到6000個TPS左右,就是因為這個模組效能一下子降低三倍。

 

jvisualvm監視查看到其中的加密方法佔用了47%的CPU處理時間,於是重點測試這個方法。

 

這個方法做了三件事。

1.加密,

2.壓縮,

3.base64編碼成可見字元

 

加密過程雖然消耗了一定的系統資源,但經百萬次迴圈測試效能是可以接受的。

BASE64這個可以放心,測試過N次了。

於是測試壓縮演算法,單線程1萬次迴圈時耗時在秒級,我想1百萬次也就應該是兩三分鐘吧。

結果一跑以後,系統差點死掉,響應非常慢,看看後台非JAVA線程在十幾秒內竟然一下子吃掉10G的記憶體,大量的磁碟交換產生。

不用說,肯定是有記憶體流失,生產系統中是因為採用CMS策略來進行GC的。因為不斷進行回收,所以不會出現瞬間記憶體都被吃光的

情況。但是對於依賴finalize來做回收的情況,雖然對象最終會被回收,但利用率大大地打了下降了。

 

這是最簡單的道理,就比如一個串連,在finalize中調用了close()方法,你即使不手工調用 close()方法它最後也被回收了,但它

利用率卻下降低了好多倍,假設系統只允許1個串連,從產生到JVM回收它的過程是1秒,那麼在1秒內如果你每次只使用100ms,然後手工close(),那麼其它線程有9次機會重新產生串連,而如果利用finalize在1秒內只有一次使用機會,這還是因為CMS策略的GC,如果是

暫停式GC策略,利用率將打更大的折扣。

 

所以最終關注壓縮的代碼,其實只有三行:

 

DeflaterOutputStream dos = new DeflaterOutputStream(byteArrayOutputStream,new Deflater(Deflater.BEST_ COMPRESSION,false));

ObjectOutputStream out = new ObjectOutputStream (dos);

out.write(加密資料);

byte[] data = byteArrayOutputStream.toByteArray();

//close等收尾工作。

對於輸出資料流這種常規操作,打造平台的大牛們不可能犯低級錯誤,而大量的記憶體流失應該和壓縮參數無關,於是直接分析

Deflater類(因為之前大多數是直接使用GZipOutputStream,對Deflater類本身並不是很熟悉)。發現文檔中有對於end方法的說明是:

end

public void end()

    關閉解壓縮器並放棄所有未處理的輸入。此方法應該在不再使用該壓縮器時調用,但是也可以由 finalize() 方法自動調用。調用此方法後,Deflater 對象的行為將是不確定的。

 

其實jdk自己的例子就給人一個誤導:

 

// Encode a String into bytes<br /> String inputString = "blahblahblah??";<br /> byte[] input = inputString.getBytes("UTF-8");<br /> // Compress the bytes<br /> byte[] output = new byte[100];<br /> Deflater compresser = new Deflater();<br /> compresser.setInput(input);<br /> compresser.finish();<br /> int compressedDataLength = compresser.deflate(output);<br /> // Decompress the bytes<br /> Inflater decompresser = new Inflater();<br /> decompresser.setInput(output, 0, compressedDataLength);<br /> byte[] result = new byte[100];<br /> int resultLength = decompresser.inflate(result);<br /> decompresser.end();<br /> // Decode the bytes into a String<br /> String outputString = new String(result, 0, resultLength, "UTF-8");<br />  

在這個例子中,壓縮的代碼並沒有調用compresser.end,因為僅調用一次,最後肯定會在對象被回收時調用finalize來調用end();

這在偶爾調用一兩次的情況下也沒有大問題。

 

另外,對於DeflaterOutputStream構造方法中,如果你沒有傳入Deflater,它自己new了一個Deflater,並使用useDefaultDeflater標記來

在close()中調用Deflater的end(),但如果是你自己傳入的Deflater,因為可能在外部會多次複用,本著誰生產誰負責的原則,它沒有為你調用

end()。

 

凡是在finalize中調用方法說明一定要保證被回收,而在密集調情況下一定不能依賴finalize,上面已經說過道理。所以一定要在用完後立即調用它以“儘早地立即回收”。所以這裡應該有一個手工調用的參與,於是修改為:

 

Deflater def = new Deflater(Deflater.BEST_ COMPRESSION,false);

DeflaterOutputStream dos = new DeflaterOutputStream(byteArrayOutputStream,def);

 

ObjectOutputStream out = new ObjectOutputStream (dos);

out.write(加密資料);

byte[] data = byteArrayOutputStream.toByteArray();

在finally語句中執行{

def.end();

close();

}

 

結果,呵呵,不用多說了..................................

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.