Java程序性能優(yōu)化實(shí)戰(zhàn)_第1頁(yè)
Java程序性能優(yōu)化實(shí)戰(zhàn)_第2頁(yè)
Java程序性能優(yōu)化實(shí)戰(zhàn)_第3頁(yè)
Java程序性能優(yōu)化實(shí)戰(zhàn)_第4頁(yè)
Java程序性能優(yōu)化實(shí)戰(zhàn)_第5頁(yè)
已閱讀5頁(yè),還剩506頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

Java程序性能優(yōu)化實(shí)戰(zhàn)目錄TOC\h\h第1章Java性能調(diào)優(yōu)概述\h1.1性能概述\h1.1.1看懂程序的性能\h1.1.2性能的參考指標(biāo)\h1.1.3木桶原理與性能瓶頸\h1.1.4Amdahl定律\h1.2性能調(diào)優(yōu)的層次\h1.2.1設(shè)計(jì)調(diào)優(yōu)\h1.2.2代碼調(diào)優(yōu)\h1.2.3JVM調(diào)優(yōu)\h1.2.4數(shù)據(jù)庫(kù)調(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)化的注意事項(xiàng)\h1.4小結(jié)\h第2章設(shè)計(jì)優(yōu)化\h2.1善用設(shè)計(jì)模式\h2.1.1單例模式\h2.1.2代理模式\h2.1.3享元模式\h2.1.4裝飾者模式\h2.1.5觀察者模式\h2.1.6值對(duì)象模式\h2.1.7業(yè)務(wù)代理模式\h2.2常用的優(yōu)化組件和方法\h2.2.1緩沖\h2.2.2緩存\h2.2.3對(duì)象復(fù)用——池\h2.2.4并行替代串行\(zhòng)h2.2.5負(fù)載均衡\h2.2.6時(shí)間換空間\h2.2.7空間換時(shí)間\h2.3小結(jié)\h第3章Java程序優(yōu)化\h3.1字符串優(yōu)化處理\h3.1.1String對(duì)象及其特點(diǎn)\h3.1.2substring()方法的內(nèi)存泄漏\h3.1.3字符串分割和查找\h3.1.4StringBuffer和StringBuilder\h3.1.5CompactStrings優(yōu)化字符串存儲(chǔ)\h3.2核心數(shù)據(jù)結(jié)構(gòu)\h3.2.1List接口\h3.2.2Map接口\h3.2.3Set接口\h3.2.4優(yōu)化集合訪問(wèn)代碼\h3.2.5RandomAccess接口\h3.3使用NIO提升性能\h3.3.1NIO中的Buffer類族和Channel\h3.3.2Buffer的基本原理\h3.3.3Buffer的相關(guān)操作\h3.3.4MappedByteBuffer性能評(píng)估\h3.3.5直接訪問(wèn)內(nèi)存\h3.4引用類型\h3.4.1強(qiáng)引用\h3.4.2軟引用\h3.4.3弱引用\h3.4.4虛引用\h3.4.5WeakHashMap類及其實(shí)現(xiàn)\h3.5性能測(cè)試工具JMH\h3.5.1JMH之HelloWorld\h3.5.2JMH之指定測(cè)量模式\h3.5.3JMH之對(duì)象作用域\h3.5.4JMH之代碼消除\h3.6有助于改善性能的技巧\h3.6.1使用局部變量\h3.6.2位運(yùn)算代替乘除法\h3.6.3替換switch\h3.6.4一維數(shù)組代替二維數(shù)組\h3.6.5提取表達(dá)式\h3.6.6展開(kāi)循環(huán)\h3.6.7布爾運(yùn)算代替位運(yùn)算\h3.6.8使用arrayCopy()\h3.6.9使用Buffer進(jìn)行I/O操作\h3.6.10使用clone()代替new\h3.6.11慎用Java函數(shù)式編程\h3.7小結(jié)\h第4章并行程序開(kāi)發(fā)及優(yōu)化\h4.1并行程序設(shè)計(jì)模式\h4.1.1Future模式\h4.1.2Master-Worker模式\h4.1.3GuardedSuspension模式\h4.1.4不變模式\h4.1.5生產(chǎn)者-消費(fèi)者模式\h4.2JDK多任務(wù)執(zhí)行框架\h4.2.1無(wú)限制線程的缺陷\h4.2.2簡(jiǎn)單的線程池實(shí)現(xiàn)\h4.2.3Executor框架\h4.2.4自定義線程池\h4.2.5優(yōu)化線程池大小\h4.2.6擴(kuò)展ThreadPoolExecutor\h4.3JDK并發(fā)數(shù)據(jù)結(jié)構(gòu)\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同步關(guān)鍵字synchronized\h4.4.3重入鎖\h4.4.4讀寫(xiě)鎖\h4.4.5讀寫(xiě)鎖的改進(jìn):StampedLock\h4.4.6Condition對(duì)象\h4.4.7信號(hào)量\h4.4.8線程局部變量ThreadLocal\h4.5鎖的性能和優(yōu)化\h4.5.1線程的開(kāi)銷\h4.5.2避免死鎖\h4.5.3減少鎖持有時(shí)間\h4.5.4減小鎖粒度\h4.5.5讀寫(xiě)分離鎖來(lái)替換獨(dú)占鎖\h4.5.6鎖分離\h4.5.7重入鎖和內(nèi)部鎖\h4.5.8鎖粗化\h4.5.9自旋鎖\h4.5.10鎖消除\h4.5.11鎖偏向\h4.6無(wú)鎖的并行計(jì)算\h4.6.1非阻塞的同步/無(wú)鎖\h4.6.2原子操作\h4.6.3Amino框架簡(jiǎn)介\h4.6.4Amino集合\h4.6.5Amino樹(shù)\h4.6.6Amino圖\h4.6.7Amino簡(jiǎn)單調(diào)度模式\h4.7協(xié)程\h4.7.1協(xié)程的概念\h4.7.2Kilim框架簡(jiǎn)介\h4.7.3Task及其狀態(tài)\h4.7.4Fiber及其狀態(tài)\h4.7.5Kilim開(kāi)發(fā)環(huán)境配置\h4.7.6Kilim之HelloWorld\h4.7.7多任務(wù)通信\h4.7.8Kilim實(shí)例及性能評(píng)估\h4.8小結(jié)\h第5章JVM調(diào)優(yōu)\h5.1Java虛擬機(jī)內(nèi)存模型\h5.1.1程序計(jì)數(shù)器\h5.1.2Java虛擬機(jī)棧\h5.1.3本地方法棧\h5.1.4Java堆\h5.1.5方法區(qū)\h5.2JVM內(nèi)存分配參數(shù)\h5.2.1設(shè)置最大堆內(nèi)存\h5.2.2設(shè)置最小堆內(nèi)存\h5.2.3設(shè)置新生代\h5.2.4設(shè)置持久代\h5.2.5設(shè)置線程棧\h5.2.6堆的比例分配\h5.2.7堆分配參數(shù)總結(jié)\h5.3垃圾收集基礎(chǔ)\h5.3.1垃圾收集的作用\h5.3.2垃圾回收算法與思想\h5.3.3垃圾回收器的類型\h5.3.4評(píng)價(jià)GC策略的指標(biāo)\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垃圾回收器對(duì)系統(tǒng)性能的影響\h5.3.14GC操作相關(guān)參數(shù)總結(jié)\h5.4常用調(diào)優(yōu)案例和方法\h5.4.1將新對(duì)象預(yù)留在新生代\h5.4.2大對(duì)象進(jìn)入老年代\h5.4.3設(shè)置對(duì)象進(jìn)入老年代的年齡\h5.4.4穩(wěn)定與振蕩的堆大小\h5.4.5吞吐量?jī)?yōu)先案例\h5.4.6使用大頁(yè)案例\h5.4.7降低停頓案例\h5.5實(shí)用JVM參數(shù)\h5.5.1JIT編譯參數(shù)\h5.5.2堆快照\(chéng)h5.5.3錯(cuò)誤處理\h5.5.4獲取GC信息\h5.5.5類和對(duì)象跟蹤\h5.5.6控制GC\h5.5.7選擇類校驗(yàn)器\h5.5.8Solaris下的線程控制\h5.5.9使用大頁(yè)\h5.5.10壓縮指針\h5.6JVM調(diào)優(yōu)實(shí)戰(zhàn)\h5.6.1Tomcat簡(jiǎn)介與啟動(dòng)加速\h5.6.2Web應(yīng)用程序簡(jiǎn)介\h5.6.3JMeter簡(jiǎn)介與使用\h5.6.4調(diào)優(yōu)前Web應(yīng)用運(yùn)行狀況\h5.6.5調(diào)優(yōu)過(guò)程\h5.7小結(jié)\h第6章Java性能調(diào)優(yōu)工具\(yùn)h6.1Linux命令行工具\(yùn)h6.1.1top命令\h6.1.2sar命令\h6.1.3vmstat命令\h6.1.4iostat命令\h6.1.5pidstat工具\(yùn)h6.2Windows工具\(yùn)h6.2.1任務(wù)管理器\h6.2.2perfmon性能監(jiān)控工具\(yùn)h6.2.3ProcessExplorer工具\(yùn)h6.2.4pslist命令行\(zhòng)h6.3JDK命令行工具\(yùn)h6.3.1jps命令\h6.3.2jstat命令\h6.3.3jinfo命令\h6.3.4jmap命令\h6.3.5jhat命令\h6.3.6jstack命令\h6.3.7jstatd命令\h6.3.8hprof工具\(yùn)h6.3.9jcmd命令\h6.4JConsole工具\(yùn)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虛擬機(jī)信息\h6.4.7MBean管理\h6.4.8使用插件\h6.5VisualVM多合一工具\(yùn)h6.5.1VisualVM連接應(yīng)用程序\h6.5.2監(jiān)控應(yīng)用程序概況\h6.5.3ThreadDump和分析\h6.5.4性能分析\h6.5.5快照\(chéng)h6.5.6內(nèi)存快照分析\h6.5.7MBean管理功能\h6.5.8TDA的使用\h6.5.9BTrace簡(jiǎn)介\h6.6VisualVM對(duì)OQL的支持\h6.6.1VisualVM的OQL基本語(yǔ)法\h6.6.2內(nèi)置heap對(duì)象\h6.6.3對(duì)象函數(shù)\h6.6.4集合/統(tǒng)計(jì)函數(shù)\h6.6.5程序化OQL\h6.7MAT內(nèi)存分析工具\(yùn)h6.7.1初識(shí)MAT\h6.7.2淺堆和深堆\h6.7.3支配樹(shù)\h6.7.4垃圾回收根\h6.7.5內(nèi)存泄漏檢測(cè)\h6.7.6最大對(duì)象報(bào)告\h6.7.7查找支配者\(yùn)h6.7.8線程分析\h6.7.9集合使用情況分析\h6.7.10擴(kuò)展MAT\h6.8MAT對(duì)OQL的支持\h6.8.1Select子句\h6.8.2From子句\h6.8.3Where子句\h6.8.4內(nèi)置對(duì)象與方法\h6.9來(lái)自JRockit的禮物——JMC\h6.9.1得到JFR文件\h6.9.2Java程序的整體運(yùn)行情況\h6.9.3CPU分析\h6.9.4內(nèi)存分析\h6.9.5I/O分析\h6.10小結(jié)第1章Java性能調(diào)優(yōu)概述本章將對(duì)性能優(yōu)化技術(shù)進(jìn)行整體性概述,讓讀者了解性能的概念和性能優(yōu)化的基本思路和方法。掌握這些內(nèi)容,有助于讀者對(duì)性能問(wèn)題進(jìn)行系統(tǒng)分析。本章涉及的主要知識(shí)點(diǎn)有:·評(píng)價(jià)性能的主要指標(biāo);·木桶原理的概念及其在性能優(yōu)化中的應(yīng)用;·Amdahl定律的含義;·性能調(diào)優(yōu)的層次;·系統(tǒng)優(yōu)化的一般步驟和注意事項(xiàng)。1.1性能概述為什么程序總是那么慢?它現(xiàn)在到底在干什么?時(shí)間都耗費(fèi)在哪里了?也許,你經(jīng)常會(huì)抱怨這些問(wèn)題。如果是這樣,那么說(shuō)明你的程序出現(xiàn)了性能問(wèn)題。和功能性問(wèn)題相比,性能問(wèn)題在有些情況下可能并不算什么太大的問(wèn)題,將就將就,也就過(guò)去了。但是,嚴(yán)重的性能問(wèn)題會(huì)導(dǎo)致程序癱瘓、假死,直至崩潰。本節(jié)就先來(lái)認(rèn)識(shí)性能的各種表現(xiàn)和指標(biāo)。1.1.1看懂程序的性能對(duì)于客戶端程序而言,拙劣的性能會(huì)嚴(yán)重影響用戶體驗(yàn)。界面停頓、抖動(dòng)、響應(yīng)遲鈍等問(wèn)題會(huì)遭到用戶不停地抱怨。一個(gè)典型的例子就是EclipseIDE工具在執(zhí)行FullGC時(shí)會(huì)出現(xiàn)程序“假死”現(xiàn)象,相信這一定被不少開(kāi)發(fā)人員所詬病。對(duì)于服務(wù)器程序來(lái)說(shuō),性能問(wèn)題則更為重要。相信不少后臺(tái)服務(wù)器軟件都有各自的性能目標(biāo),以Web服務(wù)器為例,服務(wù)器的響應(yīng)時(shí)間和吞吐量就是兩個(gè)重要的性能參數(shù)。當(dāng)服務(wù)器承受巨大的訪問(wèn)壓力時(shí),可能出現(xiàn)響應(yīng)時(shí)間變長(zhǎng)、吞吐量下降,甚至拋出內(nèi)存溢出異常而崩潰等問(wèn)題。這些問(wèn)題,都是性能調(diào)優(yōu)需要解決的。一般來(lái)說(shuō),程序的性能可以有以下幾個(gè)方面的表現(xiàn)?!?zhí)行速度:程序的反應(yīng)是否迅速,響應(yīng)時(shí)間是否足夠短。·內(nèi)存分配:內(nèi)存分配是否合理,是否過(guò)多地消耗內(nèi)存或者存在泄漏?!?dòng)時(shí)間:程序從運(yùn)行到可以正常處理業(yè)務(wù)需要花費(fèi)多長(zhǎng)時(shí)間?!へ?fù)載承受能力:當(dāng)系統(tǒng)壓力上升時(shí),系統(tǒng)的執(zhí)行速度和響應(yīng)時(shí)間的上升曲線是否平緩。1.1.2性能的參考指標(biāo)為了能夠科學(xué)地進(jìn)行性能分析,對(duì)性能指標(biāo)進(jìn)行定量評(píng)測(cè)是非常重要的。目前,可以用于定量評(píng)測(cè)的性能指標(biāo)有:·執(zhí)行時(shí)間:一段代碼從開(kāi)始運(yùn)行到運(yùn)行結(jié)束所使用的時(shí)間?!PU時(shí)間:函數(shù)或者線程占用CPU的時(shí)間?!?nèi)存分配:程序在運(yùn)行時(shí)占用的內(nèi)存空間。·磁盤吞吐量:描述I/O的使用情況?!ぞW(wǎng)絡(luò)吞吐量:描述網(wǎng)絡(luò)的使用情況?!ろ憫?yīng)時(shí)間:系統(tǒng)對(duì)某用戶行為或者事件做出響應(yīng)的時(shí)間。響應(yīng)時(shí)間越短,性能越好。1.1.3木桶原理與性能瓶頸木桶原理又稱短板理論,其核心思想是一只木桶盛水的多少,并不取決于桶壁上最高的那塊木板,而是取決于桶壁上最短的那塊木板,如圖1.1所示。圖1.1木桶原理示意圖將這個(gè)理論應(yīng)用到系統(tǒng)性能優(yōu)化上可以這么理解,即使系統(tǒng)擁有充足的內(nèi)存資源和CPU資源,但是,如果磁盤I/O性能低下,那么系統(tǒng)的總體性能是取決于當(dāng)前最慢的磁盤I/O速度,而不是當(dāng)前最優(yōu)越的CPU或者內(nèi)存。在這種情況下,如果需要進(jìn)一步提升系統(tǒng)性能,優(yōu)化內(nèi)存或者CPU資源是毫無(wú)用處的,只有提高磁盤I/O性能才能對(duì)系統(tǒng)的整體性能進(jìn)行優(yōu)化。此時(shí),磁盤I/O就是系統(tǒng)的性能瓶頸。注意:根據(jù)木桶原理,系統(tǒng)的最終性能取決于系統(tǒng)中性能表現(xiàn)最差的組件。因此,為了提升系統(tǒng)的整體性能,必須對(duì)系統(tǒng)中表現(xiàn)最差的組件進(jìn)行優(yōu)化,而不是對(duì)系統(tǒng)中表現(xiàn)良好的組件進(jìn)行優(yōu)化。根據(jù)應(yīng)用的特點(diǎn)不同,任何計(jì)算機(jī)資源都有可能成為系統(tǒng)瓶頸。其中,最有可能成為系統(tǒng)瓶頸的計(jì)算資源有:·磁盤I/O:由于磁盤I/O讀寫(xiě)的速度比內(nèi)存慢很多,程序在運(yùn)行過(guò)程中,如果需要等待磁盤I/O完成,那么低效的I/O操作會(huì)拖累整個(gè)系統(tǒng)?!ぞW(wǎng)絡(luò)操作:對(duì)網(wǎng)絡(luò)數(shù)據(jù)進(jìn)行讀寫(xiě)的情況與磁盤I/O類似,由于網(wǎng)絡(luò)環(huán)境的不確定性,尤其是對(duì)互聯(lián)網(wǎng)上數(shù)據(jù)的讀寫(xiě),網(wǎng)絡(luò)操作的速度可能比本地磁盤I/O更慢,因此,如不加特殊處理,也極可能成為系統(tǒng)瓶頸。·CPU:對(duì)計(jì)算資源要求較高的應(yīng)用,由于其長(zhǎng)時(shí)間、不間斷地大量占用CPU資源,那么對(duì)CPU的爭(zhēng)奪將導(dǎo)致性能問(wèn)題。例如,科學(xué)計(jì)算、3D渲染等對(duì)CPU需求量大的應(yīng)用便是如此?!ぎ惓#簩?duì)Java應(yīng)用來(lái)說(shuō),異常的捕獲和處理是非常消耗資源的。如果程序高頻率地進(jìn)行異常處理,則整體性能便會(huì)有明顯下降?!?shù)據(jù)庫(kù):大部分應(yīng)用程序都離不開(kāi)數(shù)據(jù)庫(kù),而海量數(shù)據(jù)的讀寫(xiě)操作往往是相當(dāng)費(fèi)時(shí)的。應(yīng)用程序需要等待數(shù)據(jù)庫(kù)操作完成并返回結(jié)果,那么緩慢的同步操作將成為系統(tǒng)瓶頸?!ゆi競(jìng)爭(zhēng):對(duì)高并發(fā)程序來(lái)說(shuō),如果存在激烈的鎖競(jìng)爭(zhēng),對(duì)性能無(wú)疑是極大的打擊。鎖競(jìng)爭(zhēng)將會(huì)明顯增加線程上下文切換的開(kāi)銷,而且這些開(kāi)銷都是與應(yīng)用需求無(wú)關(guān)的系統(tǒng)開(kāi)銷,白白占用寶貴的CPU資源,卻不帶來(lái)任何好處?!?nèi)存:一般來(lái)說(shuō),只要應(yīng)用程序設(shè)計(jì)合理,內(nèi)存在讀寫(xiě)速度上不太可能成為性能瓶頸。除非應(yīng)用程序進(jìn)行了高頻率的內(nèi)存交換和掃描,但這種情況比較少見(jiàn)。內(nèi)存制約系統(tǒng)性能最有可能出現(xiàn)的情況是內(nèi)存容量不足。與磁盤相比,內(nèi)存的容量似乎小得可憐,這意味著應(yīng)用軟件只能盡可能將常用的核心數(shù)據(jù)讀入內(nèi)存,這在一定程度上降低了系統(tǒng)性能。1.1.4Amdahl定律Amdahl定律是計(jì)算機(jī)科學(xué)中非常重要的定律,它定義了串行系統(tǒng)并行化后的加速比的計(jì)算公式和理論上限。加速比定義如下:加速比=優(yōu)化前系統(tǒng)耗時(shí)/優(yōu)化后系統(tǒng)耗時(shí)即所謂的加速比,就是優(yōu)化前系統(tǒng)耗時(shí)與優(yōu)化后系統(tǒng)耗時(shí)的比值。加速比越高,表明優(yōu)化效果越明顯。Amdahl定律給出了加速比與系統(tǒng)并行度和處理器數(shù)量的關(guān)系。設(shè)加速比為Speedup,系統(tǒng)內(nèi)必須串行化的程序比重為F,CPU處理器的數(shù)量為N,則有根據(jù)這個(gè)公式可知,如果CPU處理器的數(shù)量趨于無(wú)窮,那么加速比與系統(tǒng)的串行化率成反比,如果系統(tǒng)中必須有50%的代碼串行執(zhí)行,那么系統(tǒng)的最大加速比為2。假設(shè)有一程序分為5個(gè)步驟執(zhí)行,每個(gè)執(zhí)行步驟花費(fèi)100個(gè)時(shí)間單位。其中,只有步驟2和步驟5可以進(jìn)行并行,步驟1、3、4必須串行,如圖1.2所示。在全串行的情況下,系統(tǒng)合計(jì)耗時(shí)500個(gè)時(shí)間單位。圖1.2串行工作流程若將步驟2和步驟5并行化,假設(shè)在雙核處理器上,則有如圖1.3所示的處理流程。在這種情況下,步驟2和步驟5的耗時(shí)將為50個(gè)時(shí)間單位,故系統(tǒng)整體耗時(shí)為400個(gè)時(shí)間單位。根據(jù)加速比的定義有:加速比=優(yōu)化前系統(tǒng)耗時(shí)/優(yōu)化后系統(tǒng)耗時(shí)=500/400=1.25或者使用Amdahl定律給出的加速比公式。由于5個(gè)步驟中,3個(gè)步驟必須串行,因此其串行化比重為3/5=0.6,即F=0.6,且雙核處理器的處理器個(gè)數(shù)N為2。代入公式得:圖1.3雙核處理器上的并行化在極端情況下,假設(shè)并行處理器個(gè)數(shù)為無(wú)窮大,則有如圖1.4所示的處理過(guò)程。步驟2和步驟5的處理時(shí)間趨于0。即使這樣,系統(tǒng)整體耗時(shí)依然大于300個(gè)時(shí)間單位,即加速比的極限為500/300=1.67。使用加速比計(jì)算公式,N趨于無(wú)窮大,有Speedup=1/F,且F=0.6,故有Speedup=1.67。圖1.4極端情況下的并行化由此可見(jiàn),為了提高系統(tǒng)的速度,僅增加CPU處理器的數(shù)量并不一定能起到有效的作用。需要從根本上修改程序的串行行為,提高系統(tǒng)內(nèi)可并行化的模塊比重,在此基礎(chǔ)上合理增加并行處理器的數(shù)量,才能以最小的投入得到最大的加速比。注意:根據(jù)Amdahl定律,使用多核CPU對(duì)系統(tǒng)進(jìn)行優(yōu)化,優(yōu)化的效果取決于CPU的數(shù)量及系統(tǒng)中串行化程序的比重。CPU數(shù)量越多,串行化比重越低,則優(yōu)化效果越好。僅提高CPU數(shù)量而不降低程序的串行化比重,則無(wú)法提高系統(tǒng)性能。1.2性能調(diào)優(yōu)的層次為了提升系統(tǒng)性能,開(kāi)發(fā)人員可以從系統(tǒng)的各個(gè)角度和層次對(duì)系統(tǒng)進(jìn)行優(yōu)化。除了最常見(jiàn)的代碼優(yōu)化外,在軟件架構(gòu)、JVM虛擬機(jī)、數(shù)據(jù)庫(kù)及操作系統(tǒng)幾個(gè)層面可以通過(guò)各種手段進(jìn)行調(diào)優(yōu),從而在整體上提升系統(tǒng)的性能。1.2.1設(shè)計(jì)調(diào)優(yōu)設(shè)計(jì)調(diào)優(yōu)處于所有調(diào)優(yōu)手段的上層,它往往需要在軟件開(kāi)發(fā)之前進(jìn)行。在軟件開(kāi)發(fā)之初,軟件架構(gòu)師就應(yīng)該評(píng)估系統(tǒng)可能存在的各種潛在問(wèn)題,并給出合理的設(shè)計(jì)方案。由于軟件設(shè)計(jì)和架構(gòu)對(duì)軟件整體質(zhì)量有決定性的影響,所以,設(shè)計(jì)調(diào)優(yōu)對(duì)系統(tǒng)性能的影響也是最大的。如果說(shuō)代碼優(yōu)化、JVM優(yōu)化都是對(duì)系統(tǒng)在微觀層面上“量”的優(yōu)化,那么設(shè)計(jì)優(yōu)化就是對(duì)系統(tǒng)在宏觀層面上“質(zhì)”的優(yōu)化。設(shè)計(jì)優(yōu)化的一大顯著特點(diǎn)是,它可以規(guī)避某一個(gè)組件的性能問(wèn)題,而非改良該組件的實(shí)現(xiàn)。例如,系統(tǒng)中組件A需要等待某事件E才能觸發(fā)一個(gè)行為。如果組件A通過(guò)循環(huán)監(jiān)控不斷監(jiān)測(cè)事件E是否發(fā)生,其監(jiān)測(cè)行為必然會(huì)占用部分系統(tǒng)資源,因此,開(kāi)發(fā)人員必須在監(jiān)測(cè)頻率和資源消耗間取得平衡。如果監(jiān)測(cè)頻率太低,雖然減少了資源消耗,但是系統(tǒng)實(shí)時(shí)反應(yīng)性就會(huì)降低。如果進(jìn)行代碼層的調(diào)優(yōu),就需要優(yōu)化監(jiān)測(cè)方法的實(shí)現(xiàn)以及求得一個(gè)最為恰當(dāng)?shù)谋O(jiān)測(cè)頻率。而若將此問(wèn)題預(yù)留在設(shè)計(jì)層解決,便可以使用事件通知的方式將系統(tǒng)行為進(jìn)行倒置。例如,使用將在第2章中提到的觀察者模式,在事件E發(fā)生的時(shí)刻,由事件E通知組件A,從而觸發(fā)組件A的行為。這種設(shè)計(jì)方法棄用了存在性能隱患的循環(huán)監(jiān)控,從根本上解決了這一問(wèn)題。從某種程度上說(shuō),設(shè)計(jì)優(yōu)化直接決定了系統(tǒng)的整體品質(zhì)。如果在設(shè)計(jì)層考慮不周,留下太多問(wèn)題隱患,那么這些“質(zhì)”上的問(wèn)題,也許無(wú)法再通過(guò)代碼層的優(yōu)化進(jìn)行彌補(bǔ)。因此,開(kāi)發(fā)人員必須在軟件設(shè)計(jì)之初,要認(rèn)真、仔細(xì)地考慮軟件系統(tǒng)的性能問(wèn)題。進(jìn)行設(shè)計(jì)優(yōu)化時(shí),設(shè)計(jì)人員必須熟悉常用的軟件設(shè)計(jì)方法、設(shè)計(jì)模式、基本性能組件和常用的優(yōu)化思想,并將其有機(jī)地集成在軟件系統(tǒng)中。注意:一個(gè)良好的系統(tǒng)設(shè)計(jì)可以規(guī)避很多潛在的性能問(wèn)題。因此,盡可能多花一些時(shí)間在系統(tǒng)設(shè)計(jì)上,這是創(chuàng)建高性能程序的關(guān)鍵。1.2.2代碼調(diào)優(yōu)代碼調(diào)優(yōu)是在軟件開(kāi)發(fā)過(guò)程中或者在軟件開(kāi)發(fā)完成后,軟件維護(hù)過(guò)程中進(jìn)行的對(duì)程序代碼的改進(jìn)和優(yōu)化。代碼優(yōu)化涉及諸多編碼技巧,需要開(kāi)發(fā)人員熟悉相關(guān)語(yǔ)言的API,并在合適的場(chǎng)景中正確使用相關(guān)API或類庫(kù)。同時(shí),對(duì)算法和數(shù)據(jù)結(jié)構(gòu)的靈活使用,也是代碼優(yōu)化的重要內(nèi)容。雖然代碼優(yōu)化是從微觀上對(duì)性能進(jìn)行調(diào)整,但是一個(gè)“好”的實(shí)現(xiàn)和一個(gè)“壞”的實(shí)現(xiàn)對(duì)系統(tǒng)的影響也是非常大的。例如,同樣作為L(zhǎng)ist的實(shí)現(xiàn),LinkedList和ArrayList在隨機(jī)訪問(wèn)上的性能卻相差幾個(gè)數(shù)量級(jí)。又如,同樣是文件讀寫(xiě)的實(shí)現(xiàn),使用Stream方式與JavaNIO方式,其性能可能又會(huì)相差一個(gè)數(shù)量級(jí)。因此,與設(shè)計(jì)優(yōu)化相比,雖然筆者將代碼優(yōu)化稱為在微觀層面上的優(yōu)化,但它卻是對(duì)系統(tǒng)性能產(chǎn)生最直接影響的優(yōu)化方法。1.2.3JVM調(diào)優(yōu)由于Java軟件總是運(yùn)行在JVM虛擬機(jī)之上,因此對(duì)JVM虛擬機(jī)進(jìn)行優(yōu)化也能在一定程度上提升Java程序的性能。JVM調(diào)優(yōu)通??梢栽谲浖_(kāi)發(fā)后期進(jìn)行,如在軟件開(kāi)發(fā)完成或者在軟件開(kāi)發(fā)的某一里程碑階段進(jìn)行。作為Java軟件的運(yùn)行平臺(tái),JVM的各項(xiàng)參數(shù)如JVM的堆大小和垃圾回收策略等,將會(huì)直接影響Java程序的性能。要進(jìn)行JVM層面的調(diào)優(yōu),需要開(kāi)發(fā)人員對(duì)JVM的運(yùn)行原理和基本內(nèi)存結(jié)構(gòu)有一定了解,例如堆內(nèi)存的結(jié)構(gòu)、GC的種類等,然后依據(jù)應(yīng)用程序的特點(diǎn),設(shè)置合理的JVM啟動(dòng)參數(shù)。1.2.4數(shù)據(jù)庫(kù)調(diào)優(yōu)對(duì)絕大部分應(yīng)用系統(tǒng)而言,數(shù)據(jù)庫(kù)是必不可少的一部分。Java程序可以使用JDBC的方式連接數(shù)據(jù)庫(kù)。對(duì)數(shù)據(jù)庫(kù)的調(diào)優(yōu)可以分為以下3個(gè)部分:·在應(yīng)用層對(duì)SQL語(yǔ)句進(jìn)行優(yōu)化;·對(duì)數(shù)據(jù)庫(kù)進(jìn)行優(yōu)化;·對(duì)數(shù)據(jù)庫(kù)軟件進(jìn)行優(yōu)化。在應(yīng)用層優(yōu)化數(shù)據(jù)庫(kù)訪問(wèn),涉及大量的編程技巧。例如,當(dāng)使用JDBC進(jìn)行查詢時(shí),對(duì)于大量擁有相同結(jié)構(gòu)的SQL查詢,可以使用PreparedStatement代替Statement,提高數(shù)據(jù)庫(kù)的查詢效率;在Select語(yǔ)句中,顯示指定要查詢的列名,避免使用星號(hào)“*”。在對(duì)數(shù)據(jù)庫(kù)進(jìn)行優(yōu)化時(shí),主要目的是建立一個(gè)具有良好表結(jié)構(gòu)的數(shù)據(jù)庫(kù)。例如,為了提高多表級(jí)聯(lián)查詢效率,可以合理地使用冗余字段;對(duì)于大表,可以使用行的水平切割或者類似于Oracle分區(qū)表的技術(shù);為了提高數(shù)據(jù)庫(kù)的查詢效率,可以建立有效且合理的索引。對(duì)于數(shù)據(jù)庫(kù)軟件的優(yōu)化,根據(jù)不同的數(shù)據(jù)庫(kù),如Oracle、MySQL或SQLServer,都擁有不同的方式。以O(shè)racle為例,設(shè)置合理大小的共享池、緩存緩沖區(qū)或者PGA,對(duì)Oracle的運(yùn)行性能都有很大的影響。鑒于本書(shū)的討論范圍,數(shù)據(jù)庫(kù)優(yōu)化將不作為本書(shū)的闡述重點(diǎn)。1.2.5操作系統(tǒng)調(diào)優(yōu)作為軟件運(yùn)行的基礎(chǔ)平臺(tái),操作系統(tǒng)的性能對(duì)應(yīng)用系統(tǒng)也有較大的影響。不同類型的操作系統(tǒng),調(diào)優(yōu)的手段和參數(shù)可能會(huì)有所不同。例如,在主流UNIX系統(tǒng)中,共享內(nèi)存段、信號(hào)量、共享內(nèi)存最大值(shmmax)、共享內(nèi)存最小值(shmmin)等都是可以進(jìn)行優(yōu)化的系統(tǒng)資源。此外,如最大文件句柄數(shù)、虛擬內(nèi)存大小、磁盤的塊大小等參數(shù)都可能對(duì)軟件的性能產(chǎn)生影響。圖1.5展示了在Windows平臺(tái)上配置虛擬內(nèi)存的界面。圖1.5在Windows上設(shè)置虛擬內(nèi)存說(shuō)明:操作系統(tǒng)的性能調(diào)優(yōu)不在本書(shū)的討論范圍內(nèi),有興趣的讀者可以參考相關(guān)書(shū)籍。1.3基本調(diào)優(yōu)策略和手段存在性能問(wèn)題的系統(tǒng),十有八九是由某一系統(tǒng)瓶頸導(dǎo)致的。只要找到該性能瓶頸,分析瓶頸的形成原因,對(duì)癥下藥,使用合理的方法解決系統(tǒng)瓶頸,就能從根本上提升性能。所以,系統(tǒng)性能優(yōu)化的最主要目的就是查找并解決性能瓶頸問(wèn)題。但值得注意的是,性能優(yōu)化往往會(huì)涉及對(duì)原有的實(shí)現(xiàn)進(jìn)行較大的修改,因此很難保證這些修改不引發(fā)新的問(wèn)題。所以,在性能優(yōu)化前,需要對(duì)性能優(yōu)化的目標(biāo)和使用的方法進(jìn)行統(tǒng)籌安排。1.3.1優(yōu)化的一般步驟對(duì)軟件系統(tǒng)進(jìn)行優(yōu)化,首先需要有明確的性能目標(biāo),清楚地指出優(yōu)化的對(duì)象和最終目的。其次,需要在目標(biāo)平臺(tái)上對(duì)軟件進(jìn)行測(cè)試,通過(guò)各種性能監(jiān)控和統(tǒng)計(jì)工具,觀測(cè)和確認(rèn)當(dāng)前系統(tǒng)是否已經(jīng)達(dá)到相關(guān)目標(biāo),若已經(jīng)達(dá)到,則沒(méi)有必要再進(jìn)行優(yōu)化;若尚未達(dá)到優(yōu)化目標(biāo),則需要查找當(dāng)前的性能瓶頸??赡艹蔀樾阅芷款i的因素很多,如磁盤I/O、網(wǎng)絡(luò)I/O和CPU。當(dāng)找到性能瓶頸后,首先需要定位相關(guān)代碼,確認(rèn)是否在軟件實(shí)現(xiàn)上存在問(wèn)題或者具有優(yōu)化的空間。若存在優(yōu)化空間,則進(jìn)行代碼優(yōu)化;否則需要考慮進(jìn)行JVM層、數(shù)據(jù)庫(kù)層或者操作系統(tǒng)的優(yōu)化,甚至可以考慮修改原有設(shè)計(jì),或者提升硬件性能。當(dāng)優(yōu)化完成后,需要在目標(biāo)平臺(tái)上進(jìn)行確認(rèn)測(cè)試。若達(dá)到性能目標(biāo),則優(yōu)化過(guò)程結(jié)束;否則需要再次查找系統(tǒng)瓶頸,如此反復(fù),如圖1.6所示。圖1.6性能優(yōu)化的一般步驟1.3.2系統(tǒng)優(yōu)化的注意事項(xiàng)性能優(yōu)化雖然能提升軟件的性能,但是優(yōu)化過(guò)程往往伴隨著一些風(fēng)險(xiǎn)和弊端。例如,為了優(yōu)化某一段代碼的實(shí)現(xiàn),就需要重寫(xiě)原有的算法,而這就很可能引入新的Bug。重新實(shí)現(xiàn)新的功能模塊同時(shí)也意味著需要重新對(duì)其進(jìn)行完整的功能性測(cè)試,使優(yōu)化前所做的測(cè)試工作變得毫無(wú)意義。而且,優(yōu)化后的代碼與優(yōu)化前的代碼相比,可能會(huì)比較晦澀難懂,在一定程度上影響了系統(tǒng)的可維護(hù)性。因此,軟件優(yōu)化需要在軟件功能、正確性和可維護(hù)性之間取得平衡,而不應(yīng)該過(guò)分地追求軟件性能。在進(jìn)行優(yōu)化前,必須要有明確的已知問(wèn)題和性能目標(biāo),決不可為了“優(yōu)化”而“優(yōu)化”。因此在動(dòng)手前,必須知道自己要干什么。任何優(yōu)化都是為了解決具體的軟件問(wèn)題,如果軟件已經(jīng)可以正常工作,在沒(méi)有性能問(wèn)題暴露前,只是憑著主觀臆斷對(duì)某些模塊進(jìn)行性能改進(jìn),從軟件規(guī)范化開(kāi)發(fā)的角度來(lái)說(shuō)是非常冒險(xiǎn)的。因?yàn)樾薷暮蟮男麓a沒(méi)有經(jīng)過(guò)完整的測(cè)試,軟件質(zhì)量就沒(méi)有保障。而且,優(yōu)化后的性能提升幅度可能并不足以讓開(kāi)發(fā)者值得如此費(fèi)盡心機(jī)。因此,在進(jìn)行軟件優(yōu)化時(shí),必須要進(jìn)行慎重的評(píng)估。注意:性能調(diào)優(yōu)必須有明確的目標(biāo),不要為了調(diào)優(yōu)而調(diào)優(yōu)。如果當(dāng)前程序并沒(méi)有明顯的性能問(wèn)題,盲目地進(jìn)行調(diào)優(yōu),其風(fēng)險(xiǎn)可能遠(yuǎn)遠(yuǎn)大于收益。1.4小結(jié)通過(guò)本章的學(xué)習(xí),讀者應(yīng)該了解性能的基本概念及常用的參考指標(biāo)。此外,本章還較為詳細(xì)地介紹了與性能調(diào)優(yōu)相關(guān)的兩個(gè)重要理論——木桶原理和Amdahl定律。根據(jù)木桶原理,系統(tǒng)的最終性能總是由系統(tǒng)中性能最差的組件決定的,因此,改善該組件的性能對(duì)提升系統(tǒng)整體性能有重要的作用。而根據(jù)Amdahl定律可以知道,只是增加處理器數(shù)量對(duì)提升系統(tǒng)性能并沒(méi)有太大的實(shí)際意義,還必須同時(shí)提高程序的并行化比重。本章還簡(jiǎn)要介紹了在軟件開(kāi)發(fā)和維護(hù)過(guò)程中可以進(jìn)行性能優(yōu)化的各個(gè)階段。例如,在軟件的設(shè)計(jì)階段,需要選用合理的軟件結(jié)構(gòu)和性能組件;在編碼階段,需要提高代碼的執(zhí)行效率;對(duì)于Java應(yīng)用程序,在系統(tǒng)的運(yùn)行期,還需要設(shè)置合理的JVM虛擬機(jī)參數(shù);同時(shí),優(yōu)化數(shù)據(jù)庫(kù)和操作系統(tǒng)也對(duì)系統(tǒng)整體性能有直接影響。在本章的最后還簡(jiǎn)要介紹了性能優(yōu)化的一般步驟和注意事項(xiàng)。第2章設(shè)計(jì)優(yōu)化本章主要介紹與軟件設(shè)計(jì)相關(guān)的性能優(yōu)化方法和思想。軟件的結(jié)構(gòu)對(duì)系統(tǒng)的整體性能有著重要的影響,優(yōu)秀的設(shè)計(jì)結(jié)構(gòu)可以規(guī)避很多潛在的性能問(wèn)題,對(duì)系統(tǒng)性能的影響可能遠(yuǎn)遠(yuǎn)大于對(duì)代碼的優(yōu)化。因此,熟悉一些常用的軟件設(shè)計(jì)模式和方法,對(duì)設(shè)計(jì)高性能軟件有很大幫助。本章著眼于設(shè)計(jì)優(yōu)化,主要講解一些與性能相關(guān)的常用設(shè)計(jì)模式、組件和設(shè)計(jì)方法。本章涉及的主要知識(shí)點(diǎn)有:·單例模式的使用和實(shí)現(xiàn);·代理模式的實(shí)現(xiàn)和深入剖析;·享元模式的應(yīng)用;·裝飾者模式對(duì)性能組件的封裝;·觀察者模式的使用;·使用值對(duì)象模式減少網(wǎng)絡(luò)數(shù)據(jù)傳輸;·使用業(yè)務(wù)代理模式添加遠(yuǎn)程調(diào)用緩存;·緩沖和緩存的定義與使用;·對(duì)象池的使用場(chǎng)景及其基本實(shí)現(xiàn);·負(fù)載均衡系統(tǒng)的構(gòu)建及Terracotta框架的簡(jiǎn)單使用;·時(shí)間換空間和空間換時(shí)間的基本思路。2.1善用設(shè)計(jì)模式設(shè)計(jì)模式是前人工作的總結(jié)和提煉。通常,被人們廣泛流傳的設(shè)計(jì)模式都是對(duì)某一特定問(wèn)題的成熟解決方案。如果能合理地使用設(shè)計(jì)模式,不僅能使系統(tǒng)更容易被他人理解,同時(shí)也能使系統(tǒng)擁有更加合理的結(jié)構(gòu)。本節(jié)總結(jié)歸納一些經(jīng)典的設(shè)計(jì)模式,并詳細(xì)說(shuō)明它們與軟件性能之間的關(guān)系。2.1.1單例模式單例模式是設(shè)計(jì)模式中使用最為普遍的模式之一,它是一種對(duì)象創(chuàng)建模式,用于產(chǎn)生一個(gè)對(duì)象的具體實(shí)例,可以確保系統(tǒng)中一個(gè)類只產(chǎn)生一個(gè)實(shí)例。在Java語(yǔ)言中,這樣的行為能帶來(lái)兩大好處:(1)對(duì)于頻繁使用的對(duì)象,可以省去new操作花費(fèi)的時(shí)間,這對(duì)于那些重量級(jí)對(duì)象而言,是一筆非??捎^的系統(tǒng)開(kāi)銷。(2)由于new操作的次數(shù)減少,因而對(duì)系統(tǒng)內(nèi)存的使用頻率也會(huì)降低,這將減輕GC壓力,縮短GC停頓時(shí)間。因此對(duì)于系統(tǒng)的關(guān)鍵組件和被頻繁使用的對(duì)象,使用單例模式可以有效地改善系統(tǒng)的性能。單例模式的角色非常簡(jiǎn)單,只有單例類和使用者兩個(gè),如表2.1所示。表2.1單例模式的角色單例模式的基本結(jié)構(gòu)如圖2.1所示。圖2.1單例模式的結(jié)構(gòu)單例模式的核心在于通過(guò)一個(gè)接口返回唯一的對(duì)象實(shí)例。一個(gè)簡(jiǎn)單的單例實(shí)現(xiàn)如下:publicclassSingleton{

