中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

為JAVA性能而設計的示例分析

發布時間:2021-12-22 13:48:21 來源:億速云 閱讀:111 作者:小新 欄目:編程語言

這篇文章主要介紹為JAVA性能而設計的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!


  第一部分: 接口事宜

  概要
  許多通常的 Java 性能問題都起源于在設計過程早期中的類設計的思想, 早在許多開發者開始考慮性能問題之前. 在這個系列中, Brian Goetz討論了通常的 Java性能上的冒險以及怎么在設計時候避免它們.

許多程序員在開發周期的后期才可是考慮性能管理. 他們常常把性能優化拖延到最后, 希望能完全避免 -- 有時候這種策略是成功的. 但是早期的設計思想可以影響性能優化的需求及其成功. 如果性能是你的程序的一個重要指標, 那么性能管理應該從第一天起就和設開發周期整合在一起.

  這個系列探索一些早期的設計思想能夠極大影響應用程序性能的方法. 在這篇文章中, 我專注于最通常的性能問題中的一個: 臨時變量的創建. 一個類的對象創建方式常常在設計時候就確定了的 -- 但不是故意的 --, 就為后來的性能問題種下了種子.

  性能問題有各種形式. 最容易調整的是那些你簡單地為計算選擇了一個錯誤的算法 -- 就象使用使用冒泡算法來對一個大數據集進行排序, 或者在使用一個經常使用的數據項時不是做緩沖, 而是每次都計算. 你可以使用概要分析來簡單地找出這些瓶頸, 一旦找到了,你可以很容易地改正. 但是, 許多 Java 性能問題來自一個更深的, 更難改正的源頭 -- 一個程序組件的接口設計.

  今天大多數程序是由內部開發的或者外部買來的組件構建而成. 甚至在程序不是很大地依于已經存在的組件時, 面向對象的設計過程也鼓勵應用程序包裝成組件, 這樣就簡化了設計, 開發和測試過程. 這些優勢是不可否認的, 你應該認識到這些組件實現的接口可能極大地影響使用它們的程序的行為和性能.

  在這一點上, 你可能要問什么樣的接口和性能相關. 一個類的接口不僅定義了這個類可以實現那些功能, 也可以定義它的對象創建行為和使用它的方法調用序列. 一個類怎樣定義它的構造函數和方法決定了一個對象是否可以重用, 它的方法是否要創建 -- 或者要求它的客戶端創建 -- 中間對象,  以及一個客戶端需要調用多少方法來使用這個類.這些因素都會影響程序的性能.

  注意對象的創建

  一個最基本的 Java 性能管理原則就是: 避免大量的對象創建. 這不是說你應該不創建任何對象而放棄面向對象的好處. 但是你必須在執行性能相關的代碼時, 在緊循環中注意對象的創建. 對象的創建是如此地高代價, 以至于你應該在要求性能的情況下避免不必要的臨時或者中間對象的創建.

  String 類是在那些處理文本的程序中對象創建的主要來源. 因為 String 是不可修改的,每當一個 String 修改或創建, 就必須創建一個新的對象. 結果就是, 關注性能的程序應該避免大量 String 的使用. 但是, 這通常是不可能的. 甚至當你從你的代碼中完全除去對 String 的依賴, 你常常會發現你自己在使用一些具有根據 String 定義的接口的組件.所以, 你最后不得不使用 String.

  例子: 正規表達式匹配

  作為一個例子, 假設你寫一個叫做 MailBot 的郵件服務器. MailBot 需要處理 MIME 頭格式 -- 象發送日期或者發送者的 email 地址 -- 在每個信息的頂部. 使用一個匹配正規表達式的組件來使處理 MIME 頭的過程簡單一些. MailBot 足夠聰明, 不為每個頭的行或者頭的元素創建一個 String 對象. 相反, 它用輸入的文本填充了一個字符緩沖區, 通過對緩沖區的索引來確定要處理的頭的位置. MailBot 會調用正規表達式匹配器來處理每個頭行, 所以匹配器的性能就非常重要. 我們以一個正規表達式匹配器類的拙劣的接口作為例子:

