前段時間公司打造了自己的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();
}
結果,呵呵,不用多說了..................................