privateSingleton(){

System.out.println("Singletoniscreate");//創(chuàng)建單例的過(guò)程可能會(huì)比較慢

}

privatestaticSingletoninstance=newSingleton();

publicstaticSingletongetInstance(){

returninstance;

}

}注意代碼中的重點(diǎn)標(biāo)注部分,首先單例類必須要有一個(gè)private訪問(wèn)級(jí)別的構(gòu)造函數(shù),只有這樣,才能確保單例不會(huì)在系統(tǒng)的其他代碼內(nèi)被實(shí)例化,這一點(diǎn)是相當(dāng)重要的。其次,instance成員變量和getInstance()方法必須是static的。注意:?jiǎn)卫J绞且环N非常常用的結(jié)構(gòu),幾乎所有的系統(tǒng)中都可以找到它的身影。因此,希望讀者通過(guò)本節(jié)的學(xué)習(xí),了解單例模式的幾種實(shí)現(xiàn)方式及各自的特點(diǎn)。這種單例的實(shí)現(xiàn)方式非常簡(jiǎn)單,而且十分可靠,唯一的不足僅是無(wú)法對(duì)instance做延遲加載。假如單例的創(chuàng)建過(guò)程很慢,而由于instance成員變量是static定義的,因此在JVM加載單例類時(shí),單例對(duì)象就會(huì)被建立,如果此時(shí)這個(gè)單例類在系統(tǒng)中還扮演其他角色,那么在任何使用這個(gè)單例類的地方都會(huì)初始化這個(gè)單例變量,而不管是否會(huì)被用到。例如,單例類作為String工廠用于創(chuàng)建一些字符串(該類既用于創(chuàng)建單例,又用于創(chuàng)建String對(duì)象):publicclassSingleton{

privateSingleton(){

//創(chuàng)建單例的過(guò)程可能會(huì)比較慢

System.out.println("Singletoniscreate");

}

privatestaticSingletoninstance=newSingleton();

publicstaticSingletongetInstance(){

returninstance;

}

publicstaticvoidcreateString(){//這是模擬單例類扮演其他角色

System.out.println("createStringinSingleton");

}

}當(dāng)使用Singleton.createString()執(zhí)行任務(wù)時(shí),程序輸出以下內(nèi)容:Singletoniscreate

