麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學院 > 開發設計 > 正文

Java理論與實踐:再談Urban性能傳言

2019-11-18 13:33:42
字體:
來源:轉載
供稿:網友

  java? 語言遭到許多性能方面的攻擊。雖然有些攻擊可能是名符其實的,但是看看公告板和新聞組上關于這一主題的貼子,可以發現,對于 Java 虛擬機(JVM)實際的工作方式存在許多誤解。在本月的 Java 理論與實踐 中,Brian Goetz 駁斥了反復重復的有關 JVM 分配慢的傳言。請與作者和其他讀者在配套的 討論組 上分享您對這篇文章的想法。
  
  繼續,弄得一團糟
  
  沒有必要搜索眾多的 blog 或 Slashdot 貼子,去尋找像“垃圾收集永遠不會像直接內存治理一樣有效”這樣能夠說服人的陳述。而且,從某個方面來說,這些話說的是對的 ―― 動態內存治理并不一樣快 ―― 而是快得多。malloc/free 技術一次處理一個內存塊,而垃圾收集機制則采用大批量方式處理內存治理,從而形成更多的優化機會(以一些可以預見到的損失為代價)。
  
  這條“聽起來有理的意見” (以大批量清理垃圾要比一天到晚一點點兒清理垃圾更輕易)得到了數據的證實。一項研究(Zorn; 請參閱 參考資料)測量了在許多常見 C++ 應用程序中,用保守的 Boehm-Demers-Weiser(BDW)替換 malloc 的效果,結果是:許多程序在采用垃圾收集而不是傳統的分配器運行時,表現出了速度提升。(BDW 是個保守的、不移動的垃圾收集器,嚴重地限制了對分配和回收進行優化的能力,也限制了改善內存位置的能力;像 JVM 中使用的那些精確的浮動收集器可以做得更好。)
  
  在 JVM 中的分配并不總是這么快,早期 JVM 的分配和垃圾收集性能實際上很差,這當然就是 JVM 分配慢這一說法的起源。在非常早的時候,我們看到過許多“分配慢”的意見 ―― 因為就像早期 JVM 中的一切一樣,它確實慢 ―― 而性能顧問提供了許多避免分配的技巧,例如對象池。(公共服務聲明:除了對最重量的對象之外,對象池現在對于所有對象都是嚴重的性能損失,而且要在不造成并發瓶頸的情況下使用對象池也很需要技巧。)但是,從 JDK 1.0 開始已經發生了許多變化;JDK 1.2 中引入的分代收集器(generational collector)支持簡單得多的分配方式,可以極大地提高性能。
  
  分代垃圾收集
  
  分代垃圾收集器把堆分成多代;多數 JVM 使用兩代,“年輕代”和“年老代”。對象在年輕代中分配;假如它們在一定數量的垃圾收集之后仍然存在,就被當作是”長壽的“,并晉升到年老代。
  
  HotSpot 提供了使用三個年輕代收集器的選擇(串行拷貝、并行拷貝和并行清理),它們都采用“拷貝”收集器的形式,有幾個重要的公共特征。拷貝收集器把內存空間從中間分成兩半,每次只使用一半。開始時,使用中的一半構成了可用內存的一個大塊;分配器滿足分配請求時,返回它沒有使用的空間的前 N 個字節,并把指針(分隔“使用”部分)從“自由”部分移動過來,如清單 1 的偽代碼所示。當使用的那一半用滿時,垃圾收集器把所有活動對象(不是垃圾的那些對象)拷貝到另一半的底部(把堆壓縮成連續的),然后從另一半開始分配。
  
  清單 1. 在存在拷貝收集器的情況下,分配器的行為
  
  void *malloc(int n) {
  if (heapTop - heapStart < n)
  doGarbageCollection();
  
  void *wasStart = heapStart;
  heapStart += n;
  return wasStart;
  }
  
  從這個偽代碼可以看出為什么拷貝收集器可以實現這么快的分配 ―― 分配新對象只是檢查在堆中是否還有足夠的剩余空間,假如還有,就移動指針。不需要搜索自由列表、最佳匹配、第一匹配、lookaside 列表 ,只要從堆中取出前 N 個字節,就成功了。
  
  如何回收?
  
  但是分配僅僅是內存治理的一半,回收是另一半。對于多數對象來說,直接垃圾收集的成本為零。這是因為,拷貝收集器不需要訪問或拷貝死對象,只處理活動對象。所以在分配之后很快就變成垃圾的對象,不會造成收集周期的工作量。
  
  在典型的面向對象程序中,絕大多數對象(根據不同的研究,在 92% 到 98% 之間)“死于年輕”,這意味著它們在分配之后,通常在下一次垃圾收集之前,很快就變成垃圾。(這個屬性叫作 分代假設,對于許多面向對象語言已經得到實際測試,證實為真。)所以,不僅分配要快,對于多數對象來說,回收也要自由。
  
  線程本地分配
  
  假如分配器完全像 清單 1 所示的那樣實現,那么共享的 heapStart 字段會迅速變成顯著的并發瓶頸,因為每個分配都要取得保護這個字段的鎖。為了避免這個問題,多數 JVM 采用了 線程本地分配塊,這時每個線程都從堆中分配一個更大的內存塊,然后順序地用這個線程本地塊為小的分配請求提供服務。所以,線程花在獲得共享堆鎖的大量時間被大大減少,從而提高了并發性。(在傳統的 malloc 實現的情況下要解決這個問題更困難,成本更高;把線程支持和垃圾收集都構建進平臺促進了這類協作。)
  
  堆棧分配
  
  C++ 向程序員提供了在堆或堆棧中分配對象的選擇。基于堆棧的分配更有效:分配更便宜,回收成本真正為零,而且語言提供了隔離對象生命周期的幫助,減少了忘記釋放對象的風險。另一方面,在 C++ 中,在發布或共享基于堆棧的對象的引用時,必須非常小心,因為在堆棧幀整理時,基于堆棧的對象會被自動釋放,從而造成孤懸的指針。
  
  基于堆棧的分配的另一個優勢是它對高速緩存更加友好。在現代的處理器上,緩存遺漏的成本非常顯著,所以假如語言和運行時能夠幫助程序實現更好的數據位置,就會提高性能。堆棧的頂部通常在高速緩存中是“熱”的,而堆的頂部通常是“冷”的(因為從這部分內存使用之后可能過了很長時間)。所以,在堆上分配對象,比起在堆棧上分配對象,會帶來更多緩存遺漏。
  
  更糟的是,在堆上分配對象時,緩存遺漏還有一個非凡討厭的內存交互。在從堆中分配內存時,不管上次使用內存之后留下了什么內容,內存中的內容都被當作垃圾。假如在堆的頂部分配的內存塊不在緩存中,執行會在內存內容裝入緩存的過程中出現延遲。然后,還要用 0 或其他初始值覆蓋掉剛剛費時費力裝入緩存的那些值,從而造成大量內存活動的浪費。(有些處理器,例如 Azul 的 Vega,包含加速堆分配的硬件支持。)
  
  escape 分析
  
  Java 語句沒有提供任何明確地在堆棧上分配對象的方式,但是這個事實并不影響 JVM 仍然可以在適當的地方使用堆棧分配。JVM 可以使用叫作 escape 分析 的技術,通過這項技術,JVM 可以發現某些對象在它們的整個生命周期中都限制在單一線程內,還會發現這個生命周期綁定到指定堆棧幀的生命周期上。這樣的對象可以安全地在堆棧上而不是在堆上分配。更好的是,對于小型對象,JVM 可以把分配工作完全優化掉,只把對象的字段放入寄存器。
  
  清單 2 顯示了一個可以用 escape 分析把堆分配優化掉的示例。Component.getLocation() 方法對組件的位置做了一個保護性的拷貝,這樣調用者就無法在不經意間改變組件的實際位置。先調用 getDistanceFrom() 得到另一個組件的位置,其中包括對象的分配,然后用 getLocation() 返回的 Point 的 x 和 y 字段計算兩個組件之間的距離。
  
  清單 2. 返回復合值的典型的保護性拷貝方式
  
  public class Point {
  PRivate int x, y;
  public Point(int x, int y) {
  this.x = x; this.y = y;
  }
  public Point(Point p) { this(p.x, p.y); }
  public int getX() { return x; }
  public int getY() { return y; }
  }
  
  public class Component {
  private Point location;
  public Point getLocation() { return new Point(location); }
  
  public double getDistanceFrom(Component other) {
  Point otherLocation = other.getLocation();
  int deltaX = otherLocation.getX() - location.getX();
  int deltaY = otherLocation.getY() - location.getY();
  return Math.sqrt(deltaX*deltaX + deltaY*deltaY);
  }
  }
  
  getLocation() 方法不知道它的調用者要如何處理它返回的 Point;有可能得到一個指向 Point 的引用,比如把它放在集合中,所以 getLocation() 采用了保護性的編碼方式。但是,在這個示例中,getDistanceFrom() 并不會這么做,它只會使用 Point 很短的時間,然后釋放它,這看起來像是對完美對象的浪費。
  
  聰明的 JVM 會看出將要進行的工作,并把保護性拷貝的分配優化掉。首先,對 getLocation() 的調用會變成內聯的,對 getX() 和 getY() 的調用也同樣處理,從而導致 getDistanceFrom() 的表現會像清單 3 一樣有效。
  
  清單 3. 偽代碼描述了把內聯優化應用到 getDistanceFrom() 的結果
  
  public double getDistanceFrom(Component other) {
  Point otherLocation = new Point(other.x, other.y);
  int deltaX = otherLocation.x - location.x;
  int deltaY = otherLocation.y - location.y;
  return Math.sqrt(deltaX*deltaX + deltaY*deltaY);
  }
  
  在這一點上,escape 分析可以顯示在第一行分配的對象永遠不會脫離它的基本塊,而 getDistanceFrom() 也永遠不會修改 other 組件的狀態。(escape 指的是對象引用沒有保存到堆中,或者傳遞給可能保留一份拷貝的未知代碼。)假如 Point 真的是線程本地的,而且也清楚它的生命周期限制在分配它的基本塊內,那么它既可以進行堆棧分配,也可以完全優化掉,如清單 4 所示。
  
  清單 4. 偽代碼描述了從 getDistanceFrom() 優化掉分配后的結果
  
  public double getDistanceFrom(Component other) {<

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 99国内精品 | 欧美成年人视频在线观看 | 国产亚洲精品久久久久婷婷瑜伽 | 日本高清无遮挡 | 一区视频 | 国产成人在线观看网站 | 欧美一级黄色片在线观看 | 亚洲va久久久噜噜噜久久男同 | 中午字幕无线码一区2020 | 极品大长腿啪啪高潮露脸 | 欧美高清第一页 | 日本看片一区二区三区高清 | 亚洲精品欧美二区三区中文字幕 | 中文字幕网址 | 亚洲va久久久噜噜噜久久男同 | 国产成年人小视频 | 永久免费毛片 | 久久久久国产成人精品亚洲午夜 | 毛片在线视频免费观看 | 国产精品久久久久久婷婷天堂 | 国产瑟瑟视频 | 久草在线手机观看 | 国产成人在线综合 | 性视频久久 | 久久99久久98精品免观看软件 | 久久精品视频69 | 欧美一级特黄a | 黄色片在线观看网站 | 伊久在线| 国产99久久久久久免费看农村 | 少妇的肉体的满足毛片 | 亚州视频在线 | 91短视频网页版 | 性欧美大战久久久久久久免费观看 | 中文在线观看视频 | 免费看成年人网站 | 国产精品久久久久久久久久久久久久久 | 国产一国产一级毛片视频 | 欧美精品一区二区久久久 | 毛片成人| 久久久久久久久久久久免费 |