1、這家公司有一個數據庫代理程序,用于數據庫服務器的代理,游戲服務器執(zhí)行sql指令,DBAgent接受此指令,執(zhí)行一些組織后,調用JDBC執(zhí)行數據庫操作,然后將結果返回。

2、發(fā)生的問題:內存一直升高,處理客戶端請求的線程并不多(高峰期大概300左右吧),數據庫上的連接數也不多(100的樣子)。運行5-6天,基本上內存就用完了,而且得不到數據庫的連接。他們非常急,我就試著接下這個項目。

3、接到這個優(yōu)化項目,查看了他們的部分代碼。發(fā)現連接池寫得有些問題,得不到數據庫連接后wait,但是不會接到任何有效的notify,也就是說只要一等待就會超時。還有其他的問題,一開始我以為是這個問題,修改后讓他們去跑,結果還是一樣,內存上去后一直下不來,最后崩潰。

4、通過這個發(fā)現就是內存泄露了。一開始用jprofiler測試,發(fā)現內存上去后就Out of memory了,而且hashmap和long[]的對象非常多一直下不去。但是找不到這些對象是怎么產生的。折磨了我好幾天,還請教了很多人,都得不到答案。后來發(fā)現是java啟動參數中內存參數設置的太低了,本來需要100多M的內存,你就設置給16M,不崩潰才怪呢。于是改成了128M。結果內存上去之后,到達一個峰值就下來了,然后震蕩,但一直就沒有上去。那程序沒有泄露?可是生產上怎么泄露了呢?

5、就在我基本上要放棄的時候,我想到了測試環(huán)境可能真實環(huán)境不同,有必要看一下他們生產服務器上虛擬機的運行情況。記得Java有自帶的工具查詢java虛擬機運行情況的。jmap這個工具可以查看jvm中運行實例的個數以及實例的類名。讓他們的人用了下這個工具,將結果發(fā)給我了,我一看,嚇了一大跳。排在第一位的是int[],竟然達到了1G。最有問題的是com.mysql.jdbc.PreparedStatement對象,達到了6萬多。com.mysql.jdbc.ResultSetImpl和java.util.HashMap$Entry[]也達到了6萬多。不用說,肯定是PreparedStatement沒有關閉。

6、查看源代碼,發(fā)現PreparedStatement對象都在finally塊中關閉了,怎么會泄露呢?找了1個小時沒有找到,就去洗澡了。在洗澡的時候突然想到,里面有一個for循環(huán),PreparedStatement對象可能被賦值N次,那前面的N-1次不就沒有關閉啊,對,找到答案了。趕緊擦干身子出來找到那段代碼:

 1  String[] valuesArray = value.split(";");
 2  for (int i = 0 ;i < valuesArray.length;i++){
 3 
 4 String[] valueArray = valuesArray[i].split(",");
10                     ps = conn.prepareStatement(sqlbean.getSql());
11                     for(int k = 0;k <valueArray.length;k++){
12                         if("s".equalsIgnoreCase(paraTypeArray[k])){
13                             ps.setString(k+1,valueArray[k]);
14                         }else if("i".equalsIgnoreCase(paraTypeArray[k])){
15                             ps.setInt(k+1,Integer.parseInt(valueArray[k]));
16                         }
17                     }
18 
19                     rsString = "" + ps.executeUpdate();
20 
21 }


確實如此,循環(huán)的N-1個PreparedStatement對象沒有關閉,導致了泄漏。解決辦法就是將
ps = conn.prepareStatement(sqlbean.getSql());

移到for循環(huán)的外面,這樣就沒有問題了。不過從這段代碼也可以看出,寫得也是在是爛,這個干嗎放到
循環(huán)的里面呢,本身從語言上來說就有問題。管它呢,解決問題就行了,呵呵。

幾乎興奮了一個晚上。第二天找他們的人一說,他們說是循環(huán)N次的,不只是一個值。問題不就解決了?Great。

7、讓他們去測試運行吧。運行第一天的晚上九點(這是高峰期)以后,內存非常平穩(wěn)。問題徹底解決了。

總結這次的優(yōu)化項目:
對Java虛擬機的認識提高了。對java性能測試工具(JProfiler)更加熟練了,可以和eclipse集成呢,非常方便。