createStringinSingleton可以看到,雖然此時(shí)并沒(méi)有使用單例類,但它還是被創(chuàng)建出來(lái),這也許是開(kāi)發(fā)人員所不愿意見(jiàn)到的。為了解決這個(gè)問(wèn)題,并提高系統(tǒng)在相關(guān)函數(shù)調(diào)用時(shí)的反應(yīng)速度,就需要引入延遲加載機(jī)制。publicclassLazySingleton{

privateLazySingleton(){

//創(chuàng)建單例的過(guò)程可能會(huì)比較慢

System.out.println("LazySingletoniscreate");

}

privatestaticLazySingletoninstance=null;

publicstaticsynchronizedLazySingletongetInstance(){

if(instance==null)

instance=newLazySingleton();

returninstance;

}

}首先,對(duì)于靜態(tài)成員變量instance賦予初始值null,確保系統(tǒng)啟動(dòng)時(shí)沒(méi)有額外的負(fù)載。其次,在getInstance()工廠方法中,判斷當(dāng)前單例是否已經(jīng)存在,若存在,則返回,不存在,再建立單例。這里尤其要注意,getInstance()方法必須是同步的,否則在多線程環(huán)境下,當(dāng)線程1正新建單例完成賦值操作前,線程2可能判斷instance為null,故線程2也將啟動(dòng)新建單例的程序,從而導(dǎo)致多個(gè)實(shí)例被創(chuàng)建,因此同步關(guān)鍵字是必需步驟。使用上例中的單例,雖然實(shí)現(xiàn)了延遲加載的功能,但和第一種方法相比,它引入了同步關(guān)鍵字,因此在多線程環(huán)境中,它的時(shí)耗要遠(yuǎn)遠(yuǎn)大于第一種單例模式的時(shí)耗。以下測(cè)試代碼就說(shuō)明了這個(gè)問(wèn)題。@Override