public class AwfulRegExpMatcher {
 /** Create a matcher with the given regular expression and which will
     operate on the given input string */
 public AwfulRegExpMatcher(String regExp, String inputText);
 /** Retrieve the next match of the pattern against the input text,
     returning the matched text if possible or null if not */
 public String getNextMatch();
}

  甚至在這個類實現了一個有效的正規表達式匹配的算法的時候, 任何大量使用它的程序仍然難以忍受. 既然匹配器對象和輸入的文本聯系起來, 每一次你調用它, 你必須創建一個新的匹配器對象. 既然你的目標是減少不必要的對象的創建, 那么使這個匹配器可以賾將會是一個明顯的開始.

  下面的類定義演示了你的匹配器的另一個可能的接口, 允許你重用這個匹配器, 但仍然很壞.

public class BadRegExpMatcher {
 public BadRegExpMatcher(String regExp);
 /** Attempts to match the specified regular expression against the input
     text, returning the matched text if possible or null if not */
 public String match(String inputText);
 /** Get the next match against the input text, or return null if no match */
 public String getNextMatch();
}

  忽略正規表達式匹配中的精細點 -- 象返回匹配的子表達式, 這個看起來無害的類定義會出什么問題呢? 從功能上來看, 沒有. 但是從性能的角度來看, 許多. 首先, 匹配器需要它的調用者創建一個 String 來代表要匹配的文本. MailBot 試圖避免創建 String對象, 但是當它要找到一個要做正規表達式解析的頭時, 它不得不創建一個 String 來滿足 BadRegExpMatcher:

BadRegExpMatcher dateMatcher = new BadRegExpMatcher(...);
while (...) {
 ...
 String headerLine = new String(myBuffer, thisHeaderStart,
 thisHeaderEnd-thisHeaderStart);
 String result = dateMatcher.match(headerLine);
 if (result == null) { ... }
}

  第二, 匹配器創建了結果字符串甚至當 MailBot 只關心是否匹配了, 不需要匹配的文本時,這意味著要簡單使用 BadRegExpMatcher 來確認一個日期頭是否匹配一個特定的格式, 你必須創建兩個 String 對象 -- 匹配器的輸入和匹配的結果. 兩個對象可能看起來不多,但是如果你給 MailBot 處理的每個郵件的每個頭行都創建兩個對象, 這會極大地影響性能. 錯誤不在于 MailBot 的設計, 而在于 BadRegExpMatcher 類的設計 -- 或者使用.

  注意返回一個輕量型的 Match 對象 -- 可以提供 getOffset(), getLength(), egetMatchString() 方法 -- 而不是返回一個 String, 這不會很大提高性能. 因為創建一個 Match 對象可能比創建一個 String 代價要小 -- 包括產生一個 char[] 數組和復制數據, 你仍然創建了一個中間對象, 對你的調用者來說沒有價值.

  這已經足夠壞了, BadREgExpMatcher 強迫你使用它想看到的輸入形式, 而不是你可以提供的更有效的形式. 但是使用 BadRegExpMathcer 還有另一個危險, 潛在地給 MailBot的性能帶來更大的冒險: 在處理郵件頭的時候, 你開始有避免使用 String 的傾向. 但是既然你被迫創建許多 String 對象來滿足 BadRegExpMatcher, 你可能被引誘而放棄這個目標, 更加自由地使用 String. 現在, 一個組件的糟糕的設計已經影響了使用它的程序.
甚至你后來找到了一個更好的正規表達式的組件, 不需要你提供一個 String, 那時你的整個程序都會受影響.

  一個好一些的接口

  你怎樣定義 BadRegExpMatcher, 而不引起這樣的問題呢? 首先, BadRegExpMatcher 應該不規定它的輸入. 它應該可以接受它的調用者能夠有效提供的各種輸入格式. 第二, 它不應該自動給匹配結果產生一個 String; 應該返回足夠的信息, 這樣調用者如果愿意的話可以生成它. (為方便著想, 它可以提供一個方法來做這件事, 但不是必須的) 這里有一個好一些的接口:

class BetterRegExpMatcher {
 public BetterRegExpMatcher(...);
 /** Provide matchers for multiple formats of input -- String,
     character array, and subset of character array.  Return -1 if no
     match was made; return offset of match start if a match was
     made.  */
 public int match(String inputText);
 public int match(char[] inputText);
 public int match(char[] inputText, int offset, int length);
 /** Get the next match against the input text, if any */
 public int getNextMatch();
 /** If a match was made, returns the length of the match; between
     the offset and the length, the caller should be able to
     reconstruct the match text from the offset and length */
 public int getMatchLength();
 /** Convenience routine to get the match string, in the event the
     caller happens to wants a String */
 public String getMatchText();
}

  新的接口減少了調用者把輸入轉換成匹配器希望的格式這個要求. MailBot 現在可以象下面這樣調用 match():

int resultOffset = dateMatcher.match(myBuffer, thisHeaderStart,
thisHeaderEnd-thisHeaderStart);
if (resultOffset < 0) { ... }

  這就解決了不創建任何新對象的目標. 作為一個附加的獎勵, 它的接口設計風格加到了Java 的 "lots-of-simgle-methos" 設計哲學中.

  額外的對象創建給性能的確切的沖擊依賴于 matth() 所作的工作量. 你可以通過創建和計時兩個正規表達式匹配器類, 來確定一個性能差別的上限. 在 Sun JDK 1.3 中, 上面的代碼片段在 BetterRegExpMatcher 類中大約比 BadRegExpMatcher 類要快 50 倍左右. 使用一個簡單的字串匹配的實現, BetterRegExpMatcher 比相對應的 BadRegExpMatcher 要快5倍。

  交換類型

  BadRegExpMatcher 強迫 MailBot 把輸入文本從字符數組轉換成 String, 結果是造成了一些不必要的對象的創建. 更具諷刺意味的是, BadRegExpMatcher 的許多實現都立即把 String 轉換成一個字符數組, 使它容易對輸入文本進行訪問. 這樣不僅僅申請了另一齠象, 并且還意味著你做完了所有的工作, 最后的形式和開始時一樣. MailBot 和 BadRegExpMatcher都不想處理 String -- String 只是看起來象是在組件之間傳遞文本的很明顯的格式.

  在上面的 BadRegExpMatcher 例子中, String 類是作為一個交換類型的. 一個交換類型是一種不管是調用者還是被調用者都不想使用或者以它作為數據格式的一種類型, 但是兩個都能很容易地轉換它或者從它轉換. 以交換類型定義接口在保持靈活性的同時減少了接口的復雜性, 但是有時簡單性導致了高代價的性能.

  一個交換類型最典型的例子是 JDBC ResultSet 接口. 它不可能象任何本地數據庫提供的數據集一樣提供它的 ResultSet 接口, 但是 JDBC 驅動通過實現一個 ResultSet 可以很容易地把數據庫提供的本地數據表示包裝起來. 同樣, 客戶端程序也不能象這樣表示數據記錄, 但是你幾乎可以沒有困難地把 ResultSet 轉換為想要的數據表示. 在 JDBC 的例子中,你接受了這個層次的花費, 因為它帶來了標準化和跨數據庫實現的可移植性的好處. 但是,要注意交換類型帶來的性能代價.

  這完全不值得, 使用交換類型對性能的沖擊不容易度量. 如果你對上面調用 BadRegExpMatcher的代碼片段做測試的話, 它會在運行時創建 MailBot 的輸入 String; 但是, String 的產生只用來滿足 BadRegExpMatcher. 如果你想評定一個組件對程序性能的真正的沖擊, 你應該不僅僅度量它的代碼的資源使用狀況, 還有那些使用它和恢復的代碼. 這對于標準的測試工具此很難完成.

  結論

  不是所有的程序都關注于性能的, 不是所有的程序都有性能問題. 但是對那些關注這些的程序, 這篇文章所提到的都很重要, 因為它們不是在最后一分鐘就可以修改的. 既然在你編寫寫代碼使用一個類以后再修改它的接口非常困難, 那么在你的設計時期就花費一點額外的時間來考慮性能特性.

  在第二部分, 我會演示一些利用可修改性和不可修改性來減少不必要的對象創建的方法.

