版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
Java程序性能優(yōu)化實戰(zhàn)目錄TOC\h\h第1章Java性能調(diào)優(yōu)概述\h1.1性能概述\h1.1.1看懂程序的性能\h1.1.2性能的參考指標\h1.1.3木桶原理與性能瓶頸\h1.1.4Amdahl定律\h1.2性能調(diào)優(yōu)的層次\h1.2.1設計調(diào)優(yōu)\h1.2.2代碼調(diào)優(yōu)\h1.2.3JVM調(diào)優(yōu)\h1.2.4數(shù)據(jù)庫調(diào)優(yōu)\h1.2.5操作系統(tǒng)調(diào)優(yōu)\h1.3基本調(diào)優(yōu)策略和手段\h1.3.1優(yōu)化的一般步驟\h1.3.2系統(tǒng)優(yōu)化的注意事項\h1.4小結\h第2章設計優(yōu)化\h2.1善用設計模式\h2.1.1單例模式\h2.1.2代理模式\h2.1.3享元模式\h2.1.4裝飾者模式\h2.1.5觀察者模式\h2.1.6值對象模式\h2.1.7業(yè)務代理模式\h2.2常用的優(yōu)化組件和方法\h2.2.1緩沖\h2.2.2緩存\h2.2.3對象復用——池\h2.2.4并行替代串行\(zhòng)h2.2.5負載均衡\h2.2.6時間換空間\h2.2.7空間換時間\h2.3小結\h第3章Java程序優(yōu)化\h3.1字符串優(yōu)化處理\h3.1.1String對象及其特點\h3.1.2substring()方法的內(nèi)存泄漏\h3.1.3字符串分割和查找\h3.1.4StringBuffer和StringBuilder\h3.1.5CompactStrings優(yōu)化字符串存儲\h3.2核心數(shù)據(jù)結構\h3.2.1List接口\h3.2.2Map接口\h3.2.3Set接口\h3.2.4優(yōu)化集合訪問代碼\h3.2.5RandomAccess接口\h3.3使用NIO提升性能\h3.3.1NIO中的Buffer類族和Channel\h3.3.2Buffer的基本原理\h3.3.3Buffer的相關操作\h3.3.4MappedByteBuffer性能評估\h3.3.5直接訪問內(nèi)存\h3.4引用類型\h3.4.1強引用\h3.4.2軟引用\h3.4.3弱引用\h3.4.4虛引用\h3.4.5WeakHashMap類及其實現(xiàn)\h3.5性能測試工具JMH\h3.5.1JMH之HelloWorld\h3.5.2JMH之指定測量模式\h3.5.3JMH之對象作用域\h3.5.4JMH之代碼消除\h3.6有助于改善性能的技巧\h3.6.1使用局部變量\h3.6.2位運算代替乘除法\h3.6.3替換switch\h3.6.4一維數(shù)組代替二維數(shù)組\h3.6.5提取表達式\h3.6.6展開循環(huán)\h3.6.7布爾運算代替位運算\h3.6.8使用arrayCopy()\h3.6.9使用Buffer進行I/O操作\h3.6.10使用clone()代替new\h3.6.11慎用Java函數(shù)式編程\h3.7小結\h第4章并行程序開發(fā)及優(yōu)化\h4.1并行程序設計模式\h4.1.1Future模式\h4.1.2Master-Worker模式\h4.1.3GuardedSuspension模式\h4.1.4不變模式\h4.1.5生產(chǎn)者-消費者模式\h4.2JDK多任務執(zhí)行框架\h4.2.1無限制線程的缺陷\h4.2.2簡單的線程池實現(xiàn)\h4.2.3Executor框架\h4.2.4自定義線程池\h4.2.5優(yōu)化線程池大小\h4.2.6擴展ThreadPoolExecutor\h4.3JDK并發(fā)數(shù)據(jù)結構\h4.3.1并發(fā)List\h4.3.2并發(fā)Set\h4.3.3并發(fā)Map\h4.3.4并發(fā)Queue\h4.3.5并發(fā)Deque\h4.4并發(fā)控制方法\h4.4.1Java內(nèi)存模型與volatile\h4.4.2同步關鍵字synchronized\h4.4.3重入鎖\h4.4.4讀寫鎖\h4.4.5讀寫鎖的改進:StampedLock\h4.4.6Condition對象\h4.4.7信號量\h4.4.8線程局部變量ThreadLocal\h4.5鎖的性能和優(yōu)化\h4.5.1線程的開銷\h4.5.2避免死鎖\h4.5.3減少鎖持有時間\h4.5.4減小鎖粒度\h4.5.5讀寫分離鎖來替換獨占鎖\h4.5.6鎖分離\h4.5.7重入鎖和內(nèi)部鎖\h4.5.8鎖粗化\h4.5.9自旋鎖\h4.5.10鎖消除\h4.5.11鎖偏向\h4.6無鎖的并行計算\h4.6.1非阻塞的同步/無鎖\h4.6.2原子操作\h4.6.3Amino框架簡介\h4.6.4Amino集合\h4.6.5Amino樹\h4.6.6Amino圖\h4.6.7Amino簡單調(diào)度模式\h4.7協(xié)程\h4.7.1協(xié)程的概念\h4.7.2Kilim框架簡介\h4.7.3Task及其狀態(tài)\h4.7.4Fiber及其狀態(tài)\h4.7.5Kilim開發(fā)環(huán)境配置\h4.7.6Kilim之HelloWorld\h4.7.7多任務通信\h4.7.8Kilim實例及性能評估\h4.8小結\h第5章JVM調(diào)優(yōu)\h5.1Java虛擬機內(nèi)存模型\h5.1.1程序計數(shù)器\h5.1.2Java虛擬機棧\h5.1.3本地方法棧\h5.1.4Java堆\h5.1.5方法區(qū)\h5.2JVM內(nèi)存分配參數(shù)\h5.2.1設置最大堆內(nèi)存\h5.2.2設置最小堆內(nèi)存\h5.2.3設置新生代\h5.2.4設置持久代\h5.2.5設置線程棧\h5.2.6堆的比例分配\h5.2.7堆分配參數(shù)總結\h5.3垃圾收集基礎\h5.3.1垃圾收集的作用\h5.3.2垃圾回收算法與思想\h5.3.3垃圾回收器的類型\h5.3.4評價GC策略的指標\h5.3.5新生代串行回收器\h5.3.6老年代串行回收器\h5.3.7并行回收器\h5.3.8新生代并行回收器\h5.3.9老年代并行回收器\h5.3.10CMS回收器\h5.3.11G1回收器\h5.3.12StoptheWorld案例\h5.3.13垃圾回收器對系統(tǒng)性能的影響\h5.3.14GC操作相關參數(shù)總結\h5.4常用調(diào)優(yōu)案例和方法\h5.4.1將新對象預留在新生代\h5.4.2大對象進入老年代\h5.4.3設置對象進入老年代的年齡\h5.4.4穩(wěn)定與振蕩的堆大小\h5.4.5吞吐量優(yōu)先案例\h5.4.6使用大頁案例\h5.4.7降低停頓案例\h5.5實用JVM參數(shù)\h5.5.1JIT編譯參數(shù)\h5.5.2堆快照\h5.5.3錯誤處理\h5.5.4獲取GC信息\h5.5.5類和對象跟蹤\h5.5.6控制GC\h5.5.7選擇類校驗器\h5.5.8Solaris下的線程控制\h5.5.9使用大頁\h5.5.10壓縮指針\h5.6JVM調(diào)優(yōu)實戰(zhàn)\h5.6.1Tomcat簡介與啟動加速\h5.6.2Web應用程序簡介\h5.6.3JMeter簡介與使用\h5.6.4調(diào)優(yōu)前Web應用運行狀況\h5.6.5調(diào)優(yōu)過程\h5.7小結\h第6章Java性能調(diào)優(yōu)工具\h6.1Linux命令行工具\h6.1.1top命令\h6.1.2sar命令\h6.1.3vmstat命令\h6.1.4iostat命令\h6.1.5pidstat工具\h6.2Windows工具\h6.2.1任務管理器\h6.2.2perfmon性能監(jiān)控工具\h6.2.3ProcessExplorer工具\h6.2.4pslist命令行\(zhòng)h6.3JDK命令行工具\h6.3.1jps命令\h6.3.2jstat命令\h6.3.3jinfo命令\h6.3.4jmap命令\h6.3.5jhat命令\h6.3.6jstack命令\h6.3.7jstatd命令\h6.3.8hprof工具\h6.3.9jcmd命令\h6.4JConsole工具\h6.4.1JConsole連接Java程序\h6.4.2Java程序概況\h6.4.3內(nèi)存監(jiān)控\h6.4.4線程監(jiān)控\h6.4.5類加載情況\h6.4.6虛擬機信息\h6.4.7MBean管理\h6.4.8使用插件\h6.5VisualVM多合一工具\h6.5.1VisualVM連接應用程序\h6.5.2監(jiān)控應用程序概況\h6.5.3ThreadDump和分析\h6.5.4性能分析\h6.5.5快照\h6.5.6內(nèi)存快照分析\h6.5.7MBean管理功能\h6.5.8TDA的使用\h6.5.9BTrace簡介\h6.6VisualVM對OQL的支持\h6.6.1VisualVM的OQL基本語法\h6.6.2內(nèi)置heap對象\h6.6.3對象函數(shù)\h6.6.4集合/統(tǒng)計函數(shù)\h6.6.5程序化OQL\h6.7MAT內(nèi)存分析工具\h6.7.1初識MAT\h6.7.2淺堆和深堆\h6.7.3支配樹\h6.7.4垃圾回收根\h6.7.5內(nèi)存泄漏檢測\h6.7.6最大對象報告\h6.7.7查找支配者\h6.7.8線程分析\h6.7.9集合使用情況分析\h6.7.10擴展MAT\h6.8MAT對OQL的支持\h6.8.1Select子句\h6.8.2From子句\h6.8.3Where子句\h6.8.4內(nèi)置對象與方法\h6.9來自JRockit的禮物——JMC\h6.9.1得到JFR文件\h6.9.2Java程序的整體運行情況\h6.9.3CPU分析\h6.9.4內(nèi)存分析\h6.9.5I/O分析\h6.10小結第1章Java性能調(diào)優(yōu)概述本章將對性能優(yōu)化技術進行整體性概述,讓讀者了解性能的概念和性能優(yōu)化的基本思路和方法。掌握這些內(nèi)容,有助于讀者對性能問題進行系統(tǒng)分析。本章涉及的主要知識點有:·評價性能的主要指標;·木桶原理的概念及其在性能優(yōu)化中的應用;·Amdahl定律的含義;·性能調(diào)優(yōu)的層次;·系統(tǒng)優(yōu)化的一般步驟和注意事項。1.1性能概述為什么程序總是那么慢?它現(xiàn)在到底在干什么?時間都耗費在哪里了?也許,你經(jīng)常會抱怨這些問題。如果是這樣,那么說明你的程序出現(xiàn)了性能問題。和功能性問題相比,性能問題在有些情況下可能并不算什么太大的問題,將就將就,也就過去了。但是,嚴重的性能問題會導致程序癱瘓、假死,直至崩潰。本節(jié)就先來認識性能的各種表現(xiàn)和指標。1.1.1看懂程序的性能對于客戶端程序而言,拙劣的性能會嚴重影響用戶體驗。界面停頓、抖動、響應遲鈍等問題會遭到用戶不停地抱怨。一個典型的例子就是EclipseIDE工具在執(zhí)行FullGC時會出現(xiàn)程序“假死”現(xiàn)象,相信這一定被不少開發(fā)人員所詬病。對于服務器程序來說,性能問題則更為重要。相信不少后臺服務器軟件都有各自的性能目標,以Web服務器為例,服務器的響應時間和吞吐量就是兩個重要的性能參數(shù)。當服務器承受巨大的訪問壓力時,可能出現(xiàn)響應時間變長、吞吐量下降,甚至拋出內(nèi)存溢出異常而崩潰等問題。這些問題,都是性能調(diào)優(yōu)需要解決的。一般來說,程序的性能可以有以下幾個方面的表現(xiàn)?!?zhí)行速度:程序的反應是否迅速,響應時間是否足夠短?!?nèi)存分配:內(nèi)存分配是否合理,是否過多地消耗內(nèi)存或者存在泄漏?!訒r間:程序從運行到可以正常處理業(yè)務需要花費多長時間。·負載承受能力:當系統(tǒng)壓力上升時,系統(tǒng)的執(zhí)行速度和響應時間的上升曲線是否平緩。1.1.2性能的參考指標為了能夠科學地進行性能分析,對性能指標進行定量評測是非常重要的。目前,可以用于定量評測的性能指標有:·執(zhí)行時間:一段代碼從開始運行到運行結束所使用的時間?!PU時間:函數(shù)或者線程占用CPU的時間。·內(nèi)存分配:程序在運行時占用的內(nèi)存空間。·磁盤吞吐量:描述I/O的使用情況。·網(wǎng)絡吞吐量:描述網(wǎng)絡的使用情況?!ろ憫獣r間:系統(tǒng)對某用戶行為或者事件做出響應的時間。響應時間越短,性能越好。1.1.3木桶原理與性能瓶頸木桶原理又稱短板理論,其核心思想是一只木桶盛水的多少,并不取決于桶壁上最高的那塊木板,而是取決于桶壁上最短的那塊木板,如圖1.1所示。圖1.1木桶原理示意圖將這個理論應用到系統(tǒng)性能優(yōu)化上可以這么理解,即使系統(tǒng)擁有充足的內(nèi)存資源和CPU資源,但是,如果磁盤I/O性能低下,那么系統(tǒng)的總體性能是取決于當前最慢的磁盤I/O速度,而不是當前最優(yōu)越的CPU或者內(nèi)存。在這種情況下,如果需要進一步提升系統(tǒng)性能,優(yōu)化內(nèi)存或者CPU資源是毫無用處的,只有提高磁盤I/O性能才能對系統(tǒng)的整體性能進行優(yōu)化。此時,磁盤I/O就是系統(tǒng)的性能瓶頸。注意:根據(jù)木桶原理,系統(tǒng)的最終性能取決于系統(tǒng)中性能表現(xiàn)最差的組件。因此,為了提升系統(tǒng)的整體性能,必須對系統(tǒng)中表現(xiàn)最差的組件進行優(yōu)化,而不是對系統(tǒng)中表現(xiàn)良好的組件進行優(yōu)化。根據(jù)應用的特點不同,任何計算機資源都有可能成為系統(tǒng)瓶頸。其中,最有可能成為系統(tǒng)瓶頸的計算資源有:·磁盤I/O:由于磁盤I/O讀寫的速度比內(nèi)存慢很多,程序在運行過程中,如果需要等待磁盤I/O完成,那么低效的I/O操作會拖累整個系統(tǒng)?!ぞW(wǎng)絡操作:對網(wǎng)絡數(shù)據(jù)進行讀寫的情況與磁盤I/O類似,由于網(wǎng)絡環(huán)境的不確定性,尤其是對互聯(lián)網(wǎng)上數(shù)據(jù)的讀寫,網(wǎng)絡操作的速度可能比本地磁盤I/O更慢,因此,如不加特殊處理,也極可能成為系統(tǒng)瓶頸?!PU:對計算資源要求較高的應用,由于其長時間、不間斷地大量占用CPU資源,那么對CPU的爭奪將導致性能問題。例如,科學計算、3D渲染等對CPU需求量大的應用便是如此。·異常:對Java應用來說,異常的捕獲和處理是非常消耗資源的。如果程序高頻率地進行異常處理,則整體性能便會有明顯下降?!?shù)據(jù)庫:大部分應用程序都離不開數(shù)據(jù)庫,而海量數(shù)據(jù)的讀寫操作往往是相當費時的。應用程序需要等待數(shù)據(jù)庫操作完成并返回結果,那么緩慢的同步操作將成為系統(tǒng)瓶頸?!ゆi競爭:對高并發(fā)程序來說,如果存在激烈的鎖競爭,對性能無疑是極大的打擊。鎖競爭將會明顯增加線程上下文切換的開銷,而且這些開銷都是與應用需求無關的系統(tǒng)開銷,白白占用寶貴的CPU資源,卻不帶來任何好處。·內(nèi)存:一般來說,只要應用程序設計合理,內(nèi)存在讀寫速度上不太可能成為性能瓶頸。除非應用程序進行了高頻率的內(nèi)存交換和掃描,但這種情況比較少見。內(nèi)存制約系統(tǒng)性能最有可能出現(xiàn)的情況是內(nèi)存容量不足。與磁盤相比,內(nèi)存的容量似乎小得可憐,這意味著應用軟件只能盡可能將常用的核心數(shù)據(jù)讀入內(nèi)存,這在一定程度上降低了系統(tǒng)性能。1.1.4Amdahl定律Amdahl定律是計算機科學中非常重要的定律,它定義了串行系統(tǒng)并行化后的加速比的計算公式和理論上限。加速比定義如下:加速比=優(yōu)化前系統(tǒng)耗時/優(yōu)化后系統(tǒng)耗時即所謂的加速比,就是優(yōu)化前系統(tǒng)耗時與優(yōu)化后系統(tǒng)耗時的比值。加速比越高,表明優(yōu)化效果越明顯。Amdahl定律給出了加速比與系統(tǒng)并行度和處理器數(shù)量的關系。設加速比為Speedup,系統(tǒng)內(nèi)必須串行化的程序比重為F,CPU處理器的數(shù)量為N,則有根據(jù)這個公式可知,如果CPU處理器的數(shù)量趨于無窮,那么加速比與系統(tǒng)的串行化率成反比,如果系統(tǒng)中必須有50%的代碼串行執(zhí)行,那么系統(tǒng)的最大加速比為2。假設有一程序分為5個步驟執(zhí)行,每個執(zhí)行步驟花費100個時間單位。其中,只有步驟2和步驟5可以進行并行,步驟1、3、4必須串行,如圖1.2所示。在全串行的情況下,系統(tǒng)合計耗時500個時間單位。圖1.2串行工作流程若將步驟2和步驟5并行化,假設在雙核處理器上,則有如圖1.3所示的處理流程。在這種情況下,步驟2和步驟5的耗時將為50個時間單位,故系統(tǒng)整體耗時為400個時間單位。根據(jù)加速比的定義有:加速比=優(yōu)化前系統(tǒng)耗時/優(yōu)化后系統(tǒng)耗時=500/400=1.25或者使用Amdahl定律給出的加速比公式。由于5個步驟中,3個步驟必須串行,因此其串行化比重為3/5=0.6,即F=0.6,且雙核處理器的處理器個數(shù)N為2。代入公式得:圖1.3雙核處理器上的并行化在極端情況下,假設并行處理器個數(shù)為無窮大,則有如圖1.4所示的處理過程。步驟2和步驟5的處理時間趨于0。即使這樣,系統(tǒng)整體耗時依然大于300個時間單位,即加速比的極限為500/300=1.67。使用加速比計算公式,N趨于無窮大,有Speedup=1/F,且F=0.6,故有Speedup=1.67。圖1.4極端情況下的并行化由此可見,為了提高系統(tǒng)的速度,僅增加CPU處理器的數(shù)量并不一定能起到有效的作用。需要從根本上修改程序的串行行為,提高系統(tǒng)內(nèi)可并行化的模塊比重,在此基礎上合理增加并行處理器的數(shù)量,才能以最小的投入得到最大的加速比。注意:根據(jù)Amdahl定律,使用多核CPU對系統(tǒng)進行優(yōu)化,優(yōu)化的效果取決于CPU的數(shù)量及系統(tǒng)中串行化程序的比重。CPU數(shù)量越多,串行化比重越低,則優(yōu)化效果越好。僅提高CPU數(shù)量而不降低程序的串行化比重,則無法提高系統(tǒng)性能。1.2性能調(diào)優(yōu)的層次為了提升系統(tǒng)性能,開發(fā)人員可以從系統(tǒng)的各個角度和層次對系統(tǒng)進行優(yōu)化。除了最常見的代碼優(yōu)化外,在軟件架構、JVM虛擬機、數(shù)據(jù)庫及操作系統(tǒng)幾個層面可以通過各種手段進行調(diào)優(yōu),從而在整體上提升系統(tǒng)的性能。1.2.1設計調(diào)優(yōu)設計調(diào)優(yōu)處于所有調(diào)優(yōu)手段的上層,它往往需要在軟件開發(fā)之前進行。在軟件開發(fā)之初,軟件架構師就應該評估系統(tǒng)可能存在的各種潛在問題,并給出合理的設計方案。由于軟件設計和架構對軟件整體質(zhì)量有決定性的影響,所以,設計調(diào)優(yōu)對系統(tǒng)性能的影響也是最大的。如果說代碼優(yōu)化、JVM優(yōu)化都是對系統(tǒng)在微觀層面上“量”的優(yōu)化,那么設計優(yōu)化就是對系統(tǒng)在宏觀層面上“質(zhì)”的優(yōu)化。設計優(yōu)化的一大顯著特點是,它可以規(guī)避某一個組件的性能問題,而非改良該組件的實現(xiàn)。例如,系統(tǒng)中組件A需要等待某事件E才能觸發(fā)一個行為。如果組件A通過循環(huán)監(jiān)控不斷監(jiān)測事件E是否發(fā)生,其監(jiān)測行為必然會占用部分系統(tǒng)資源,因此,開發(fā)人員必須在監(jiān)測頻率和資源消耗間取得平衡。如果監(jiān)測頻率太低,雖然減少了資源消耗,但是系統(tǒng)實時反應性就會降低。如果進行代碼層的調(diào)優(yōu),就需要優(yōu)化監(jiān)測方法的實現(xiàn)以及求得一個最為恰當?shù)谋O(jiān)測頻率。而若將此問題預留在設計層解決,便可以使用事件通知的方式將系統(tǒng)行為進行倒置。例如,使用將在第2章中提到的觀察者模式,在事件E發(fā)生的時刻,由事件E通知組件A,從而觸發(fā)組件A的行為。這種設計方法棄用了存在性能隱患的循環(huán)監(jiān)控,從根本上解決了這一問題。從某種程度上說,設計優(yōu)化直接決定了系統(tǒng)的整體品質(zhì)。如果在設計層考慮不周,留下太多問題隱患,那么這些“質(zhì)”上的問題,也許無法再通過代碼層的優(yōu)化進行彌補。因此,開發(fā)人員必須在軟件設計之初,要認真、仔細地考慮軟件系統(tǒng)的性能問題。進行設計優(yōu)化時,設計人員必須熟悉常用的軟件設計方法、設計模式、基本性能組件和常用的優(yōu)化思想,并將其有機地集成在軟件系統(tǒng)中。注意:一個良好的系統(tǒng)設計可以規(guī)避很多潛在的性能問題。因此,盡可能多花一些時間在系統(tǒng)設計上,這是創(chuàng)建高性能程序的關鍵。1.2.2代碼調(diào)優(yōu)代碼調(diào)優(yōu)是在軟件開發(fā)過程中或者在軟件開發(fā)完成后,軟件維護過程中進行的對程序代碼的改進和優(yōu)化。代碼優(yōu)化涉及諸多編碼技巧,需要開發(fā)人員熟悉相關語言的API,并在合適的場景中正確使用相關API或類庫。同時,對算法和數(shù)據(jù)結構的靈活使用,也是代碼優(yōu)化的重要內(nèi)容。雖然代碼優(yōu)化是從微觀上對性能進行調(diào)整,但是一個“好”的實現(xiàn)和一個“壞”的實現(xiàn)對系統(tǒng)的影響也是非常大的。例如,同樣作為List的實現(xiàn),LinkedList和ArrayList在隨機訪問上的性能卻相差幾個數(shù)量級。又如,同樣是文件讀寫的實現(xiàn),使用Stream方式與JavaNIO方式,其性能可能又會相差一個數(shù)量級。因此,與設計優(yōu)化相比,雖然筆者將代碼優(yōu)化稱為在微觀層面上的優(yōu)化,但它卻是對系統(tǒng)性能產(chǎn)生最直接影響的優(yōu)化方法。1.2.3JVM調(diào)優(yōu)由于Java軟件總是運行在JVM虛擬機之上,因此對JVM虛擬機進行優(yōu)化也能在一定程度上提升Java程序的性能。JVM調(diào)優(yōu)通??梢栽谲浖_發(fā)后期進行,如在軟件開發(fā)完成或者在軟件開發(fā)的某一里程碑階段進行。作為Java軟件的運行平臺,JVM的各項參數(shù)如JVM的堆大小和垃圾回收策略等,將會直接影響Java程序的性能。要進行JVM層面的調(diào)優(yōu),需要開發(fā)人員對JVM的運行原理和基本內(nèi)存結構有一定了解,例如堆內(nèi)存的結構、GC的種類等,然后依據(jù)應用程序的特點,設置合理的JVM啟動參數(shù)。1.2.4數(shù)據(jù)庫調(diào)優(yōu)對絕大部分應用系統(tǒng)而言,數(shù)據(jù)庫是必不可少的一部分。Java程序可以使用JDBC的方式連接數(shù)據(jù)庫。對數(shù)據(jù)庫的調(diào)優(yōu)可以分為以下3個部分:·在應用層對SQL語句進行優(yōu)化;·對數(shù)據(jù)庫進行優(yōu)化;·對數(shù)據(jù)庫軟件進行優(yōu)化。在應用層優(yōu)化數(shù)據(jù)庫訪問,涉及大量的編程技巧。例如,當使用JDBC進行查詢時,對于大量擁有相同結構的SQL查詢,可以使用PreparedStatement代替Statement,提高數(shù)據(jù)庫的查詢效率;在Select語句中,顯示指定要查詢的列名,避免使用星號“*”。在對數(shù)據(jù)庫進行優(yōu)化時,主要目的是建立一個具有良好表結構的數(shù)據(jù)庫。例如,為了提高多表級聯(lián)查詢效率,可以合理地使用冗余字段;對于大表,可以使用行的水平切割或者類似于Oracle分區(qū)表的技術;為了提高數(shù)據(jù)庫的查詢效率,可以建立有效且合理的索引。對于數(shù)據(jù)庫軟件的優(yōu)化,根據(jù)不同的數(shù)據(jù)庫,如Oracle、MySQL或SQLServer,都擁有不同的方式。以Oracle為例,設置合理大小的共享池、緩存緩沖區(qū)或者PGA,對Oracle的運行性能都有很大的影響。鑒于本書的討論范圍,數(shù)據(jù)庫優(yōu)化將不作為本書的闡述重點。1.2.5操作系統(tǒng)調(diào)優(yōu)作為軟件運行的基礎平臺,操作系統(tǒng)的性能對應用系統(tǒng)也有較大的影響。不同類型的操作系統(tǒng),調(diào)優(yōu)的手段和參數(shù)可能會有所不同。例如,在主流UNIX系統(tǒng)中,共享內(nèi)存段、信號量、共享內(nèi)存最大值(shmmax)、共享內(nèi)存最小值(shmmin)等都是可以進行優(yōu)化的系統(tǒng)資源。此外,如最大文件句柄數(shù)、虛擬內(nèi)存大小、磁盤的塊大小等參數(shù)都可能對軟件的性能產(chǎn)生影響。圖1.5展示了在Windows平臺上配置虛擬內(nèi)存的界面。圖1.5在Windows上設置虛擬內(nèi)存說明:操作系統(tǒng)的性能調(diào)優(yōu)不在本書的討論范圍內(nèi),有興趣的讀者可以參考相關書籍。1.3基本調(diào)優(yōu)策略和手段存在性能問題的系統(tǒng),十有八九是由某一系統(tǒng)瓶頸導致的。只要找到該性能瓶頸,分析瓶頸的形成原因,對癥下藥,使用合理的方法解決系統(tǒng)瓶頸,就能從根本上提升性能。所以,系統(tǒng)性能優(yōu)化的最主要目的就是查找并解決性能瓶頸問題。但值得注意的是,性能優(yōu)化往往會涉及對原有的實現(xiàn)進行較大的修改,因此很難保證這些修改不引發(fā)新的問題。所以,在性能優(yōu)化前,需要對性能優(yōu)化的目標和使用的方法進行統(tǒng)籌安排。1.3.1優(yōu)化的一般步驟對軟件系統(tǒng)進行優(yōu)化,首先需要有明確的性能目標,清楚地指出優(yōu)化的對象和最終目的。其次,需要在目標平臺上對軟件進行測試,通過各種性能監(jiān)控和統(tǒng)計工具,觀測和確認當前系統(tǒng)是否已經(jīng)達到相關目標,若已經(jīng)達到,則沒有必要再進行優(yōu)化;若尚未達到優(yōu)化目標,則需要查找當前的性能瓶頸??赡艹蔀樾阅芷款i的因素很多,如磁盤I/O、網(wǎng)絡I/O和CPU。當找到性能瓶頸后,首先需要定位相關代碼,確認是否在軟件實現(xiàn)上存在問題或者具有優(yōu)化的空間。若存在優(yōu)化空間,則進行代碼優(yōu)化;否則需要考慮進行JVM層、數(shù)據(jù)庫層或者操作系統(tǒng)的優(yōu)化,甚至可以考慮修改原有設計,或者提升硬件性能。當優(yōu)化完成后,需要在目標平臺上進行確認測試。若達到性能目標,則優(yōu)化過程結束;否則需要再次查找系統(tǒng)瓶頸,如此反復,如圖1.6所示。圖1.6性能優(yōu)化的一般步驟1.3.2系統(tǒng)優(yōu)化的注意事項性能優(yōu)化雖然能提升軟件的性能,但是優(yōu)化過程往往伴隨著一些風險和弊端。例如,為了優(yōu)化某一段代碼的實現(xiàn),就需要重寫原有的算法,而這就很可能引入新的Bug。重新實現(xiàn)新的功能模塊同時也意味著需要重新對其進行完整的功能性測試,使優(yōu)化前所做的測試工作變得毫無意義。而且,優(yōu)化后的代碼與優(yōu)化前的代碼相比,可能會比較晦澀難懂,在一定程度上影響了系統(tǒng)的可維護性。因此,軟件優(yōu)化需要在軟件功能、正確性和可維護性之間取得平衡,而不應該過分地追求軟件性能。在進行優(yōu)化前,必須要有明確的已知問題和性能目標,決不可為了“優(yōu)化”而“優(yōu)化”。因此在動手前,必須知道自己要干什么。任何優(yōu)化都是為了解決具體的軟件問題,如果軟件已經(jīng)可以正常工作,在沒有性能問題暴露前,只是憑著主觀臆斷對某些模塊進行性能改進,從軟件規(guī)范化開發(fā)的角度來說是非常冒險的。因為修改后的新代碼沒有經(jīng)過完整的測試,軟件質(zhì)量就沒有保障。而且,優(yōu)化后的性能提升幅度可能并不足以讓開發(fā)者值得如此費盡心機。因此,在進行軟件優(yōu)化時,必須要進行慎重的評估。注意:性能調(diào)優(yōu)必須有明確的目標,不要為了調(diào)優(yōu)而調(diào)優(yōu)。如果當前程序并沒有明顯的性能問題,盲目地進行調(diào)優(yōu),其風險可能遠遠大于收益。1.4小結通過本章的學習,讀者應該了解性能的基本概念及常用的參考指標。此外,本章還較為詳細地介紹了與性能調(diào)優(yōu)相關的兩個重要理論——木桶原理和Amdahl定律。根據(jù)木桶原理,系統(tǒng)的最終性能總是由系統(tǒng)中性能最差的組件決定的,因此,改善該組件的性能對提升系統(tǒng)整體性能有重要的作用。而根據(jù)Amdahl定律可以知道,只是增加處理器數(shù)量對提升系統(tǒng)性能并沒有太大的實際意義,還必須同時提高程序的并行化比重。本章還簡要介紹了在軟件開發(fā)和維護過程中可以進行性能優(yōu)化的各個階段。例如,在軟件的設計階段,需要選用合理的軟件結構和性能組件;在編碼階段,需要提高代碼的執(zhí)行效率;對于Java應用程序,在系統(tǒng)的運行期,還需要設置合理的JVM虛擬機參數(shù);同時,優(yōu)化數(shù)據(jù)庫和操作系統(tǒng)也對系統(tǒng)整體性能有直接影響。在本章的最后還簡要介紹了性能優(yōu)化的一般步驟和注意事項。第2章設計優(yōu)化本章主要介紹與軟件設計相關的性能優(yōu)化方法和思想。軟件的結構對系統(tǒng)的整體性能有著重要的影響,優(yōu)秀的設計結構可以規(guī)避很多潛在的性能問題,對系統(tǒng)性能的影響可能遠遠大于對代碼的優(yōu)化。因此,熟悉一些常用的軟件設計模式和方法,對設計高性能軟件有很大幫助。本章著眼于設計優(yōu)化,主要講解一些與性能相關的常用設計模式、組件和設計方法。本章涉及的主要知識點有:·單例模式的使用和實現(xiàn);·代理模式的實現(xiàn)和深入剖析;·享元模式的應用;·裝飾者模式對性能組件的封裝;·觀察者模式的使用;·使用值對象模式減少網(wǎng)絡數(shù)據(jù)傳輸;·使用業(yè)務代理模式添加遠程調(diào)用緩存;·緩沖和緩存的定義與使用;·對象池的使用場景及其基本實現(xiàn);·負載均衡系統(tǒng)的構建及Terracotta框架的簡單使用;·時間換空間和空間換時間的基本思路。2.1善用設計模式設計模式是前人工作的總結和提煉。通常,被人們廣泛流傳的設計模式都是對某一特定問題的成熟解決方案。如果能合理地使用設計模式,不僅能使系統(tǒng)更容易被他人理解,同時也能使系統(tǒng)擁有更加合理的結構。本節(jié)總結歸納一些經(jīng)典的設計模式,并詳細說明它們與軟件性能之間的關系。2.1.1單例模式單例模式是設計模式中使用最為普遍的模式之一,它是一種對象創(chuàng)建模式,用于產(chǎn)生一個對象的具體實例,可以確保系統(tǒng)中一個類只產(chǎn)生一個實例。在Java語言中,這樣的行為能帶來兩大好處:(1)對于頻繁使用的對象,可以省去new操作花費的時間,這對于那些重量級對象而言,是一筆非??捎^的系統(tǒng)開銷。(2)由于new操作的次數(shù)減少,因而對系統(tǒng)內(nèi)存的使用頻率也會降低,這將減輕GC壓力,縮短GC停頓時間。因此對于系統(tǒng)的關鍵組件和被頻繁使用的對象,使用單例模式可以有效地改善系統(tǒng)的性能。單例模式的角色非常簡單,只有單例類和使用者兩個,如表2.1所示。表2.1單例模式的角色單例模式的基本結構如圖2.1所示。圖2.1單例模式的結構單例模式的核心在于通過一個接口返回唯一的對象實例。一個簡單的單例實現(xiàn)如下:publicclassSingleton{
privateSingleton(){
System.out.println("Singletoniscreate");//創(chuàng)建單例的過程可能會比較慢
}
privatestaticSingletoninstance=newSingleton();
publicstaticSingletongetInstance(){
returninstance;
}
}注意代碼中的重點標注部分,首先單例類必須要有一個private訪問級別的構造函數(shù),只有這樣,才能確保單例不會在系統(tǒng)的其他代碼內(nèi)被實例化,這一點是相當重要的。其次,instance成員變量和getInstance()方法必須是static的。注意:單例模式是一種非常常用的結構,幾乎所有的系統(tǒng)中都可以找到它的身影。因此,希望讀者通過本節(jié)的學習,了解單例模式的幾種實現(xiàn)方式及各自的特點。這種單例的實現(xiàn)方式非常簡單,而且十分可靠,唯一的不足僅是無法對instance做延遲加載。假如單例的創(chuàng)建過程很慢,而由于instance成員變量是static定義的,因此在JVM加載單例類時,單例對象就會被建立,如果此時這個單例類在系統(tǒng)中還扮演其他角色,那么在任何使用這個單例類的地方都會初始化這個單例變量,而不管是否會被用到。例如,單例類作為String工廠用于創(chuàng)建一些字符串(該類既用于創(chuàng)建單例,又用于創(chuàng)建String對象):publicclassSingleton{
privateSingleton(){
//創(chuàng)建單例的過程可能會比較慢
System.out.println("Singletoniscreate");
}
privatestaticSingletoninstance=newSingleton();
publicstaticSingletongetInstance(){
returninstance;
}
publicstaticvoidcreateString(){//這是模擬單例類扮演其他角色
System.out.println("createStringinSingleton");
}
}當使用Singleton.createString()執(zhí)行任務時,程序輸出以下內(nèi)容:Singletoniscreate
createStringinSingleton可以看到,雖然此時并沒有使用單例類,但它還是被創(chuàng)建出來,這也許是開發(fā)人員所不愿意見到的。為了解決這個問題,并提高系統(tǒng)在相關函數(shù)調(diào)用時的反應速度,就需要引入延遲加載機制。publicclassLazySingleton{
privateLazySingleton(){
//創(chuàng)建單例的過程可能會比較慢
System.out.println("LazySingletoniscreate");
}
privatestaticLazySingletoninstance=null;
publicstaticsynchronizedLazySingletongetInstance(){
if(instance==null)
instance=newLazySingleton();
returninstance;
}
}首先,對于靜態(tài)成員變量instance賦予初始值null,確保系統(tǒng)啟動時沒有額外的負載。其次,在getInstance()工廠方法中,判斷當前單例是否已經(jīng)存在,若存在,則返回,不存在,再建立單例。這里尤其要注意,getInstance()方法必須是同步的,否則在多線程環(huán)境下,當線程1正新建單例完成賦值操作前,線程2可能判斷instance為null,故線程2也將啟動新建單例的程序,從而導致多個實例被創(chuàng)建,因此同步關鍵字是必需步驟。使用上例中的單例,雖然實現(xiàn)了延遲加載的功能,但和第一種方法相比,它引入了同步關鍵字,因此在多線程環(huán)境中,它的時耗要遠遠大于第一種單例模式的時耗。以下測試代碼就說明了這個問題。@Override
publicvoidrun(){
for(inti=0;i<100000;i++)
Singleton.getInstance();
//LazySingleton.getInstance();
System.out.println("spend:"+(System.currentTimeMillis()-begintime));
}開啟5個線程同時完成以上代碼的運行,使用第一種單例耗時0ms,而使用LazySingleton卻相對耗時約390ms,性能至少相差2個數(shù)量級。注意:在本書中,會使用很多類似的代碼片段來測試不同代碼的執(zhí)行速度,在不同的計算機上其測試結果很可能與筆者不同。讀者大可不必關心測試數(shù)據(jù)的絕對值,而只要觀察用于比較的目標代碼間的相對耗時即可。為了使用延遲加載引入的同步關鍵字反而降低了系統(tǒng)性能,是不是有點得不償失呢?為了解決這個問題,還需要對其進行以下改進:publicclassStaticSingleton{
privateStaticSingleton(){
System.out.println("StaticSingletoniscreate");
}
privatestaticclassSingletonHolder{
privatestaticStaticSingletoninstance=newStaticSingleton();
}
publicstaticStaticSingletongetInstance(){
returnSingletonHolder.instance;
}
}在這個實現(xiàn)中,單例模式使用內(nèi)部類來維護單例的實例,當StaticSingleton被加載時,其內(nèi)部類并不會被初始化,故可以確保當StaticSingleton類被載入JVM時不會初始化單例類,而當getInstance()方法被調(diào)用時才會加載SingletonHolder,從而初始化instance。同時,由于實例的建立是在類加載時完成的,故天生對多線程友好,getInstance()方法也不需要使用同步關鍵字。因此,這種實現(xiàn)方式同時兼?zhèn)湟陨蟽煞N實現(xiàn)方式的優(yōu)點。注意:使用內(nèi)部類的方式實現(xiàn)單例,既可以做到延遲加載,也不必使用同步關鍵字,是一種比較完善的實現(xiàn)方式。通常情況下,用以上方式實現(xiàn)的單例已經(jīng)可以確保在系統(tǒng)中只存在唯一實例了。但仍然有例外情況——可能導致系統(tǒng)生成多個實例,例如在代碼中通過反射機制,強行調(diào)用單例類的私有構造函數(shù)生成多個單例??紤]到情況的特殊性,本書不對這種極端的方式進行討論。但仍有些合法的方法,可能導致系統(tǒng)出現(xiàn)多個單例類的實例。以下是一個可以被串行化的單例:publicclassSerSingletonimplementsjava.io.Serializable{
Stringname;
privateSerSingleton(){
//創(chuàng)建單例的過程可能會比較慢
System.out.println("Singletoniscreate");
name="SerSingleton";
}
privatestaticSerSingletoninstance=newSerSingleton();
publicstaticSerSingletongetInstance(){
returninstance;
}
publicstaticvoidcreateString(){
System.out.println("createStringinSingleton");
}
privateObjectreadResolve(){//阻止生成新的實例,總是返回當前對象
returninstance;
}
}測試代碼如下:@Test
publicvoidtest()throwsException{
SerSingletons1=null;
SerSingletons=SerSingleton.getInstance();
//先將實例串行化到文件
FileOutputStreamfos=newFileOutputStream("SerSingleton.txt");
ObjectOutputStreamoos=newObjectOutputStream(fos);
oos.writeObject(s);
oos.flush();
oos.close();
//從文件讀出原有的單例類
FileInputStreamfis=newFileInputStream("SerSingleton.txt");
ObjectInputStreamois=newObjectInputStream(fis);
s1=(SerSingleton)ois.readObject();
Assert.assertEquals(s,s1);
}使用一段測試代碼測試單例的串行化和反串行化,當去掉SerSingleton代碼中加粗的readResolve()函數(shù)時,測試代碼拋出以下異常:junit.framework.AssertionFailedError:expected:javatuning.ch2.singleton.serialization.SerSingleton@5224ee
butwas:javatuning.ch2.singleton.serialization.SerSingleton@18fe7c3這說明測試代碼中的s和s1指向了不同的實例,在反序列化后生成了多個對象實例。而加上readResolve()函數(shù)的程序則正常退出,這說明即便經(jīng)過反序列化,也仍然保持單例的特征。事實上,在實現(xiàn)了私有的readResolve()方法后,readObject()方法已經(jīng)形同虛設,它直接使用readResolve()替換了原本的返回值,從而在形式上構造了單例。注意:序列化和反序列化可能會破壞單例。一般來說,對單例進行序列化和反序列化的場景并不多見,但如果存在,就要多加注意。2.1.2代理模式代理模式也是一種很常見的設計模式,它使用代理對象完成用戶請求,屏蔽用戶對真實對象的訪問。就如同現(xiàn)實中的代理一樣,代理人被授權執(zhí)行當事人的一些事宜,而無須當事人出面,從第三方的角度看,似乎當事人并不存在,因為他只和代理人通信。而事實上,代理人要有當事人的授權,并且在核心問題上還需要請示當事人。在現(xiàn)實中,使用代理的情況很普遍,而且原因也很多。例如:當事人因為某些隱私不方便出面,或者當事人不具備某些相關的專業(yè)技能,而需要一個專業(yè)人員來完成一些專業(yè)的操作;由于當事人沒有時間處理事務,而聘用代理人出面。在軟件設計中,使用代理模式的意圖也很多,例如,出于安全原因,需要屏蔽客戶端直接訪問真實對象;在遠程調(diào)用中,需要使用代理類處理遠程方法調(diào)用的技術細節(jié)(如RMI);為了提升系統(tǒng)性能,對真實對象進行封裝,從而達到延遲加載的目的。在本小節(jié)中,主要討論使用代理模式實現(xiàn)延遲加載,從而提升系統(tǒng)的性能和反應速度。1.代理模式的結構代理模式的主要角色有4個,如表2.2所示。表2.2代理模式的角色下面以一個簡單的示例來闡述使用代理模式實現(xiàn)延遲加載的方法及其意義。假設某客戶端軟件有根據(jù)用戶請求去數(shù)據(jù)庫查詢數(shù)據(jù)的功能,在查詢數(shù)據(jù)前需要獲得數(shù)據(jù)庫連接,軟件開啟時初始化系統(tǒng)的所有類,此時嘗試獲得數(shù)據(jù)庫連接。當系統(tǒng)有大量的類似操作(如xml解析等)存在時,所有這些初始化操作的疊加會使得系統(tǒng)的啟動速度變得非常緩慢。為此,可以使用代理模式的代理類封裝對數(shù)據(jù)庫查詢中的初始化操作,當系統(tǒng)啟動時初始化這個代理類,而非初始化真實的數(shù)據(jù)庫查詢類,在此過程中代理類什么都沒有做,因此它的構造是相當迅速的。在系統(tǒng)啟動時,將消耗資源最多的方法都使用代理模式分離,這樣就可以加快系統(tǒng)的啟動速度,從而減少用戶的等待時間。而在用戶真正做查詢操作時,再由代理類單獨去加載真實的數(shù)據(jù)庫查詢類,從而完成用戶的請求。這個過程就是使用代理模式實現(xiàn)了延遲加載。注意:代理模式可以用于多種場合,如用于遠程調(diào)用的網(wǎng)絡代理,以及考慮安全因素的安全代理等。延遲加載只是代理模式的一種應用場景。延遲加載的核心思想是:如果當前并沒有使用這個組件,則不需要真正地初始化它,而是使用一個代理對象替代它原有的位置,只有在真正需要使用的時候,才對它進行加載。使用代理模式的延遲加載是非常有意義的:首先,它可以在時間軸上分散系統(tǒng)的壓力,尤其是在系統(tǒng)啟動時,不必完成所有的初始化工作,從而減少啟動時間;其次,對于很多真實主題而言,可能在軟件啟動直到關閉的整個過程中根本不會被調(diào)用,因此初始化這些數(shù)據(jù)無疑是一種資源浪費。圖2.2顯示了使用代理類封裝數(shù)據(jù)庫查詢類后系統(tǒng)的啟動過程。圖2.2代理類的工作流程若系統(tǒng)不使用代理模式,則在啟動時就要初始化DBQuery對象,而使用代理模式后,啟動時只需要初始化一個輕量級的對象DBQueryProxy即可。系統(tǒng)結構如圖2.3所示。IDBQuery是主題接口,定義代理類和真實類需要對外提供的服務,在本例中定義了實現(xiàn)數(shù)據(jù)庫查詢的公共方法,即request()函數(shù)。DBQuery是真實主題,負責實際的業(yè)務操作,DBQueryProxy是DBQuery的代理類。圖2.3代理模式的一種實現(xiàn)2.代理模式的實現(xiàn)和使用基于以上設計,IDBQuery的實現(xiàn)方式如下。它只有一個request()方法。publicinterfaceIDBQuery{
Stringrequest();
}DBQuery的實現(xiàn)方式如下。它是一個重量級對象,構造會比較慢。publicclassDBQueryimplementsIDBQuery{
publicDBQuery(){
try{
Thread.sleep(1000);//可能包含數(shù)據(jù)庫連接等耗時操作
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
@Override
publicStringrequest(){
return"requeststring";
}
}代理類DBQueryProxy是輕量級對象,創(chuàng)建很快,用于替代DBQuery的位置。publicclassDBQueryProxyimplementsIDBQuery{
privateDBQueryreal=null;
@Override
publicStringrequest(){
//在真正需要的時候才創(chuàng)建真實的對象,創(chuàng)建過程可能很慢
if(real==null)
real=newDBQuery();
//在多線程環(huán)境下,返回一個虛假類,類似于Future模式
returnreal.request();
}
}最后,主函數(shù)如下。它引用IDBQuery接口,并使用代理類工作。publicclassMain{
publicstaticvoidmain(Stringargs[]){
IDBQueryq=newDBQueryProxy();//使用代理
q.request();//在真正使用時才創(chuàng)建真實對象
}
}注意:將代理模式用于實現(xiàn)延遲加載,可以有效地提升系統(tǒng)的啟動速度,對改善用戶體驗有很大的幫助。3.動態(tài)代理介紹動態(tài)代理是指在運行時動態(tài)生成代理類,即代理類的字節(jié)碼將在運行時生成并載入當前的ClassLoader。與靜態(tài)代理類相比,動態(tài)類有諸多好處。首先,不需要為真實主題寫一個形式上完全一樣的封裝類,假如主題接口中的方法很多,為每一個接口寫一個代理方法也是非常煩人的事,如果接口有變動,則真實主題和代理類都要修改,不利于系統(tǒng)維護;其次,使用一些動態(tài)代理的生成方法甚至可以在運行時指定代理類的執(zhí)行邏輯,從而大大提升系統(tǒng)的靈活性。注意:動態(tài)代理使用字節(jié)碼動態(tài)生成加載技術,在運行時生成并加載類。生成動態(tài)代理類的方法很多,如JDK自帶的動態(tài)代理、CGLIB、Javassist或者ASM庫。JDK的動態(tài)代理使用簡單,內(nèi)置在JDK中,因此不需要引入第三方Jar包,但功能相對比較弱。CGLIB和Javassist都是高級字節(jié)碼生成庫,總體性能比JDK自帶的動態(tài)代理好,而且功能十分強大。ASM是低級字節(jié)碼生成工具,使用ASM已經(jīng)近乎于在使用Java字節(jié)碼編程,對開發(fā)人員要求最高,當然也是一種性能最好的動態(tài)代理生成工具。但ASM的使用實在過于煩瑣,而且性能也沒有數(shù)量級的提升,與CGLIB等高級字節(jié)碼生成工具相比,ASM程序的可維護性也較差,如果不是對性能有苛刻要求的場合,筆者還是推薦使用CGLIB或者Javassist。4.動態(tài)代理實現(xiàn)仍以DBQueryProxy為例,使用動態(tài)代理生成動態(tài)類,替代上例中的DBQueryProxy。首先,使用JDK的動態(tài)代理生成代理對象。JDK的動態(tài)代理需要實現(xiàn)一個處理方法調(diào)用的Handler,用于實現(xiàn)代理方法的內(nèi)部邏輯。publicclassJdkDbQueryHandlerimplementsInvocationHandler{
IDBQueryreal=null;//主題接口
@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
throwsThrowable{
if(real==null)
real=newDBQuery();//如果是第一次調(diào)用,則生成真實對象
returnreal.request();//使用真實主題完成實際操作
}
}以上代碼實現(xiàn)了一個Handler??梢钥吹?,它的內(nèi)部邏輯和DBQueryProxy是類似的。在調(diào)用真實主題的方法前,先嘗試生成真實主題對象,接著需要使用這個Handler生成動態(tài)代理對象,代碼如下:publicstaticIDBQuerycreateJdkProxy(){
IDBQueryjdkProxy=(IDBQuery)Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
newClass[]{IDBQuery.class},
newJdkDbQueryHandler());//指定Handler
returnjdkProxy;
}以上代碼生成一個實現(xiàn)了IDBQuery接口的代理類,代理類的內(nèi)部邏輯由JdkDbQuery-Handler決定。生成代理類后,由newProxyInstance()方法返回該代理類的一個實例。至此,一個完整的JDK動態(tài)代理就完成了。CGLIB和Javassist的使用和JDK的動態(tài)代理的使用非常類似,下面嘗試使用CGLIB生成動態(tài)代理。CGLIB也需要實現(xiàn)一個處理代理邏輯的切入類,代碼如下:publicclassCglibDbQueryInterceptorimplementsMethodInterceptor{
IDBQueryreal=null;
@Override
publicObjectintercept(Objectarg0,Methodarg1,Object[]arg2,
MethodProxyarg3)throwsThrowable{
if(real==null)//代理類的內(nèi)部邏輯
//和前文中的一樣
real=newDBQuery();
returnreal.request();
}
}在這個切入對象的基礎上可以生成動態(tài)代理,代碼如下:publicstaticIDBQuerycreateCglibProxy(){
Enhancerenhancer=newEnhancer();
//指定切入器,定義代理類邏輯
enhancer.setCallback(newCglibDbQueryInterceptor());
//指定實現(xiàn)的接口
enhancer.setInterfaces(newClass[]{IDBQuery.class});
//生成代理類的實例
IDBQuerycglibProxy=(IDBQuery)enhancer.create();
returncglibProxy;
}使用Javassist生成動態(tài)代理有兩種方式,一種是使用代理工廠創(chuàng)建,另一種是使用動態(tài)代碼創(chuàng)建。使用代理工廠創(chuàng)建時,方法與CGLIB類似,也需要實現(xiàn)一個用于處理代理邏輯的Handler,代碼如下:publicclassJavassistDynDbQueryHandlerimplementsMethodHandler{
IDBQueryreal=null;
@Override
publicObjectinvoke(Objectarg0,Methodarg1,Methodarg2,Object[]arg3)
throwsThrowable{
if(real==null)
real=newDBQuery();
returnreal.request();
}
}以這個Handler為基礎,創(chuàng)建動態(tài)Javasssit代理,代碼如下:publicstaticIDBQuerycreateJavassistDynProxy()throwsException{
ProxyFactoryproxyFactory=newProxyFactory();
//指定接口
proxyFactory.setInterfaces(newClass[]{IDBQuery.class});
ClassproxyClass=proxyFactory.createClass();
IDBQueryjavassistProxy=(IDBQuery)proxyClass.newInstance();
//設置Handler
((ProxyObject)javassistProxy).setHandler(newJavassistDynDbQueryHandler());
returnjavassistProxy;
}Javassist使用動態(tài)Java代碼創(chuàng)建代理的過程和前文的方法略有不同。Javassist內(nèi)部可以通過動態(tài)Java代碼生成字節(jié)碼,這種方式創(chuàng)建的動態(tài)代理非常靈活,甚至可以在運行時生成業(yè)務邏輯。publicstaticIDBQuerycreateJavassistBytecodeDynamicProxy()throwsException{
ClassPoolmPool=newClassPool(true);
//定義類名
CtClassmCtc=mPool.makeClass(IDBQuery.class.getName()+"Javassist
BytecodeProxy");
//需要實現(xiàn)的接口
mCtc.addInterface(mPool.get(IDBQuery.class.getName()));
//添加構造函數(shù)
mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
//添加類的字段信息,使用動態(tài)Java代碼
mCtc.addField(CtField.make("public"+IDBQuery.class.getName()+
"real;",mCtc));
Stringdbqueryname=DBQuery.class.getName();
//添加方法,這里使用動態(tài)Java代碼指定內(nèi)部邏輯
mCtc.addMethod(CtNewMethod.make("publicStringrequest(){if(real==
null)real=new"+dbqueryname+"();returnreal.request();}",mCtc));
//基于以上信息生成動態(tài)類
Classpc=mCtc.toClass();
//生成動態(tài)類的實例
IDBQuerybytecodeProxy=(IDBQuery)pc.newInstance();
returnbytecodeProxy;
}在以上代碼中,使用CtField.make()方法和CtNewMehod.make()方法在運行時分別生成了代理類的字段和方法。這些邏輯由Javassist的CtClass對象處理,將Java代碼轉(zhuǎn)換為對應的字節(jié)碼,并生成動態(tài)代理類的實例。注意:與靜態(tài)代理相比,動態(tài)代理可以大幅度地減少代碼行數(shù),并提升系統(tǒng)的靈活性。在Java中,動態(tài)代理類的生成主要涉及對ClassLoader的使用。這里以CGLIB為例,簡要闡述動態(tài)類的加載過程。使用CGLIB生成動態(tài)代理,首先需要生成Enhancer類實例,并指定用于處理代理業(yè)務的回調(diào)類。在Enhancer.create()方法中,會使用DefaultGenerator-Strategy.Generate()方法生成動態(tài)代理類的字節(jié)碼,并保存在byte數(shù)組中。接著使用ReflectUtils.define-Class()方法通過反射調(diào)用ClassLoader.defineClass()方法,將字節(jié)碼裝載到ClassLoader中,完成類的加載。最后使用ReflectUtils.newInstance()方法通過反射生成動態(tài)類的實例,并返回該實例。無論使用何種方法生成動態(tài)代理,雖然實現(xiàn)細節(jié)不同,但主要邏輯都如圖2.4所示。圖2.4實現(xiàn)動態(tài)代理的基本步驟前文介紹的幾種動態(tài)代理的生成方法,性能有一定差異。為了能更好地測試它們的性能,去掉DBQuery類中的sleep()代碼,并使用以下方法進行測試。publicstaticfinalintCIRCLE=30000000;
publicstaticvoidmain(String[]args)throwsException{
IDBQueryd=null;
longbegin=System.currentTimeMillis();
d=createJdkProxy();//測試JDK動態(tài)代理
System.out.println("createJdkProxy:"+(System.currentTimeMillis()-begin));
System.out.println("JdkProxyclass:"+d.getClass().getName());
begin=System.currentTimeMillis();
for(inti=0;i<CIRCLE;i++)
d.request();
System.out.println("callJdkProxy:"+(System.currentTimeMillis()-begin));
begin=System.currentTimeMillis();
d=createCglibProxy();//測試CGLIB動態(tài)代理
System.out.println("createCglibProxy:"+(System.currentTimeMillis()
-begin));
System.out.println("CglibProxyclass:"+d.getClass().getName());
begin=System.currentTimeMillis();
for(inti=0;i<CIRCLE;i++)
d.request();
System.out.println("callCglibProxy:"+(System.currentTimeMillis()
-begin));
begin=System.currentTimeMillis();
d=createJavassistDynProxy();//測試Javassist動態(tài)代理
System.out.println("createJavassistDynProxy:"+(System.currentTimeMillis()
-begin));
System.out.println("JavassistDynProxyclass:"+d.getClass().getName());
begin=System.currentTimeMillis();
for(inti=0;i<CIRCLE;i++)
d.request();
System.out.println("callJavassistDynProxy:"+(System.currentTimeMillis()
-begin));
begin=System.currentTimeMillis();
//測試Javassistbytecode動態(tài)代理
d=createJavassistBytecodeDynamicProxy();
System.out.println("createJavassistBytecodeDynamicProxy:"+(System.
currentTimeMillis()-begin));
System.out.println("JavassistBytecodeDynamicProxyclass:"+d.getClass().
getName());
begin=System.currentTimeMillis();
for(inti=0;i<CIRCLE;i++)
d.request();
System.out.println("callJavassistBytecodeDynamicProxy:"+(System.
currentTimeMillis()-begin));
}以上代碼分別生成了4種代理,并對生成的代理類進行高頻率的調(diào)用,最后輸出各個代理類的創(chuàng)建耗時、動態(tài)類類名和方法調(diào)用耗時。結果如下:createJdkProxy:0
JdkProxyclass:$Proxy0
callJdkProxy:610
createCglibProxy:140
CglibProxyclass:$xy.IDBQuery$$EnhancerByCGLIB$$b75a4bbf
callCglibProxy:594
createJavassistDynProxy:47
JavassistDynProxyclass:xy.IDBQuery_$$_javassist_0
callJavassistDynProxy:1422
createJavassistBytecodeDynamicProxy:94
JavassistBytecodeDynamicProxyclass:xy.IDBQueryJavassistBytecodeProxy
callJavassistBytecodeDynamicProxy:562可以看到,JDK的動態(tài)類創(chuàng)建過程最快,這是因為在這個內(nèi)置實現(xiàn)中,defineClass()方法被定義為native實現(xiàn),故性能高于其他幾種實現(xiàn)。但在代理類的函數(shù)調(diào)用性能上,JDK的動態(tài)代理不如CGLIB和Javassist的基于動態(tài)代碼的代理,而Javassist的基于代理工廠的代理實現(xiàn),代理的性能質(zhì)量最差,甚至不如JDK的實現(xiàn)。在實際開發(fā)應用中,代理類的方法調(diào)用頻率通常要遠遠高于代理類的實際生成頻率(相同類的重復生成會使用Cache),故動態(tài)代理對象的方法調(diào)用性能應該作為性能的主要關注點。注意:就動態(tài)代理的方法調(diào)用性能而言,CGLIB和Javassist的基于動態(tài)代碼的代理都優(yōu)于JDK自帶的動態(tài)代理。此外,JDK的動態(tài)代理要求代理類和真實主題都實現(xiàn)同一個接口,而CGLIB和Javassist則沒有強制要求。5.Hibernate中代理模式的應用用代理模式實現(xiàn)延遲加載的一個經(jīng)典應用就在Hibernate框架中。當Hibernate加載實體bean時,并不會一次性將數(shù)據(jù)庫所有的數(shù)據(jù)都裝載。默認情況下,它會采取延遲加載的機制,以提高系統(tǒng)的性能。Hibernate中的延遲加載主要有兩種:一是屬性的延遲加載;二是關聯(lián)表的延時加載。這里以屬性的延遲加載為例,簡單闡述Hibernate是如何使用動態(tài)代理的。假定有以下用戶模型:publicclassUserimplementsjava.io.Serializable{
privateIntegerid;
privateStringname;
privateintage;
//省略getter和setter使用以下代碼,通過Hibernate加載一條User信息:publicstaticvoidmain(String[]args)throwsSecurityException,
NoSuchFieldException,
IllegalArgumentException,
IllegalAccessException{
//從數(shù)據(jù)庫載入ID為1的用戶
Useru=(User)HibernateSessionFactory.getSession().load(User.class,1);
//打印類名稱
System.out.println("ClassName:"+u.getClass().getName());
//打印父類名稱
System.out.println("SuperClassName:"+u.getClass().getSuperclass().
getName());
//實現(xiàn)的所有接口
Class[]ins=u.getClass().getInterfaces();
for(Classcls:ins){
System.out.println("interface:"+cls.getName());
}
System.out.println(u.getName());
}以上代碼在執(zhí)行l(wèi)oad(User.class.1)后,首先輸出了User的類名、父類名以及User實現(xiàn)的接口,最后輸出調(diào)用User的getName()方法,取得數(shù)據(jù)庫中的數(shù)據(jù)。這段程序的輸出結果如下(本例中使用的是Hibernate3.2.6,不同的Hibernate版本會有細節(jié)上的差異):ClassName:$xy.hibernate.User$
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 防暑降溫的宣傳橫幅標語(60句)
- 《供配電技術》2.1 教案
- 銷售業(yè)務年終的總結
- 投標單位授權委托書(10篇)
- 有關青春的三分鐘演講稿(34篇)
- 內(nèi)蒙古呼倫貝爾市(2024年-2025年小學五年級語文)人教版課后作業(yè)(上學期)試卷及答案
- 2024年人造原油項目資金申請報告代可行性研究報告
- 計算機應用基礎教案
- 上海市縣(2024年-2025年小學五年級語文)統(tǒng)編版階段練習((上下)學期)試卷及答案
- 四年級數(shù)學(簡便運算)計算題專項練習與答案
- XX銀行信息系統(tǒng)軟件版本管理辦法
- 叉車選型的注意點
- 第一單元 計算機中的編碼 課件 初中信息技術七年級上冊
- 燒結過程中氮氧化物生成機理及控制
- GB/T 26832-2011無損檢測儀器鋼絲繩電磁檢測儀技術條件
- GB/T 11375-1999金屬和其他無機覆蓋層熱噴涂操作安全
- GA 1800.3-2021電力系統(tǒng)治安反恐防范要求第3部分:水力發(fā)電企業(yè)
- 英屬哥倫比亞大學PPT
- 《文獻閱讀》課件
- 2022年新版《建設工程工程量清單計價規(guī)范》
評論
0/150
提交評論