publicvoidrun(){

for(inti=0;i<100000;i++)

Singleton.getInstance();

//LazySingleton.getInstance();

System.out.println("spend:"+(System.currentTimeMillis()-begintime));

}開(kāi)啟5個(gè)線程同時(shí)完成以上代碼的運(yùn)行,使用第一種單例耗時(shí)0ms,而使用LazySingleton卻相對(duì)耗時(shí)約390ms,性能至少相差2個(gè)數(shù)量級(jí)。注意:在本書(shū)中,會(huì)使用很多類似的代碼片段來(lái)測(cè)試不同代碼的執(zhí)行速度,在不同的計(jì)算機(jī)上其測(cè)試結(jié)果很可能與筆者不同。讀者大可不必關(guān)心測(cè)試數(shù)據(jù)的絕對(duì)值,而只要觀察用于比較的目標(biāo)代碼間的相對(duì)耗時(shí)即可。為了使用延遲加載引入的同步關(guān)鍵字反而降低了系統(tǒng)性能,是不是有點(diǎn)得不償失呢?為了解決這個(gè)問(wèn)題,還需要對(duì)其進(jìn)行以下改進(jìn):publicclassStaticSingleton{

privateStaticSingleton(){

System.out.println("StaticSingletoniscreate");

}

privatestaticclassSingletonHolder{

privatestaticStaticSingletoninstance=newStaticSingleton();

}

publicstaticStaticSingletongetInstance(){

returnSingletonHolder.instance;

}

}在這個(gè)實(shí)現(xiàn)中,單例模式使用內(nèi)部類來(lái)維護(hù)單例的實(shí)例,當(dāng)StaticSingleton被加載時(shí),其內(nèi)部類并不會(huì)被初始化,故可以確保當(dāng)StaticSingleton類被載入JVM時(shí)不會(huì)初始化單例類,而當(dāng)getInstance()方法被調(diào)用時(shí)才會(huì)加載SingletonHolder,從而初始化instance。同時(shí),由于實(shí)例的建立是在類加載時(shí)完成的,故天生對(duì)多線程友好,getInstance()方法也不需要使用同步關(guān)鍵字。因此,這種實(shí)現(xiàn)方式同時(shí)兼?zhèn)湟陨蟽煞N實(shí)現(xiàn)方式的優(yōu)點(diǎn)。注意:使用內(nèi)部類的方式實(shí)現(xiàn)單例,既可以做到延遲加載,也不必使用同步關(guān)鍵字,是一種比較完善的實(shí)現(xiàn)方式。通常情況下,用以上方式實(shí)現(xiàn)的單例已經(jīng)可以確保在系統(tǒng)中只存在唯一實(shí)例了。但仍然有例外情況——可能導(dǎo)致系統(tǒng)生成多個(gè)實(shí)例,例如在代碼中通過(guò)反射機(jī)制,強(qiáng)行調(diào)用單例類的私有構(gòu)造函數(shù)生成多個(gè)單例。考慮到情況的特殊性,本書(shū)不對(duì)這種極端的方式進(jìn)行討論。但仍有些合法的方法,可能導(dǎo)致系統(tǒng)出現(xiàn)多個(gè)單例類的實(shí)例。以下是一個(gè)可以被串行化的單例:publicclassSerSingletonimplementsjava.io.Serializable{

Stringname;

privateSerSingleton(){

//創(chuàng)建單例的過(guò)程可能會(huì)比較慢

System.out.println("Singletoniscreate");

name="SerSingleton";

}

privatestaticSerSingletoninstance=newSerSingleton();

publicstaticSerSingletongetInstance(){

returninstance;

}

publicstaticvoidcreateString(){

System.out.println("createStringinSingleton");

}

privateObjectreadResolve(){//阻止生成新的實(shí)例,總是返回當(dāng)前對(duì)象

returninstance;

}

}測(cè)試代碼如下:@Test