第二部分: 減少對象創建

  概要
  許多通常的 Java 性能問題都起源于在設計過程早期中的類設計的思想, 早在許多開發者開始考慮性能問題之前. 在這個系列中, Brian Goetz 討論了通常的 Java 性能上的冒險以及怎么在設計時候避免它們. 在第二部分, 他討論了減少臨時對象創建的一些技術。

  雖然許多程序員把性能管理一直推遲到開發過程的最后, 性能考慮應該從第一天起就和設計周期結合在一起. 這個系列探索一些早期的設計思想能夠極大影響應用程序性能的方法.在這篇文章里, 我繼續探索大量臨時對象創建的問題, 并且提供一些避免它們的一些技術.

  臨時對象就是一些生命周期比較短的對象, 一般用于保存其他數據而再沒有其他用途. 程序員一般用臨時變量向一個方法傳遞數據或者從一個方法返回數據. 第一部分探討了臨時對象是怎樣給一個程序的性能帶來負面的沖擊, 以及一些類接口的設計思想怎樣提供了臨時對象的創建. 避免了那些接口的創建, 你就能極大地減少那些影響你的程序性能的臨時對象創建的需求,

  只是對 String 說不嗎?

  當它要創建臨時變量時, String 類是最大的罪人中的一個. 為了演示, 在第一部分我寫了一個正規表達式匹配的例子, 通過和一個類似的但是經過仔細設計的接口相比較, 演示了看起來無害的接口是怎樣引起大量對象的創建, 而慢上幾倍. 這里是原來的和好一些的類的接口:

BadRegExpMatcher

public class BadRegExpMatcher {
 public BadRegExpMatcher(String regExp);
 /** Attempts to match the specified regular expression against the input
     text, returning the matched text if possible or null if not */
 public String match(String inputText);
}

BetterRegExpMatcher

class BetterRegExpMatcher {
 public BetterRegExpMatcher(...);
 /** Provide matchers for multiple formats of input -- String,
     character array, and subset of character array.  Return -1 if no
     match was made; return offset of match start if a match was
     made.  */
 public int match(String inputText);
 public int match(char[] inputText);
 public int match(char[] inputText, int offset, int length);
 /** If a match was made, returns the length of the match; between
     the offset and the length, the caller should be able to
     reconstruct the match text from the offset and length */
 public int getMatchLength();
 /** Convenience routine to get the match string, in the event the
     caller happens to wants a String */
 public String getMatchText();
}

  大量使用 BadREgExpMatcher 的程序比使用 BtterRegExpMatcher 的要慢好多. 首先,調用者不得不創建一個 String 傳入 match(), 接著 match() 又創建了一個 String 來返回匹配的文本. 結果是每次調用都有兩個對象創建, 看起來不多, 但是如果要經常調用match(), 這些對象創建帶給性能的代價就太打了. BadRegExpMatcher 的性能問題不是在它的實現中, 而是它的接口; 象它定義的接口, 沒有辦法避免一些臨時變量的創建.

  BetterRegExpMatcher 的 match() 用原類型(整數和字符數組)代替了 String 對象; 不需要創建中間對象來在調用者和 match() 之間傳遞信息.

  既然在設計時候避免性能問題要比寫完整個系統以后再修改要容易一些, 你應該注意你的類中控制對象創建的方法. 在 RegExpMatcher 的例子中, 它的方法要求和返回 String 對象, 就應該為潛在的性能冒險提個警告信號. 因為 String 類是不可變的, 除了最常用以外, 所有的 String 參數在每次調用處理函數時都需要創建一個新的 String.

  不可變性對于性能來說是否很壞?

  因為 String 經常和大量的對象創建聯系在一起, 一般來說歸咎于它的不可變性. 許多程序員認為不可變的對象與生俱來對性能沒有好處. 但是, 事實多少會更復雜一些. 實際上, 不可變性有時候提供了性能上的優勢, 可變性的對象有時候導致性能問題. 不管可變性對性能來說有幫助或者有害, 依賴于對象是怎么使用的.

  程序經常處理和修改文本字符串 -- 和不可變性非常不匹配. 每次你想處理一個 String --想查找和解析出前綴或者子串, 變小寫或者大寫, 或者把兩個字符串合并 -- 你必須創建一個新的 String 對象. (在合并的情況下, 編譯器也會隱藏地創建一個 StringBuffer() 對象)

  另一個方面, 一個不可變的對象的一個引用可以自由共享, 而不用擔心被引用的對象要被修改, 這個比可變對象提供性能優勢, 就象下一節例子所說的.

  可變的對象有它們自己的臨時對象問題.

  在 RegExpMatcher 的例子中, 你看見了 當一個方法返回一個 String 類型時, 它通常需要新建一個 String 對象. BadRegExpMatcher 的一個問題就是 match() 返回一個對象而不是一個原類型 -- 但是只因為一個方法返回一個對象, 不意味著必須有一個新對象創建. 考慮一下 java.awt 中的幾何類, 象 Point 和 Rectangle. 一個 Rectangle只是四個整數(x, y, 寬度, 長度)的容器. AWT Component 類存儲組件的位置, 通過getBounds()作為一個Rectangle 返回

