java效能調優(轉載)
阿新 • • 發佈:2018-12-30
1.用new關鍵詞建立類的例項時,建構函式鏈中的所有建構函式都會被自動呼叫。但如果一個物件實現了Cloneable介面,我們可以呼叫它的clone()方法。clone()方法不會呼叫任何類建構函式。
在使用設計模式(Design Pattern)的場合,如果用Factory模式建立物件,則改用clone()方法建立新的物件例項非常簡單。例如,下面是Factory模式的一個典型實現:
public static Credit getNewCredit() {return new Credit();} 改進後的程式碼使用clone()方法,如下所示:private static Credit BaseCredit = new Credit();public static Credit getNewCredit() {return (Credit) BaseCredit.clone();}
面的思路對於陣列處理同樣很有用。
2. 使用非阻塞I/O
版本較低的JDK不支援非阻塞I/O API。為避免I/O阻塞,一些應用採用了建立大量執行緒的辦法(在較好的情況下,會使用一個緩衝池)。這種技術可以在許多必須支援併發I/O流的應用中見到,如Web伺服器、報價和拍賣應用等。然而,建立Java執行緒需要相當可觀的開銷。
3. 慎用異常
異常對效能不利。丟擲異常首先要建立一個新的物件。Throwable介面的建構函式呼叫名為fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法檢查堆疊,收集呼叫跟蹤資訊。只要有異常被丟擲,VM就必須調整呼叫堆疊,因為在處理過程中建立了一個新的物件。異常只能用於錯誤處理,不應該用來控制程式流程。
4. 不要重複初始化變數
預設情況下,呼叫類的建構函式時, Java會把變數初始化成確定的值:所有的物件被設定成null,整數變數(byte、short、int、long)設定成0,float和double變數設定成0.0,邏輯值設定成false。當一個類從另一個類派生時,這一點尤其應該注意,因為用new關鍵詞建立一個物件時,建構函式鏈中的所有建構函式都會被自動呼叫。
5. 儘量指定類的final修飾符
帶有final修飾符的類是不可派生的。在Java核心API中,有許多應用final的例子,例如java.lang.String。為String類指定final防止了人們覆蓋length()方法。另外,如果指定一個類為final,則該類所有的方法都是final。Java編譯器會尋找機會內聯(inline)所有的final方法(這和具體的編譯器實現有關)。此舉能夠使效能平均提高50%。
6. 儘量使用區域性變數
呼叫方法時傳遞的引數以及在呼叫中建立的臨時變數都儲存在棧(Stack)中,速度較快。其他變數,如靜態變數、例項變數等,都在堆(Heap)中建立,速度較慢。另外,依賴於具體的編譯器/JVM,區域性變數還可能得到進一步優化。
7. 乘法和除法
考慮下面的程式碼:
for (val = 0; val < 100000; val +=5) { alterX = val * 8; myResult = val * 2; } 用移位操作替代乘法操作可以極大地提高效能。下面是修改後的程式碼:for (val = 0; val < 100000; val += 5) { alterX = val << 3; myResult = val << 1; }
修改後的程式碼不再做乘以8的操作,而是改用等價的左移3位操作,每左移1位相當於乘以2。相應地,右移1位操作相當於除以2。值得一提的是,雖然移位操作速度快,但可能使程式碼比較難於理解,所以最好加上一些註釋。
3. 選擇合適的引用機制
在典型的JSP應用系統中,頁頭、頁尾部分往往被抽取出來,然後根據需要引入頁頭、頁尾。當前,在JSP頁面中引入外部資源的方法主要有兩種:include指令,以及include動作。
include指令:例如
<%@ include file="copyright.html" %>
該指令在編譯時引入指定的資源。在編譯之前,帶有include指令的頁面和指定的資源被合併成一個檔案。被引用的外部資源在編譯時就確定,比執行時才確定資源更高效。
include動作:例如
<jsp:include page="copyright.jsp" />
該動作引入指定頁面執行後生成的結果。由於它在執行時完成,因此對輸出結果的控制更加靈活。但時,只有當被引用的內容頻繁地改變時,或者在對主頁面的請求沒有出現之前,被引用的頁面無法確定時,使用include動作才合算。
4. 在spring中對orm層的動作設定只讀屬性
將 (只對資料庫進行讀取的操作) 設定只讀屬性
10. Servlet與記憶體使用
許多開發者隨意地把大量資訊儲存到使用者會話之中。一些時候,儲存在會話中的物件沒有及時地被垃圾回收機制回收。從效能上看,典型的症狀是使用者感到系統週期性地變慢,卻又不能把原因歸於任何一個具體的元件。如果監視JVM的堆空間,它的表現是記憶體佔用不正常地大起大落。解決這類記憶體問題主要有二種辦法。第一種辦法是,在所有作用範圍為會話的Bean中實現HttpSessionBindingListener介面。這樣,只要實現valueUnbound()方法,就可以顯式地釋放Bean使用的資源。
另外一種辦法就是儘快地把會話作廢。大多數應用伺服器都有設定會話作廢間隔時間的選項。另外,也可以用程式設計的方式呼叫會話的setMaxInactiveInterval()方法,該方法用來設定在作廢會話之前,Servlet容器允許的客戶請求的最大間隔時間,以秒計算。
11. HTTP Keep-Alive
Keep-Alive功能使客戶端到伺服器端的連線持續有效,當出現對伺服器的後繼請求時,Keep-Alive功能避免了建立或者重新建立連線。市場上的大部分Web伺服器,包括iPlanet、IIS和Apache,都支援HTTP Keep-Alive。對於提供靜態內容的網站來說,這個功能通常很有用。但是,對於負擔較重的網站來說,這裡存在另外一個問題:雖然為客戶保留開啟的連線有一定的好處,但它同樣影響了效能,因為在處理暫停期間,本來可以釋放的資源仍舊被佔用。當Web伺服器和應用伺服器在同一臺機器上執行時,Keep-Alive功能對資源利用的影響尤其突出。
12. JDBC與Unicode
想必你已經瞭解一些使用JDBC時提高效能的措施,比如利用連線池、正確地選擇儲存過程和直接執行的SQL、從結果集刪除多餘的列、預先編譯SQL語句,等等。除了這些顯而易見的選擇之外,另一個提高效能的好選擇可能就是把所有的字元資料都儲存為Unicode(內碼表13488)。Java以Unicode形式處理所有資料,因此,資料庫驅動程式不必再執行轉換過程。但應該記住:如果採用這種方式,資料庫會變得更大,因為每個Unicode字元需要2個位元組儲存空間。另外,如果有其他非Unicode的程式訪問資料庫,效能問題仍舊會出現,因為這時資料庫驅動程式仍舊必須執行轉換過程。
13. JDBC與I/O
如果應用程式需要訪問一個規模很大的資料集,則應當考慮使用塊提取方式。預設情況下,JDBC每次提取32行資料。舉例來說,假設我們要遍歷一個5000行的記錄集,JDBC必須呼叫資料庫157次才能提取到全部資料。如果把塊大小改成512,則呼叫資料庫的次數將減少到10次。在一些情形下這種技術無效。例如,如果使用可滾動的記錄集,或者在查詢中指定了FOR UPDATE,則塊操作方式不再有效。
Java程式碼優化--儘可能地使用stack(棧)變數(方法內部的區域性變數)
Java程式包含了大量的物件,我們需要了解它們是從哪裡被訪問的,變數儲存於何處對程式的效能有顯著的影響--尤其是某些需要被頻繁訪問的變數。
我們寫一個Java類,在其內部方法中定義的區域性變數或物件是儲存在stack(堆疊)中的,且JVM是一種stack-based的,因此訪問和操縱stack中的資料時效能最佳。而Java類的instance變數(這個類的field)和static變數是在constant pool(常量池)中儲存和得到訪問的。constant pool中儲存了所有的符號引用(symbolic references),指向所有型別(types)、值域(field),以及每個型別所使用的所有函式(mothods)。訪問instance和static變數時,由於它們存放於constant pool中,所以JVM需要使用更多更耗時的操作碼(分析程式生成的bytecode可以看出來)來訪問它們。
下面給出一段程式碼示例,對比後說明怎麼儘可能地使用stack變數:
package test;
public class StackVars {
private int x; // instance變數
private static int staticX; //static 變數
public void stackAccess(int val) { //訪問和操作stack變數j
int j = 0;
for (int i = 0; i < val; i++) {
j += 1;
}
}
public void instanceAccess(int val) {//訪問和操作instance變數x
for (int i = 0; i < val; i++) {
x += 1;
}
}
public void staticAccess(int val) {//訪問和操作static變數staticX
for (int i = 0; i < val; i++) {
staticX += 1;
}
}
}
經測試,發現執行instanceAccess()和staticAccess()方法的時間大約相同,但卻比執行stackAccess()方法慢了2~3倍。因此我們對instanceAccess()、staticAccess()兩個方法的程式碼作以下調整,以得到更快的效能:
public void instanceAccess(int val) {//訪問和操作instance變數x
int tempX=x;
for (int i = 0; i < val; i++) {
tempX += 1;
}
x=tempX;
}
public void staticAccess(int val) {//訪問和操作static變數staticX
int tempStaticX=staticX;
for (int i = 0; i < val; i++) {
tempStaticX += 1;
}
staticX=tempStaticX;
}
改善之處就是將instance和static變數放到迴圈之外,而用一個stack變數來完成多次區域性運算,最後再將這個stack變數的值傳回instance或static變數,從而提高了程式碼的效能。
Sun JDK自帶JVM記憶體使用分析工具HProf
使用Sun JDK自帶JVM記憶體使用分析工具HProf可以分析JVM堆疊,從而找到佔用記憶體較大的物件。這對應經常出現記憶體洩漏(OOM)的JAVA系統進行調優很有幫助。
HProf使用方法
• 在WeblogicServer啟動指令碼中增加-Xrunhprof:heap=sites,重新啟動WeblogicServer。
• 使用kill -3 <pid> 或退出WeblogicServer均會生成java.hprof.txt檔案,直接開啟此檔案便可分析JVM的具體執行情況。
從java.hprof.txt記錄的JVM堆疊結果中可以發現JVM佔用記憶體較大的物件:
percent live alloc'ed stack class
rank self accum bytes objs bytes objs trace name
1 4.57% 4.57% 2289696 47702 8392224 174838 4251 [C
2 3.99% 8.57% 2000016 1 2000016 1 12308 [C
3 3.65% 12.22% 1827552 9622 1852672 10082 43265 [C
4 2.58% 14.80% 1293912 53913 3929424 163726 4258 java.lang.String
5 2.05% 16.85% 1028664 7585 3207272 24923 4252 [C
6 2.03% 18.88% 1015816 159 1015816 159 18694 [B
7 1.88% 20.77% 942080 230 2740224 669 20416 [B
8 1.61% 22.37% 805752 2142 2150856 4635 45318 [B
9 1.60% 23.98% 802880 772 802880 772 24710 weblogic.servlet.utils.URLMatchMap$URLMatchNode
10 1.60% 25.57% 799400 19985 2781400 69535 45073 cnc.util.Field
11 1.36% 26.93% 679360 3805 679360 3805 494 [B
12 1.35% 28.28% 674856 28119 5181240 215885 2985 java.util.HashMap$Entry
……
……
96 0.19% 63.73% 94776 3112 94776 3112 9146 [C
97 0.19% 63.92% 93456 3894 123936 5164 23631 java.lang.String
98 0.19% 64.10% 93224 3884 123968 5165 23644 java.lang.String
99 0.19% 64.29% 93192 3883 123936 5164 23636 java.lang.String
100 0.18% 64.47% 89528 238 240264 520 33227 [B
101 0.17% 64.64% 86448 1901 103472 2255 18715 java.lang.Object
102 0.17% 64.81% 85464 676 85768 695 18715 [S
103 0.17% 64.98% 85184 1331 85184 1331 28266 weblogic.ejb20.internal.MethodDescriptor
104 0.17% 65.15% 84224 752 84224 752 24148 weblogic.servlet.internal.dd.ServletDescriptor
105 0.17% 65.32% 84136 528 50471136 348769 63 [C
106 0.16% 65.48% 79968 1428 388976 6946 5503 java.lang.reflect.Method
107 0.15% 65.63% 77520 1615 77520 1615 27967 weblogic.ejb20.deployer.mbimpl.MethodInfoImpl
108 0.15% 65.79% 77056 4816 469808 29363 20250 java.lang.Object
109 0.15% 65.94% 76960 74 76960 74 23695 [B
110 0.15% 66.09% 76104 3171 215040 8960 45071 cnc.util.FyCol
111 0.15% 66.24% 74688 3112 74688 3112 9152 java.util.Hashtable$Entry
112 0.15% 66.39% 74688 3112 74688 3112 9147 java.lang.String
113 0.15% 66.54% 74280 61 794328 788 45313 [C
114 0.14% 66.68% 72480 1510 436032 9084 45353 [C
115 0.14% 66.82% 70720 68 70720 68 25869 [B
116 0.14% 66.97% 70720 68 70720 68 27448 [B
117 0.14% 67.11% 70272 1279 142672 2439 5503 [C
118 0.14% 67.24% 69256 86 69256 86 6584 [S
119 0.13% 67.38% 67056 66 67056 66 28882 java.lang.Object
120 0.13% 67.51% 66176 752 66176 752 24170 weblogic.servlet.internal.dd.UIDescriptor
121 0.13% 67.64% 65688 715 65688 715 25389 [C
122 0.13% 67.77% 65600 4 885600 54 23939 [C
123 0.13% 67.90% 65600 4 623200 38 40639 [C
124 0.13% 68.03% 65576 367 65576 367 51686 [C
125 0.13% 68.17% 65568 2 65568 2 30610 java.util.HashMap$Entry
126 0.13% 68.30% 65568 2 130816 16 43271 java.util.HashMap$Entry
127 0.13% 68.43% 65552 1 65552 1 16617 [B
128 0.13% 68.56% 64600 1615 64600 1615 27969 java.util.HashMap
129 0.13% 68.68% 63888 2662 64032 2668 16951 java.util.HashMap$Entry
130 0.13% 68.81% 63888 2662 64032 2668 16997 java.util.HashMap$Entry
131 0.13% 68.94% 63888 2662 64032 2668 16996 weblogic.rmi.internal.ClientMethodDescriptor
132 0.13% 69.07% 63888 2662 99120 4130 16949 java.lang.String
133 0.13% 69.19% 63888 2662 64032 2668 16976 java.lang.String
134 0.13% 69.32% 63232 152 63232 152 9655 weblogic.utils.collections.ConcurrentHashMap$Entry
135 0.13% 69.45% 63232 152 63232 152 9704 weblogic.utils.collections.ConcurrentHashMap$Entry
136 0.12% 69.57% 62168 3885 82632 5164 23628 [B
137 0.12% 69.69% 61680 406 66904 468 1 [C
138 0.12% 69.82% 61504 4 246016 16 47372 [B
139 0.12% 69.94% 61144 36 91019160 23904 92 [B
140 0.12% 70.06% 61040 763 61040 763 24194 weblogic.servlet.internal.dd.ServletMappingDescriptor
141 0.12% 70.18% 60400 1510 363360 9084 45338 java.util.Hashtable
142 0.12% 70.30% 59544 827 59544 827 24746 weblogic.servlet.internal.ServletRuntimeMBeanImpl
143 0.12% 70.42% 59248 1058 484984 8664 33236 oracle.jdbc.ttc7.TTCItem
144 0.12% 70.53% 58152 232 187176 764 748 [C
145 0.12% 70.65% 57888 2412 161904 6746 16621 java.lang.String
146 0.11% 70.77% 57400 1435 57400 1435 16855 java.util.HashMap
……
……
根據以上的結果,在java.hprof.txt中定位到導致分配大記憶體的操作如下:
TRACE 63:
java.lang.StringBuffer.expandCapacity(StringBuffer.java:202)
java.lang.StringBuffer.append(StringBuffer.java:401)
java.util.zip.ZipFile.getEntry(ZipFile.java:148)
java.util.jar.JarFile.getEntry(JarFile.java:198)
TRACE 92:
java.util.zip.InflaterInputStream.<init>(InflaterInputStream.java:71)
java.util.zip.ZipFile$1.<init>(ZipFile.java:240)
java.util.zip.ZipFile.getInputStream(ZipFile.java:212)
java.util.zip.ZipFile.getInputStream(ZipFile.java:183)
再進一步分析則需要應用開發人員對應用程式碼做相應的分析定位。
注意:使用HProf非常消耗資源,切記不要在生產系統使用。
Java優化程式設計(第二版)
2.1 垃圾回收堆記憶體
記憶體管理的話題在C或C++程式設計中討論得相對較多,因為在C與C++程式設計中需要開發人員自己申請並管理記憶體,開發人員可以申請/借用(Apply)系統記憶體並且負責釋放/歸還(Release)系統記憶體,如果“只借不還”就會造成系統記憶體洩露的問題。在Java程式設計中,這些工作由Java虛擬機器(JVM)負責處理。所有記憶體的申請、分配、釋放都由JVM負責完成。因此,開發人員就省去了這部分工作,不過這並不意味著開發人員可以完全依賴於JVM的記憶體管理功能,如果你這樣想並在實際的應用開發中也這樣做,你所開發的應用的效能,就有可能不是最優的。這是因為,無論配置多麼優良的硬體環境其自身資源都是有限的,由於沒有合理、科學的使用記憶體資源,即使是Java應用也會出現記憶體枯竭的現象。例如,我們經常會遇到的OutOfMemoryException。再者Java語言(其實不只Java語言)的效能極大程度上依賴於其執行的硬體環境資源,而記憶體又是硬體環境資源中重要的一部分,因此說,如果開發人員開發的Java應用沒能有效、合理地使用系統記憶體,那麼這個應用就不可能具備較高的效能,甚至會導致整個系統在執行一段時間後崩潰。本章將對Java應用開發中與記憶體管理相關的技術做詳細的講解。
2.1 垃圾回收
談到Java記憶體管理的話題,就必然會提到垃圾回收的概念,垃圾回收的英文名稱為Garbage Collection,簡稱GC,它是Java程式設計中有關記憶體管理的核心概念,Java虛擬機器(JVM)的記憶體管理機制被稱為垃圾回收機制。因此,要想掌握在開發Java應用時怎樣才能合理地管理記憶體,首先應該瞭解Java虛擬機器的記憶體管理機制——垃圾回收機制。否則,在不瞭解垃圾回收具體實現機制的情況下討論Java程式設計中的記憶體管理,優化Java應用效能,就有些紙上談兵,捨本逐末了。
上面我們提到Java程式設計中的記憶體管理機制是通過垃圾回收來完成的,那麼在JVM執行環境中什麼樣的物件是垃圾呢?下面我們給出了在JVM執行環境中垃圾物件的定義:
一個物件建立後被放置在JVM的堆記憶體(heap)中,當永遠不再引用這個物件時,它將被JVM在堆記憶體(heap)中回收。被建立的物件不能再生,同時也沒有辦法通過程式語句釋放它們。
我們也可以這樣給JVM中的垃圾物件下定義:
當物件在JVM執行空間中無法通過根集合(root set)到達(找到)時,這個物件就被稱為垃圾物件。根集合是由類中的靜態引用域與本地引用域組成的。JVM中通過根集合索引物件如圖2-1所示。
圖2-1 JVM中通過根集合索引物件
&注意 圖2-1中打了X標記的物件就是不可到達的物件,這些物件就被JVM視為垃圾物件並被JVM回收。JVM將給這些物件打上相應的標記,然後清掃回收這些物件,並將散碎的記憶體單元收集整合。
這裡提到了堆記憶體的概念,它是JVM管理的一種記憶體型別,在做Java應用開發時經常會用到由JVM管理的兩種型別的記憶體:堆記憶體(heap)與棧記憶體(stack)。有關堆記憶體的概念,在前面的相關章節中,已經做過相應的介紹。簡單地講,堆記憶體主要用來儲存程式在執行時建立或例項化的物件與變數,例如:我們通過new MyClass()建立的類MyClass的物件。而棧記憶體(stack)則是用
在使用設計模式(Design Pattern)的場合,如果用Factory模式建立物件,則改用clone()方法建立新的物件例項非常簡單。例如,下面是Factory模式的一個典型實現:
public static Credit getNewCredit() {return new Credit();} 改進後的程式碼使用clone()方法,如下所示:private static Credit BaseCredit = new Credit();public static Credit getNewCredit() {return (Credit) BaseCredit.clone();}
面的思路對於陣列處理同樣很有用。
2. 使用非阻塞I/O
版本較低的JDK不支援非阻塞I/O API。為避免I/O阻塞,一些應用採用了建立大量執行緒的辦法(在較好的情況下,會使用一個緩衝池)。這種技術可以在許多必須支援併發I/O流的應用中見到,如Web伺服器、報價和拍賣應用等。然而,建立Java執行緒需要相當可觀的開銷。
3. 慎用異常
異常對效能不利。丟擲異常首先要建立一個新的物件。Throwable介面的建構函式呼叫名為fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法檢查堆疊,收集呼叫跟蹤資訊。只要有異常被丟擲,VM就必須調整呼叫堆疊,因為在處理過程中建立了一個新的物件。異常只能用於錯誤處理,不應該用來控制程式流程。
4. 不要重複初始化變數
預設情況下,呼叫類的建構函式時, Java會把變數初始化成確定的值:所有的物件被設定成null,整數變數(byte、short、int、long)設定成0,float和double變數設定成0.0,邏輯值設定成false。當一個類從另一個類派生時,這一點尤其應該注意,因為用new關鍵詞建立一個物件時,建構函式鏈中的所有建構函式都會被自動呼叫。
5. 儘量指定類的final修飾符
帶有final修飾符的類是不可派生的。在Java核心API中,有許多應用final的例子,例如java.lang.String。為String類指定final防止了人們覆蓋length()方法。另外,如果指定一個類為final,則該類所有的方法都是final。Java編譯器會尋找機會內聯(inline)所有的final方法(這和具體的編譯器實現有關)。此舉能夠使效能平均提高50%。
6. 儘量使用區域性變數
呼叫方法時傳遞的引數以及在呼叫中建立的臨時變數都儲存在棧(Stack)中,速度較快。其他變數,如靜態變數、例項變數等,都在堆(Heap)中建立,速度較慢。另外,依賴於具體的編譯器/JVM,區域性變數還可能得到進一步優化。
7. 乘法和除法
考慮下面的程式碼:
for (val = 0; val < 100000; val +=5) { alterX = val * 8; myResult = val * 2; } 用移位操作替代乘法操作可以極大地提高效能。下面是修改後的程式碼:for (val = 0; val < 100000; val += 5) { alterX = val << 3; myResult = val << 1; }
修改後的程式碼不再做乘以8的操作,而是改用等價的左移3位操作,每左移1位相當於乘以2。相應地,右移1位操作相當於除以2。值得一提的是,雖然移位操作速度快,但可能使程式碼比較難於理解,所以最好加上一些註釋。
3. 選擇合適的引用機制
在典型的JSP應用系統中,頁頭、頁尾部分往往被抽取出來,然後根據需要引入頁頭、頁尾。當前,在JSP頁面中引入外部資源的方法主要有兩種:include指令,以及include動作。
include指令:例如
<%@ include file="copyright.html" %>
該指令在編譯時引入指定的資源。在編譯之前,帶有include指令的頁面和指定的資源被合併成一個檔案。被引用的外部資源在編譯時就確定,比執行時才確定資源更高效。
include動作:例如
<jsp:include page="copyright.jsp" />
該動作引入指定頁面執行後生成的結果。由於它在執行時完成,因此對輸出結果的控制更加靈活。但時,只有當被引用的內容頻繁地改變時,或者在對主頁面的請求沒有出現之前,被引用的頁面無法確定時,使用include動作才合算。
4. 在spring中對orm層的動作設定只讀屬性
將 (只對資料庫進行讀取的操作) 設定只讀屬性
10. Servlet與記憶體使用
許多開發者隨意地把大量資訊儲存到使用者會話之中。一些時候,儲存在會話中的物件沒有及時地被垃圾回收機制回收。從效能上看,典型的症狀是使用者感到系統週期性地變慢,卻又不能把原因歸於任何一個具體的元件。如果監視JVM的堆空間,它的表現是記憶體佔用不正常地大起大落。解決這類記憶體問題主要有二種辦法。第一種辦法是,在所有作用範圍為會話的Bean中實現HttpSessionBindingListener介面。這樣,只要實現valueUnbound()方法,就可以顯式地釋放Bean使用的資源。
另外一種辦法就是儘快地把會話作廢。大多數應用伺服器都有設定會話作廢間隔時間的選項。另外,也可以用程式設計的方式呼叫會話的setMaxInactiveInterval()方法,該方法用來設定在作廢會話之前,Servlet容器允許的客戶請求的最大間隔時間,以秒計算。
11. HTTP Keep-Alive
Keep-Alive功能使客戶端到伺服器端的連線持續有效,當出現對伺服器的後繼請求時,Keep-Alive功能避免了建立或者重新建立連線。市場上的大部分Web伺服器,包括iPlanet、IIS和Apache,都支援HTTP Keep-Alive。對於提供靜態內容的網站來說,這個功能通常很有用。但是,對於負擔較重的網站來說,這裡存在另外一個問題:雖然為客戶保留開啟的連線有一定的好處,但它同樣影響了效能,因為在處理暫停期間,本來可以釋放的資源仍舊被佔用。當Web伺服器和應用伺服器在同一臺機器上執行時,Keep-Alive功能對資源利用的影響尤其突出。
12. JDBC與Unicode
想必你已經瞭解一些使用JDBC時提高效能的措施,比如利用連線池、正確地選擇儲存過程和直接執行的SQL、從結果集刪除多餘的列、預先編譯SQL語句,等等。除了這些顯而易見的選擇之外,另一個提高效能的好選擇可能就是把所有的字元資料都儲存為Unicode(內碼表13488)。Java以Unicode形式處理所有資料,因此,資料庫驅動程式不必再執行轉換過程。但應該記住:如果採用這種方式,資料庫會變得更大,因為每個Unicode字元需要2個位元組儲存空間。另外,如果有其他非Unicode的程式訪問資料庫,效能問題仍舊會出現,因為這時資料庫驅動程式仍舊必須執行轉換過程。
13. JDBC與I/O
如果應用程式需要訪問一個規模很大的資料集,則應當考慮使用塊提取方式。預設情況下,JDBC每次提取32行資料。舉例來說,假設我們要遍歷一個5000行的記錄集,JDBC必須呼叫資料庫157次才能提取到全部資料。如果把塊大小改成512,則呼叫資料庫的次數將減少到10次。在一些情形下這種技術無效。例如,如果使用可滾動的記錄集,或者在查詢中指定了FOR UPDATE,則塊操作方式不再有效。
Java程式碼優化--儘可能地使用stack(棧)變數(方法內部的區域性變數)
Java程式包含了大量的物件,我們需要了解它們是從哪裡被訪問的,變數儲存於何處對程式的效能有顯著的影響--尤其是某些需要被頻繁訪問的變數。
我們寫一個Java類,在其內部方法中定義的區域性變數或物件是儲存在stack(堆疊)中的,且JVM是一種stack-based的,因此訪問和操縱stack中的資料時效能最佳。而Java類的instance變數(這個類的field)和static變數是在constant pool(常量池)中儲存和得到訪問的。constant pool中儲存了所有的符號引用(symbolic references),指向所有型別(types)、值域(field),以及每個型別所使用的所有函式(mothods)。訪問instance和static變數時,由於它們存放於constant pool中,所以JVM需要使用更多更耗時的操作碼(分析程式生成的bytecode可以看出來)來訪問它們。
下面給出一段程式碼示例,對比後說明怎麼儘可能地使用stack變數:
package test;
public class StackVars {
private int x; // instance變數
private static int staticX; //static 變數
public void stackAccess(int val) { //訪問和操作stack變數j
int j = 0;
for (int i = 0; i < val; i++) {
j += 1;
}
}
public void instanceAccess(int val) {//訪問和操作instance變數x
for (int i = 0; i < val; i++) {
x += 1;
}
}
public void staticAccess(int val) {//訪問和操作static變數staticX
for (int i = 0; i < val; i++) {
staticX += 1;
}
}
}
經測試,發現執行instanceAccess()和staticAccess()方法的時間大約相同,但卻比執行stackAccess()方法慢了2~3倍。因此我們對instanceAccess()、staticAccess()兩個方法的程式碼作以下調整,以得到更快的效能:
public void instanceAccess(int val) {//訪問和操作instance變數x
int tempX=x;
for (int i = 0; i < val; i++) {
tempX += 1;
}
x=tempX;
}
public void staticAccess(int val) {//訪問和操作static變數staticX
int tempStaticX=staticX;
for (int i = 0; i < val; i++) {
tempStaticX += 1;
}
staticX=tempStaticX;
}
改善之處就是將instance和static變數放到迴圈之外,而用一個stack變數來完成多次區域性運算,最後再將這個stack變數的值傳回instance或static變數,從而提高了程式碼的效能。
Sun JDK自帶JVM記憶體使用分析工具HProf
使用Sun JDK自帶JVM記憶體使用分析工具HProf可以分析JVM堆疊,從而找到佔用記憶體較大的物件。這對應經常出現記憶體洩漏(OOM)的JAVA系統進行調優很有幫助。
HProf使用方法
• 在WeblogicServer啟動指令碼中增加-Xrunhprof:heap=sites,重新啟動WeblogicServer。
• 使用kill -3 <pid> 或退出WeblogicServer均會生成java.hprof.txt檔案,直接開啟此檔案便可分析JVM的具體執行情況。
從java.hprof.txt記錄的JVM堆疊結果中可以發現JVM佔用記憶體較大的物件:
percent live alloc'ed stack class
rank self accum bytes objs bytes objs trace name
1 4.57% 4.57% 2289696 47702 8392224 174838 4251 [C
2 3.99% 8.57% 2000016 1 2000016 1 12308 [C
3 3.65% 12.22% 1827552 9622 1852672 10082 43265 [C
4 2.58% 14.80% 1293912 53913 3929424 163726 4258 java.lang.String
5 2.05% 16.85% 1028664 7585 3207272 24923 4252 [C
6 2.03% 18.88% 1015816 159 1015816 159 18694 [B
7 1.88% 20.77% 942080 230 2740224 669 20416 [B
8 1.61% 22.37% 805752 2142 2150856 4635 45318 [B
9 1.60% 23.98% 802880 772 802880 772 24710 weblogic.servlet.utils.URLMatchMap$URLMatchNode
10 1.60% 25.57% 799400 19985 2781400 69535 45073 cnc.util.Field
11 1.36% 26.93% 679360 3805 679360 3805 494 [B
12 1.35% 28.28% 674856 28119 5181240 215885 2985 java.util.HashMap$Entry
……
……
96 0.19% 63.73% 94776 3112 94776 3112 9146 [C
97 0.19% 63.92% 93456 3894 123936 5164 23631 java.lang.String
98 0.19% 64.10% 93224 3884 123968 5165 23644 java.lang.String
99 0.19% 64.29% 93192 3883 123936 5164 23636 java.lang.String
100 0.18% 64.47% 89528 238 240264 520 33227 [B
101 0.17% 64.64% 86448 1901 103472 2255 18715 java.lang.Object
102 0.17% 64.81% 85464 676 85768 695 18715 [S
103 0.17% 64.98% 85184 1331 85184 1331 28266 weblogic.ejb20.internal.MethodDescriptor
104 0.17% 65.15% 84224 752 84224 752 24148 weblogic.servlet.internal.dd.ServletDescriptor
105 0.17% 65.32% 84136 528 50471136 348769 63 [C
106 0.16% 65.48% 79968 1428 388976 6946 5503 java.lang.reflect.Method
107 0.15% 65.63% 77520 1615 77520 1615 27967 weblogic.ejb20.deployer.mbimpl.MethodInfoImpl
108 0.15% 65.79% 77056 4816 469808 29363 20250 java.lang.Object
109 0.15% 65.94% 76960 74 76960 74 23695 [B
110 0.15% 66.09% 76104 3171 215040 8960 45071 cnc.util.FyCol
111 0.15% 66.24% 74688 3112 74688 3112 9152 java.util.Hashtable$Entry
112 0.15% 66.39% 74688 3112 74688 3112 9147 java.lang.String
113 0.15% 66.54% 74280 61 794328 788 45313 [C
114 0.14% 66.68% 72480 1510 436032 9084 45353 [C
115 0.14% 66.82% 70720 68 70720 68 25869 [B
116 0.14% 66.97% 70720 68 70720 68 27448 [B
117 0.14% 67.11% 70272 1279 142672 2439 5503 [C
118 0.14% 67.24% 69256 86 69256 86 6584 [S
119 0.13% 67.38% 67056 66 67056 66 28882 java.lang.Object
120 0.13% 67.51% 66176 752 66176 752 24170 weblogic.servlet.internal.dd.UIDescriptor
121 0.13% 67.64% 65688 715 65688 715 25389 [C
122 0.13% 67.77% 65600 4 885600 54 23939 [C
123 0.13% 67.90% 65600 4 623200 38 40639 [C
124 0.13% 68.03% 65576 367 65576 367 51686 [C
125 0.13% 68.17% 65568 2 65568 2 30610 java.util.HashMap$Entry
126 0.13% 68.30% 65568 2 130816 16 43271 java.util.HashMap$Entry
127 0.13% 68.43% 65552 1 65552 1 16617 [B
128 0.13% 68.56% 64600 1615 64600 1615 27969 java.util.HashMap
129 0.13% 68.68% 63888 2662 64032 2668 16951 java.util.HashMap$Entry
130 0.13% 68.81% 63888 2662 64032 2668 16997 java.util.HashMap$Entry
131 0.13% 68.94% 63888 2662 64032 2668 16996 weblogic.rmi.internal.ClientMethodDescriptor
132 0.13% 69.07% 63888 2662 99120 4130 16949 java.lang.String
133 0.13% 69.19% 63888 2662 64032 2668 16976 java.lang.String
134 0.13% 69.32% 63232 152 63232 152 9655 weblogic.utils.collections.ConcurrentHashMap$Entry
135 0.13% 69.45% 63232 152 63232 152 9704 weblogic.utils.collections.ConcurrentHashMap$Entry
136 0.12% 69.57% 62168 3885 82632 5164 23628 [B
137 0.12% 69.69% 61680 406 66904 468 1 [C
138 0.12% 69.82% 61504 4 246016 16 47372 [B
139 0.12% 69.94% 61144 36 91019160 23904 92 [B
140 0.12% 70.06% 61040 763 61040 763 24194 weblogic.servlet.internal.dd.ServletMappingDescriptor
141 0.12% 70.18% 60400 1510 363360 9084 45338 java.util.Hashtable
142 0.12% 70.30% 59544 827 59544 827 24746 weblogic.servlet.internal.ServletRuntimeMBeanImpl
143 0.12% 70.42% 59248 1058 484984 8664 33236 oracle.jdbc.ttc7.TTCItem
144 0.12% 70.53% 58152 232 187176 764 748 [C
145 0.12% 70.65% 57888 2412 161904 6746 16621 java.lang.String
146 0.11% 70.77% 57400 1435 57400 1435 16855 java.util.HashMap
……
……
根據以上的結果,在java.hprof.txt中定位到導致分配大記憶體的操作如下:
TRACE 63:
java.lang.StringBuffer.expandCapacity(StringBuffer.java:202)
java.lang.StringBuffer.append(StringBuffer.java:401)
java.util.zip.ZipFile.getEntry(ZipFile.java:148)
java.util.jar.JarFile.getEntry(JarFile.java:198)
TRACE 92:
java.util.zip.InflaterInputStream.<init>(InflaterInputStream.java:71)
java.util.zip.ZipFile$1.<init>(ZipFile.java:240)
java.util.zip.ZipFile.getInputStream(ZipFile.java:212)
java.util.zip.ZipFile.getInputStream(ZipFile.java:183)
再進一步分析則需要應用開發人員對應用程式碼做相應的分析定位。
注意:使用HProf非常消耗資源,切記不要在生產系統使用。
Java優化程式設計(第二版)
2.1 垃圾回收堆記憶體
記憶體管理的話題在C或C++程式設計中討論得相對較多,因為在C與C++程式設計中需要開發人員自己申請並管理記憶體,開發人員可以申請/借用(Apply)系統記憶體並且負責釋放/歸還(Release)系統記憶體,如果“只借不還”就會造成系統記憶體洩露的問題。在Java程式設計中,這些工作由Java虛擬機器(JVM)負責處理。所有記憶體的申請、分配、釋放都由JVM負責完成。因此,開發人員就省去了這部分工作,不過這並不意味著開發人員可以完全依賴於JVM的記憶體管理功能,如果你這樣想並在實際的應用開發中也這樣做,你所開發的應用的效能,就有可能不是最優的。這是因為,無論配置多麼優良的硬體環境其自身資源都是有限的,由於沒有合理、科學的使用記憶體資源,即使是Java應用也會出現記憶體枯竭的現象。例如,我們經常會遇到的OutOfMemoryException。再者Java語言(其實不只Java語言)的效能極大程度上依賴於其執行的硬體環境資源,而記憶體又是硬體環境資源中重要的一部分,因此說,如果開發人員開發的Java應用沒能有效、合理地使用系統記憶體,那麼這個應用就不可能具備較高的效能,甚至會導致整個系統在執行一段時間後崩潰。本章將對Java應用開發中與記憶體管理相關的技術做詳細的講解。
2.1 垃圾回收
談到Java記憶體管理的話題,就必然會提到垃圾回收的概念,垃圾回收的英文名稱為Garbage Collection,簡稱GC,它是Java程式設計中有關記憶體管理的核心概念,Java虛擬機器(JVM)的記憶體管理機制被稱為垃圾回收機制。因此,要想掌握在開發Java應用時怎樣才能合理地管理記憶體,首先應該瞭解Java虛擬機器的記憶體管理機制——垃圾回收機制。否則,在不瞭解垃圾回收具體實現機制的情況下討論Java程式設計中的記憶體管理,優化Java應用效能,就有些紙上談兵,捨本逐末了。
上面我們提到Java程式設計中的記憶體管理機制是通過垃圾回收來完成的,那麼在JVM執行環境中什麼樣的物件是垃圾呢?下面我們給出了在JVM執行環境中垃圾物件的定義:
一個物件建立後被放置在JVM的堆記憶體(heap)中,當永遠不再引用這個物件時,它將被JVM在堆記憶體(heap)中回收。被建立的物件不能再生,同時也沒有辦法通過程式語句釋放它們。
我們也可以這樣給JVM中的垃圾物件下定義:
當物件在JVM執行空間中無法通過根集合(root set)到達(找到)時,這個物件就被稱為垃圾物件。根集合是由類中的靜態引用域與本地引用域組成的。JVM中通過根集合索引物件如圖2-1所示。
圖2-1 JVM中通過根集合索引物件
&注意 圖2-1中打了X標記的物件就是不可到達的物件,這些物件就被JVM視為垃圾物件並被JVM回收。JVM將給這些物件打上相應的標記,然後清掃回收這些物件,並將散碎的記憶體單元收集整合。
這裡提到了堆記憶體的概念,它是JVM管理的一種記憶體型別,在做Java應用開發時經常會用到由JVM管理的兩種型別的記憶體:堆記憶體(heap)與棧記憶體(stack)。有關堆記憶體的概念,在前面的相關章節中,已經做過相應的介紹。簡單地講,堆記憶體主要用來儲存程式在執行時建立或例項化的物件與變數,例如:我們通過new MyClass()建立的類MyClass的物件。而棧記憶體(stack)則是用