publicvoidtest()throwsException{

SerSingletons1=null;

SerSingletons=SerSingleton.getInstance();

//先將實(shí)例串行化到文件

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);

}使用一段測(cè)試代碼測(cè)試單例的串行化和反串行化,當(dāng)去掉SerSingleton代碼中加粗的readResolve()函數(shù)時(shí),測(cè)試代碼拋出以下異常:junit.framework.AssertionFailedError:expected:javatuning.ch2.singleton.serialization.SerSingleton@5224ee

butwas:javatuning.ch2.singleton.serialization.SerSingleton@18fe7c3這說(shuō)明測(cè)試代碼中的s和s1指向了不同的實(shí)例,在反序列化后生成了多個(gè)對(duì)象實(shí)例。而加上readResolve()函數(shù)的程序則正常退出,這說(shuō)明即便經(jīng)過(guò)反序列化,也仍然保持單例的特征。事實(shí)上,在實(shí)現(xiàn)了私有的readResolve()方法后,readObject()方法已經(jīng)形同虛設(shè),它直接使用readResolve()替換了原本的返回值,從而在形式上構(gòu)造了單例。注意:序列化和反序列化可能會(huì)破壞單例。一般來(lái)說(shuō),對(duì)單例進(jìn)行序列化和反序列化的場(chǎng)景并不多見(jiàn),但如果存在,就要多加注意。2.1.2代理模式代理模式也是一種很常見(jiàn)的設(shè)計(jì)模式,它使用代理對(duì)象完成用戶請(qǐng)求,屏蔽用戶對(duì)真實(shí)對(duì)象的訪問(wèn)。就如同現(xiàn)實(shí)中的代理一樣,代理人被授權(quán)執(zhí)行當(dāng)事人的一些事宜,而無(wú)須當(dāng)事人出面,從第三方的角度看,似乎當(dāng)事人并不存在,因?yàn)樗缓痛砣送ㄐ?。而事?shí)上,代理人要有當(dāng)事人的授權(quán),并且在核心問(wèn)題上還需要請(qǐng)示當(dāng)事人。在現(xiàn)實(shí)中,使用代理的情況很普遍,而且原因也很多。例如:當(dāng)事人因?yàn)槟承╇[私不方便出面,或者當(dāng)事人不具備某些相關(guān)的專業(yè)技能,而需要一個(gè)專業(yè)人員來(lái)完成一些專業(yè)的操作;由于當(dāng)事人沒(méi)有時(shí)間處理事務(wù),而聘用代理人出面。在軟件設(shè)計(jì)中,使用代理模式的意圖也很多,例如,出于安全原因,需要屏蔽客戶端直接訪問(wèn)真實(shí)對(duì)象;在遠(yuǎn)程調(diào)用中,需要使用代理類處理遠(yuǎn)程方法調(diào)用的技術(shù)細(xì)節(jié)(如RMI);為了提升系統(tǒng)性能,對(duì)真實(shí)對(duì)象進(jìn)行封裝,從而達(dá)到延遲加載的目的。在本小節(jié)中,主要討論使用代理模式實(shí)現(xiàn)延遲加載,從而提升系統(tǒng)的性能和反應(yīng)速度。1.代理模式的結(jié)構(gòu)代理模式的主要角色有4個(gè),如表2.2所示。表2.2代理模式的角色下面以一個(gè)簡(jiǎn)單的示例來(lái)闡述使用代理模式實(shí)現(xiàn)延遲加載的方法及其意義。假設(shè)某客戶端軟件有根據(jù)用戶請(qǐng)求去數(shù)據(jù)庫(kù)查詢數(shù)據(jù)的功能,在查詢數(shù)據(jù)前需要獲得數(shù)據(jù)庫(kù)連接,軟件開(kāi)啟時(shí)初始化系統(tǒng)的所有類,此時(shí)嘗試獲得數(shù)據(jù)庫(kù)連接。當(dāng)系統(tǒng)有大量的類似操作(如xml解析等)存在時(shí),所有這些初始化操作的疊加會(huì)使得系統(tǒng)的啟動(dòng)速度變得非常緩慢。為此,可以使用代理模式的代理類封裝對(duì)數(shù)據(jù)庫(kù)查詢中的初始化操作,當(dāng)系統(tǒng)啟動(dòng)時(shí)初始化這個(gè)代理類,而非初始化真實(shí)的數(shù)據(jù)庫(kù)查詢類,在此過(guò)程中代理類什么都沒(méi)有做,因此它的構(gòu)造是相當(dāng)迅速的。在系統(tǒng)啟動(dòng)時(shí),將消耗資源最多的方法都使用代理模式分離,這樣就可以加快系統(tǒng)的啟動(dòng)速度,從而減少用戶的等待時(shí)間。而在用戶真正做查詢操作時(shí),再由代理類單獨(dú)去加載真實(shí)的數(shù)據(jù)庫(kù)查詢類,從而完成用戶的請(qǐng)求。這個(gè)過(guò)程就是使用代理模式實(shí)現(xiàn)了延遲加載。注意:代理模式可以用于多種場(chǎng)合,如用于遠(yuǎn)程調(diào)用的網(wǎng)絡(luò)代理,以及考慮安全因素的安全代理等。延遲加載只是代理模式的一種應(yīng)用場(chǎng)景。延遲加載的核心思想是:如果當(dāng)前并沒(méi)有使用這個(gè)組件,則不需要真正地初始化它,而是使用一個(gè)代理對(duì)象替代它原有的位置,只有在真正需要使用的時(shí)候,才對(duì)它進(jìn)行加載。使用代理模式的延遲加載是非常有意義的:首先,它可以在時(shí)間軸上分散系統(tǒng)的壓力,尤其是在系統(tǒng)啟動(dòng)時(shí),不必完成所有的初始化工作,從而減少啟動(dòng)時(shí)間;其次,對(duì)于很多真實(shí)主題而言,可能在軟件啟動(dòng)直到關(guān)閉的整個(gè)過(guò)程中根本不會(huì)被調(diào)用,因此初始化這些數(shù)據(jù)無(wú)疑是一種資源浪費(fèi)。圖2.2顯示了使用代理類封裝數(shù)據(jù)庫(kù)查詢類后系統(tǒng)的啟動(dòng)過(guò)程。圖2.2代理類的工作流程若系統(tǒng)不使用代理模式,則在啟動(dòng)時(shí)就要初始化DBQuery對(duì)象,而使用代理模式后,啟動(dòng)時(shí)只需要初始化一個(gè)輕量級(jí)的對(duì)象DBQueryProxy即可。系統(tǒng)結(jié)構(gòu)如圖2.3所示。IDBQuery是主題接口,定義代理類和真實(shí)類需要對(duì)外提供的服務(wù),在本例中定義了實(shí)現(xiàn)數(shù)據(jù)庫(kù)查詢的公共方法,即request()函數(shù)。DBQuery是真實(shí)主題,負(fù)責(zé)實(shí)際的業(yè)務(wù)操作,DBQueryProxy是DBQuery的代理類。圖2.3代理模式的一種實(shí)現(xiàn)2.代理模式的實(shí)現(xiàn)和使用基于以上設(shè)計(jì),IDBQuery的實(shí)現(xiàn)方式如下。它只有一個(gè)request()方法。publicinterfaceIDBQuery{

Stringrequest();

}DBQuery的實(shí)現(xiàn)方式如下。它是一個(gè)重量級(jí)對(duì)象,構(gòu)造會(huì)比較慢。publicclassDBQueryimplementsIDBQuery{

publicDBQuery(){

try{

Thread.sleep(1000);//可能包含數(shù)據(jù)庫(kù)連接等耗時(shí)操作

}catch(InterruptedExceptione){

e.printStackTrace();

}

}

@Override

publicStringrequest(){

return"requeststring";

}

}代理類DBQueryProxy是輕量級(jí)對(duì)象,創(chuàng)建很快,用于替代DBQuery的位置。publicclassDBQueryProxyimplementsIDBQuery{

privateDBQueryreal=null;

@Override

publicStringrequest(){

//在真正需要的時(shí)候才創(chuàng)建真實(shí)的對(duì)象,創(chuàng)建過(guò)程可能很慢

if(real==null)

real=newDBQuery();

//在多線程環(huán)境下,返回一個(gè)虛假類,類似于Future模式

returnreal.request();

}

}最后,主函數(shù)如下。它引用IDBQuery接口,并使用代理類工作。publicclassMain{

publicstaticvoidmain(Stringargs[]){

IDBQueryq=newDBQueryProxy();//使用代理

q.request();//在真正使用時(shí)才創(chuàng)建真實(shí)對(duì)象

}

}注意:將代理模式用于實(shí)現(xiàn)延遲加載,可以有效地提升系統(tǒng)的啟動(dòng)速度,對(duì)改善用戶體驗(yàn)有很大的幫助。3.動(dòng)態(tài)代理介紹動(dòng)態(tài)代理是指在運(yùn)行時(shí)動(dòng)態(tài)生成代理類,即代理類的字節(jié)碼將在運(yùn)行時(shí)生成并載入當(dāng)前的ClassLoader。與靜態(tài)代理類相比,動(dòng)態(tài)類有諸多好處。首先,不需要為真實(shí)主題寫(xiě)一個(gè)形式上完全一樣的封裝類,假如主題接口中的方法很多,為每一個(gè)接口寫(xiě)一個(gè)代理方法也是非常煩人的事,如果接口有變動(dòng),則真實(shí)主題和代理類都要修改,不利于系統(tǒng)維護(hù);其次,使用一些動(dòng)態(tài)代理的生成方法甚至可以在運(yùn)行時(shí)指定代理類的執(zhí)行邏輯,從而大大提升系統(tǒng)的靈活性。注意:動(dòng)態(tài)代理使用字節(jié)碼動(dòng)態(tài)生成加載技術(shù),在運(yùn)行時(shí)生成并加載類。生成動(dòng)態(tài)代理類的方法很多,如JDK自帶的動(dòng)態(tài)代理、CGLIB、Javassist或者ASM庫(kù)。JDK的動(dòng)態(tài)代理使用簡(jiǎn)單,內(nèi)置在JDK中,因此不需要引入第三方Jar包,但功能相對(duì)比較弱。CGLIB和Javassist都是高級(jí)字節(jié)碼生成庫(kù),總體性能比JDK自帶的動(dòng)態(tài)代理好,而且功能十分強(qiáng)大。ASM是低級(jí)字節(jié)碼生成工具,使用ASM已經(jīng)近乎于在使用Java字節(jié)碼編程,對(duì)開(kāi)發(fā)人員要求最高,當(dāng)然也是一種性能最好的動(dòng)態(tài)代理生成工具。但ASM的使用實(shí)在過(guò)于煩瑣,而且性能也沒(méi)有數(shù)量級(jí)的提升,與CGLIB等高級(jí)字節(jié)碼生成工具相比,ASM程序的可維護(hù)性也較差,如果不是對(duì)性能有苛刻要求的場(chǎng)合,筆者還是推薦使用CGLIB或者Javassist。4.動(dòng)態(tài)代理實(shí)現(xiàn)仍以DBQueryProxy為例,使用動(dòng)態(tài)代理生成動(dòng)態(tài)類,替代上例中的DBQueryProxy。首先,使用JDK的動(dòng)態(tài)代理生成代理對(duì)象。JDK的動(dòng)態(tài)代理需要實(shí)現(xiàn)一個(gè)處理方法調(diào)用的Handler,用于實(shí)現(xiàn)代理方法的內(nèi)部邏輯。publicclassJdkDbQueryHandlerimplementsInvocationHandler{

IDBQueryreal=null;//主題接口

@Override

publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)

throwsThrowable{

if(real==null)

real=newDBQuery();//如果是第一次調(diào)用,則生成真實(shí)對(duì)象

returnreal.request();//使用真實(shí)主題完成實(shí)際操作

}

}以上代碼實(shí)現(xiàn)了一個(gè)Handler??梢钥吹?,它的內(nèi)部邏輯和DBQueryProxy是類似的。在調(diào)用真實(shí)主題的方法前,先嘗試生成真實(shí)主題對(duì)象,接著需要使用這個(gè)Handler生成動(dòng)態(tài)代理對(duì)象,代碼如下:publicstaticIDBQuerycreateJdkProxy(){

IDBQueryjdkProxy=(IDBQuery)Proxy.newProxyInstance(

ClassLoader.getSystemClassLoader(),

newClass[]{IDBQuery.class},

newJdkDbQueryHandler());//指定Handler

returnjdkProxy;

}以上代碼生成一個(gè)實(shí)現(xiàn)了IDBQuery接口的代理類,代理類的內(nèi)部邏輯由JdkDbQuery-Handler決定。生成代理類后,由newProxyInstance()方法返回該代理類的一個(gè)實(shí)例。至此,一個(gè)完整的JDK動(dòng)態(tài)代理就完成了。CGLIB和Javassist的使用和JDK的動(dòng)態(tài)代理的使用非常類似,下面嘗試使用CGLIB生成動(dòng)態(tài)代理。CGLIB也需要實(shí)現(xiàn)一個(gè)處理代理邏輯的切入類,代碼如下:publicclassCglibDbQueryInterceptorimplementsMethodInterceptor{

IDBQueryreal=null;

@Override

publicObjectintercept(Objectarg0,Methodarg1,Object[]arg2,

MethodProxyarg3)throwsThrowable{

if(real==null)//代理類的內(nèi)部邏輯

//和前文中的一樣

real=newDBQuery();

returnreal.request();

}

}在這個(gè)切入對(duì)象的基礎(chǔ)上可以生成動(dòng)態(tài)代理,代碼如下:publicstaticIDBQuerycreateCglibProxy(){

Enhancerenhancer=newEnhancer();

//指定切入器,定義代理類邏輯

enhancer.setCallback(newCglibDbQueryInterceptor());

//指定實(shí)現(xiàn)的接口

enhancer.setInterfaces(newClass[]{IDBQuery.class});

//生成代理類的實(shí)例

IDBQuerycglibProxy=(IDBQuery)enhancer.create();

returncglibProxy;

}使用Javassist生成動(dòng)態(tài)代理有兩種方式,一種是使用代理工廠創(chuàng)建,另一種是使用動(dòng)態(tài)代碼創(chuàng)建。使用代理工廠創(chuàng)建時(shí),方法與CGLIB類似,也需要實(shí)現(xiàn)一個(gè)用于處理代理邏輯的Handler,代碼如下:publicclassJavassistDynDbQueryHandlerimplementsMethodHandler{

IDBQueryreal=null;

@Override

publicObjectinvoke(Objectarg0,Methodarg1,Methodarg2,Object[]arg3)

throwsThrowable{

if(real==null)

real=newDBQuery();

returnreal.request();

}

}以這個(gè)Handler為基礎(chǔ),創(chuàng)建動(dòng)態(tài)Javasssit代理,代碼如下:publicstaticIDBQuerycreateJavassistDynProxy()throwsException{

ProxyFactoryproxyFactory=newProxyFactory();

//指定接口

proxyFactory.setInterfaces(newClass[]{IDBQuery.class});

ClassproxyClass=proxyFactory.createClass();

IDBQueryjavassistProxy=(IDBQuery)proxyClass.newInstance();

//設(shè)置Handler

((ProxyObject)javassistProxy).setHandler(newJavassistDynDbQueryHandler());

returnjavassistProxy;

}Javassist使用動(dòng)態(tài)Java代碼創(chuàng)建代理的過(guò)程和前文的方法略有不同。Javassist內(nèi)部可以通過(guò)動(dòng)態(tài)Java代碼生成字節(jié)碼,這種方式創(chuàng)建的動(dòng)態(tài)代理非常靈活,甚至可以在運(yùn)行時(shí)生成業(yè)務(wù)邏輯。publicstaticIDBQuerycreateJavassistBytecodeDynamicProxy()throwsException{

ClassPoolmPool=newClassPool(true);

//定義類名

CtClassmCtc=mPool.makeClass(IDBQuery.class.getName()+"Javassist

BytecodeProxy");

//需要實(shí)現(xiàn)的接口

mCtc.addInterface(mPool.get(IDBQuery.class.getName()));

//添加構(gòu)造函數(shù)

mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));