public class Component {
 ...
 public Rectangle getBounds();
}

  在上面的例子中, getBounds() 只是一個存儲元 -- 它只使一些 Component 內部的一些狀態信息可用. getBounds() 需要創建它返回的 Rectangle 嗎? 可能. 考慮一下下面getBounds() 可能的實現.

public class Component {
 ...
 protected Rectangle myBounds;
 public Rectangle getBounds()  { return myBounds; }
}

  當一個調用者調用上面例子中的 getBounds(), 沒有新對象創建 -- 因為組件已經知道它在哪里 -- 所以 getBounds() 效率很高. 但是 Rectangle 的可變性又有了其他問題. 當一個調用者運行一下程序會發生什么呢?

Rectangle r = component.getBounds();
 ...
 r.height *= 2;

  因為 Rectangle 是可變的, 它在 Component 不知道的情況下使 Component 移動. 對象AWT 這樣的 GUI 工具箱來說, 這是個災難,  因為當一個組件移動以后, 屏幕需要重繪, 件監聽器需要被通知, 等等. 所以上面的實現 Component.getBounds() 的代碼看起來很危險. 一個安全一點的實現就象下面這樣:

public Rectangle getBounds() {
   return new Rectangle(myBounds.x, myBounds.y,
                        myBounds.height, myBounds.width);
 }

  但是現在, 每一個 getBounds() 的調用都創建一個新對象, 就象 RegExpMatcher 一樣.實際上, 下面的代碼片段創建了 4 個臨時對象:

int x = component.getBounds().x;
 int y = component.getBounds().y;
 int h = component.getBounds().height;
 int w = component.getBounds().width;

  在 String 的情況中, 對象創建是必要的, 因為 String 是不可變的. 但在這個例子中,對象的創建也是必要的, 因為 Rectangle 是可變的. 我們使用 String 避免了這個問題,在我們的接口中沒有使用對象. 雖然在 RegExpMatcher 的情況下很好, 這個方法不總是可行的或者是希望的. 幸運的是, 你可以在實際類的時候可以使用一些技巧, 來免除太多小對象的問題, 而不是完全避免小對象.

  減少對象的技巧 1: 加上好的存取函數

  在 Swing 工具箱的初始版本中, 對象小對象的臨時創建, 象 Point, Rectangle 和 Dimension極大地阻礙了性能. 把它們放在一個 Point 或者 Rectangle 中來一次返回多個值, 看起來更有效, 實際上, 對象的創建比多個方法調用代價更高. 在 Swing 的最后發布之前, 通過給 Component 和其他一些類加一些新的存取方法, 問題就簡單地解決了, 就象下面這樣:

public int getX()      { return myBounds.x; }
 public int getY()      { return myBounds.y; }
 public int getHeight() { return myBounds.height; }
 public int getWidth()  { return myBounds.width; }

  現在一個調用者可以這樣獲取邊界而不用創建對象:

int x = component.getX();
 int y = component.getY();
 int h = component.getHeight();
 int w = component.getWidth();

  getBounds() 的舊形式仍然支持; 好的存取方法簡單地提供了有效的方法來達到相同的目的. 結果是, Rectangle 的接口全部在 Component 中使用. 當修改 Swing 包支持和使用這樣的存取函數后, 在許多 Swing 操作中比以前要快到兩倍. 這很好, 因為 GUI 代碼非常注意性能 -- 用戶等待發生一些事, 希望 UI 操作瞬間完成.

  使用這個技術不好的地方就是你的對象提供了更多的方法, 有多于一個的方法來得到相同的信息, 就使文檔更大更復雜, 可能使用戶害怕. 但是就象 Swing 的例子顯示的, 在關注性能的情況下, 這樣的優化技術是有效的.

  技巧 2: 利用可變性

  除了給 Component 加上原類型的存儲函數 -- 象上面討論的 getX() 函數 -- 以外, Java 2 在 AWT 和 Swing 中也使用了另一種技術來減少對象創建, 允許一個調用者把邊界作為一個 Rectangle 得到, 但是不需要任何臨時對象的創建.