//添加類的字段信息,使用動(dòng)態(tài)Java代碼

mCtc.addField(CtField.make("public"+IDBQuery.class.getName()+

"real;",mCtc));

Stringdbqueryname=DBQuery.class.getName();

//添加方法,這里使用動(dòng)態(tài)Java代碼指定內(nèi)部邏輯

mCtc.addMethod(CtNewMethod.make("publicStringrequest(){if(real==

null)real=new"+dbqueryname+"();returnreal.request();}",mCtc));

//基于以上信息生成動(dòng)態(tài)類

Classpc=mCtc.toClass();

//生成動(dòng)態(tài)類的實(shí)例

IDBQuerybytecodeProxy=(IDBQuery)pc.newInstance();

returnbytecodeProxy;

}在以上代碼中,使用CtField.make()方法和CtNewMehod.make()方法在運(yùn)行時(shí)分別生成了代理類的字段和方法。這些邏輯由Javassist的CtClass對(duì)象處理,將Java代碼轉(zhuǎn)換為對(duì)應(yīng)的字節(jié)碼,并生成動(dòng)態(tài)代理類的實(shí)例。注意:與靜態(tài)代理相比,動(dòng)態(tài)代理可以大幅度地減少代碼行數(shù),并提升系統(tǒng)的靈活性。在Java中,動(dòng)態(tài)代理類的生成主要涉及對(duì)ClassLoader的使用。這里以CGLIB為例,簡(jiǎn)要闡述動(dòng)態(tài)類的加載過(guò)程。使用CGLIB生成動(dòng)態(tài)代理,首先需要生成Enhancer類實(shí)例,并指定用于處理代理業(yè)務(wù)的回調(diào)類。在Enhancer.create()方法中,會(huì)使用DefaultGenerator-Strategy.Generate()方法生成動(dòng)態(tài)代理類的字節(jié)碼,并保存在byte數(shù)組中。接著使用ReflectUtils.define-Class()方法通過(guò)反射調(diào)用ClassLoader.defineClass()方法,將字節(jié)碼裝載到ClassLoader中,完成類的加載。最后使用ReflectUtils.newInstance()方法通過(guò)反射生成動(dòng)態(tài)類的實(shí)例,并返回該實(shí)例。無(wú)論使用何種方法生成動(dòng)態(tài)代理,雖然實(shí)現(xiàn)細(xì)節(jié)不同,但主要邏輯都如圖2.4所示。圖2.4實(shí)現(xiàn)動(dòng)態(tài)代理的基本步驟前文介紹的幾種動(dòng)態(tài)代理的生成方法,性能有一定差異。為了能更好地測(cè)試它們的性能,去掉DBQuery類中的sleep()代碼,并使用以下方法進(jìn)行測(cè)試。publicstaticfinalintCIRCLE=30000000;