public Rectangle getBounds(Rectangle returnVal) {
   returnVal.x = myBounds.x;
   returnVal.y = myBounds.y;
   returnVal.height = myBounds.height;
   returnVal.width = myBounds.width;
   return returnVal;
 }

  調用者仍然需要創建一個 Rectangle 對象, 但它可以在后來的調用中重用. 如果一個調用者在一系列的 Component 中循環, 可以只創建一個 Rectangle 對象, 在每個 Component 中重用. 注意這個技術只用于可變性對象; 你不能用這種方法消除 String 的創建.

  技巧 3: 得到兩個中的最好的.

  一個解決在簡單類(象 Point 之類)的對象創建的問題, 更好的方法是使 Point 對象不可? 但是定義一個可變的子類, 就象下面這樣:

public class Point {
   protected int x, y;
   public Point(int x, int y) { this.x = x; this.y = y; }
   public final int getX() { return x; }
   public final int getY() { return y; }
 }

public class MutablePoint extends Point {
   public final void setX(int x) { this.x = x; }
   public final void setY(int y) { this.y = y; }
 }

public class Shape {
   private MutablePoint myLocation;
   public Shape(int x, int y) { myLocation = new MutablePoint(x, y); }
   public Point getLocation()       { return (Point) myLocation; }
 }

  在上面的例子中, Shape 可以安全返回一個 myLocation 的引用, 因為調用者試圖修改域或者調用設置函數會失敗. (當然, 調用者仍然可以把 Point 轉換為 MutablePoint, 但這明顯不安全, 這樣的調用者可能得到他們想要的) C++ 程序員可能注意到了這個技巧很象 C++ 中返回一個 Rectangle 的常量引用(cong Rectangle&) -- 一個 Java 不支持的特點.

  這個技巧 -- 返回一個具有可變的和不可變的類, 只允許讀的對象, 而不創建新對象 --在 Java 1.3 類庫 java.math.BigInteger 類中使用. MutableBigInteger 類不可見 --它是一個只在 java.math 類庫中內部使用的私有類型. 但是既然 BigInteger 的一些方法(象 gcd()) 在許多數學操作中都有, 在一個地方操作比創建上百個臨時變量性能提高非常大.

  結論

  所有的性能優化的建議中, 值得記住的是有許多程序的性能可以完全接受的情況. 在這些情況下, 不值得犧牲可讀性, 可維護性, 抽象, 或者其他可取的程序屬性來獲得性能. 但是, 既然許多性能問題的種子在設計時就種下了, 要注意到設計思想潛在地對性能的沖?當你設計的類在關注性能的情況性愛使用, 你可以有效地使用這里提到的技巧來減少臨時對象的創建,

  在第三部分中, 我將看看分布式的應用程序中特有的性能問題, 怎樣在設計時候找出和消除它們。

第三部分: 遠程接口

  概述
  許多 Java 的通常性能問題來源于設計過程早期的類設計想法中, 早在開發者開始考慮性能問題之前. 在這個系列中, Brian Goetz 討論了一些通常的 Java 性能的冒險, 解釋了怎樣在設計時間避免它們. 在這篇文章中, 它檢驗了遠程應用程序中的特定的性能問題.

  遠程調用的概念

  在分布式的應用程序中, 一個運行在一個系統中的對象可以調用另一個系統中的一個對象的方法. 這個通過很多使遠程對象表現為本地的結構的幫助而實現. 要訪問一個遠程對象,你首先要找到它, 可以通過使用目錄或者命名服務來實現, 象 RMI 注冊, JNDI, 或者 CORBA命名服務.

  當你通過目錄服務得到一個遠程對象的引用時, 你并沒有得到那個對象的實際的引用, 而是一個實現了和遠程對象同樣接口的stub對象的引用. 當你調用一個stub對象的方法時, 對象把方法的所有參數匯集起來 -- 把它們轉化成一個字節流的表現形式, 類似于序列化過程. 這個stub對象把匯集的參數通過網絡傳遞給一個skeleton對象, 把參數分解出來, 你想調用的實際的對象的方法. 然后這個方法向skeleton對象返回一個值, skeleton對象把它傳送給stub對象, stub對象把它分解出來, 傳遞給調用者. Phew! 一個單獨的調用要做這么多的工作. 很明顯, 除去表面的相似性, 一個遠程方法調用比本地方法調用更大.

  以上描述瀏覽了一些對于程序性能非常重要的細節. 當一個遠程方法返回的不是一個原類? 而是一個對象時, 會發生什么? 不一定. 如果返回的對象是一種支持遠程方法調用的類型, 它就創建一個中stub對象和一個skeleton對象, 在這種情況下需要在注冊表中查找一個遠潭韻,這顯然是一個高代價的操作. (遠程對象支持一種分布式的垃圾回收的形式, 包括了每一個參與的 JVM 維護一個線程來和其他 JVM 的維護線程進行通訊, 來回傳遞引用信息). 如果返回的對象不支持遠程調用, 這個對象所有的域和引用的對象都要匯集起來, 這也是一個代價的操作.

  遠程和本地方法調用的性能比較

  遠程對象訪問的性能特征和本地的不一樣:遠程對象的創建比本地對象創建代價要高. 不僅僅是當它不存在時要創建它, 而且stub對和skeleton對象也要創建, 還要互相感知.

  遠程方法調用還包括網絡的傳遞 -- 匯集起來的參數必須發送到遠程系統, 而且響應也需匯集起來, 在調用程序重新得到控制權之前發送回來. 匯集, 分解, 網絡延時, 實際的遠調用所導致的延遲都加在一起; 客戶端通常是等待所有這些而步驟完成. 一個遠程調用也大地依賴于底層網絡的延時.

  不同的數據類型有不同的匯集開支. 匯集原類型相對來說花費少一些; 匯集簡單的對象, Point 或者 String 要多一些; 匯集遠程對象要多得多, 而匯集那些引用非常多的對象的對象(象 collection 等)要更多. 這和本地調用完全矛盾, 因為傳遞一個簡單對象的引用比一個復雜對象的引用花費多.

  接口設計是關鍵

  設計不好的遠程接口可能完全消除一個程序的性能. 不幸的是, 對本地對象來說好的接口的特性對遠程對象可能不適合. 大量的臨時對象創建, 就象在本系列的第一, 二部分討論,也能阻礙分布式的應用程序, 但是大量的傳遞更是一個性能問題. 所以, 調用一個在一個時對象(比如一個 Point)中返回多個值的方法比多次調用來分別得到它們可能更有效.

  實際遠程應用程序的一些重要的性能指導:

  提防不必要的數據傳遞. 如果一個對象要同時得到幾個相關的項, 如果可能的話, 在一個遠程調用中實現可能容易一些.
  當調用者可能不必要保持一個遠程對象的引用時, 提防返回遠程的對象.當遠程對象不需要一個對象的拷貝時, 提防傳遞復雜對象.
  幸運的是, 你可以通過簡單查看遠程對象的接口來找出所有的問題. 要求做任何高層動作的方法調用序列可以從類接口中明顯看到. 如果你看到一個通常的高層操作需要許多連續的遠程方法調用, 這就是一個警告信號, 可能你需要重新查看一下類接口.

  減少遠程調用代價的技巧

  一個例子, 考慮下面假定的管理一個組織目錄的應用程序: 一個遠程的 Directory 對象包含了 DirectoryEntry 對象的引用, 表現了電話簿的入口.