publicstaticvoidmain(String[]args)throwsException{

IDBQueryd=null;

longbegin=System.currentTimeMillis();

d=createJdkProxy();//測(cè)試JDK動(dòng)態(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();//測(cè)試CGLIB動(dòng)態(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();//測(cè)試Javassist動(dòng)態(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();

//測(cè)試Javassistbytecode動(dòng)態(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種代理,并對(duì)生成的代理類進(jìn)行高頻率的調(diào)用,最后輸出各個(gè)代理類的創(chuàng)建耗時(shí)、動(dòng)態(tài)類類名和方法調(diào)用耗時(shí)。結(jié)果如下: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的動(dòng)態(tài)類創(chuàng)建過(guò)程最快,這是因?yàn)樵谶@個(gè)內(nèi)置實(shí)現(xiàn)中,defineClass()方法被定義為native實(shí)現(xiàn),故性能高于其他幾種實(shí)現(xiàn)。但在代理類的函數(shù)調(diào)用性能上,JDK的動(dòng)態(tài)代理不如CGLIB和Javassist的基于動(dòng)態(tài)代碼的代理,而Javassist的基于代理工廠的代理實(shí)現(xiàn),代理的性能質(zhì)量最差,甚至不如JDK的實(shí)現(xiàn)。在實(shí)際開(kāi)發(fā)應(yīng)用中,代理類的方法調(diào)用頻率通常要遠(yuǎn)遠(yuǎn)高于代理類的實(shí)際生成頻率(相同類的重復(fù)生成會(huì)使用Cache),故動(dòng)態(tài)代理對(duì)象的方法調(diào)用性能應(yīng)該作為性能的主要關(guān)注點(diǎn)。注意:就動(dòng)態(tài)代理的方法調(diào)用性能而言,CGLIB和Javassist的基于動(dòng)態(tài)代碼的代理都優(yōu)于JDK自帶的動(dòng)態(tài)代理。此外,JDK的動(dòng)態(tài)代理要求代理類和真實(shí)主題都實(shí)現(xiàn)同一個(gè)接口,而CGLIB和Javassist則沒(méi)有強(qiáng)制要求。5.Hibernate中代理模式的應(yīng)用用代理模式實(shí)現(xiàn)延遲加載的一個(gè)經(jīng)典應(yīng)用就在Hibernate框架中。當(dāng)Hibernate加載實(shí)體bean時(shí),并不會(huì)一次性將數(shù)據(jù)庫(kù)所有的數(shù)據(jù)都裝載。默認(rèn)情況下,它會(huì)采取延遲加載的機(jī)制,以提高系統(tǒng)的性能。Hibernate中的延遲加載主要有兩種:一是屬性的延遲加載;二是關(guān)聯(lián)表的延時(shí)加載。這里以屬性的延遲加載為例,簡(jiǎn)單闡述Hibernate是如何使用動(dòng)態(tài)代理的。假定有以下用戶模型:publicclassUserimplementsjava.io.Serializable{

privateIntegerid;

privateStringname;

privateintage;

//省略getter和setter使用以下代碼,通過(guò)Hibernate加載一條User信息:publicstaticvoidmain(String[]args)throwsSecurityException,

NoSuchFieldException,

IllegalArgumentException,

IllegalAccessException{

//從數(shù)據(jù)庫(kù)載入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());

//實(shí)現(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實(shí)現(xiàn)的接口,最后輸出調(diào)用User的getName()方法,取得數(shù)據(jù)庫(kù)中的數(shù)據(jù)。這段程序的輸出結(jié)果如下(本例中使用的是Hibernate3.2.6,不同的Hibernate版本會(huì)有細(xì)節(jié)上的差異):ClassName:$xy.hibernate.User$

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論