public interface Directory extends Remote {
 DirectoryEntry[] getEntries();
 void addEntry(DirectoryEntry entry);
 void removeEntry(DirectoryEntry entry);
}
public interface DirectoryEntry extends Remote {
 String getName();
 String getPhoneNumber();
 String getEmailAddress();
}

  現在假設你想在一個 GUI email 程序中使用 Directory 的東西. 程序首先調用getEntries() 來得到入口的列表, 接著在每個入口中調用 getName(), 計算結果的列表,當用戶選擇一個時,  應用程序在相應的入口調用 getEmailAdress() 來得到 email 地址.

  在你能夠寫一封 email 之前有多少遠程方法調用必須發生? 你必須調用 getEntries() 一次, 地址簿中每個入口調用一次 getName(), 一次 getEmailAddress(). 所以如果在地址中有 N 個入口, 你必須進行 N + 2 次遠程調用. 注意你也需要創建 N + 1 個遠程對象引用, 也是一個代價很高的操作. 如果你的地址簿有許多入口的話, 不僅僅是打開 email 窗口的時候非常慢, 也造成了網絡阻塞, 給你的目錄服務程序造成高負載, 導致可擴展性的問題.

  現在考慮增強的 Directory 接口:

public interface Directory extends Remote {
 String[] getNames();
 DirectoryEntry[] getEntries();
 DirectoryEntry getEntryByName(String name);
 void addEntry(DirectoryEntry entry);
 void removeEntry(DirectoryEntry entry);
}

  這將減少多少你的 email 程序所造成的花費呢? 現在你可以調用 Directory.getNames()一次就可以同時得到所有的名字, 只需要給你想要發送 email 的容器調用 getEntryByName() .這個過程需要 3 個遠程方法調用, 而不是 N + 2, 和兩個遠程對象, 而不是 N + 1 個.如果地址簿有再多一點的名字, 這個調用的減少在程序的響應和網絡負載和系統負載有很大的不同.

  用來減少遠程調用和引用傳遞的代價的技術叫做使用次要對象標識符. 使用一個對象的標屬性, -- 在這個例子中, 是 name -- 而不是傳回一個遠程對象, 作為對象的一個輕量級曄斗?次要標識符包含了它描述的對象足夠的信息, 這樣你只需要獲取你實際需要的遠程對象.在這個目錄系統的例子中, 一個人的名字是一個好的次要標識符. 在另一個例子中, 一個安全皮包管理系統, 一個采購標識號可能是一個好的次要標識符.

  另一個減少遠程調用數量的技巧是塊獲取. 你可以進一步給 Directory 接口加個方法, 來一次獲取多個需要的 DirectoryEntry 對象:

public interface Directory extends Remote {
 String[] getNames();
 DirectoryEntry[] getEntries();
 DirectoryEntry getEntryByName(String name);
 DirectoryEntry[] getEntriesByName(String names[]);
 void addEntry(DirectoryEntry entry);
 void removeEntry(DirectoryEntry entry);
}

  現在你不僅可以得到需要的遠程 DirectoryEntry , 也可以用單獨一個遠程方法調用得到要的所有的入口. 雖然這并不減少匯集的代價, 但極大地較少了網絡往返的次數. 如果網延遲很重要的話, 就可以產生一個響應更快的系統(也能減少這個網絡的使用).

  照亮去向 RMI 層次的路徑的第三的技巧是不把 DirectoryEntry 作為一個遠程對象, 而把它定義為一個通常的對象, 帶有訪問 name, address, email address 和其他域的訪問函數.(在 CORBA 系統中, 我可能要使用類似的 object-by-value 機制.) 然后, 當 email 應用程序調用 getEntryName() 時, 它會獲取一個 entry 對象的值 -- 不需要創建一個stub對象或者skeleton對象, getEmailAddress() 的調用也是一個本地的調用而不是一個遠程的.

  當然, 所有這些技巧都都依賴于對遠程對象實際上是怎樣使用的理解上的, 但是對于這個理解, 你甚至不需要看一看遠程類的實現就可以找出一些潛在的嚴重性能問題. 

以上是“為JAVA性能而設計的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

浦江县| 康平县| 霍州市| 阿合奇县| 丹东市| 东至县| 武义县| 河源市| 沈丘县| 丰县| 阿合奇县| 江山市| 孟州市| 阳原县| 西畴县| 休宁县| 高陵县| 宁波市| 工布江达县| 额尔古纳市| 平定县| 漯河市| 皋兰县| 秭归县| 疏附县| 游戏| 利辛县| 郧西县| 凤冈县| 比如县| 堆龙德庆县| 扎赉特旗| 正镶白旗| 敖汉旗| 屏东市| 蚌埠市| 土默特右旗| 承德县| 子长县| 临清市| 吴江市|