熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Javascript >> 正文

關於垃圾收集的一些話

2022-06-13   來源: Javascript 

  很難相信Java居然能和C++一樣快甚至還能更快一些
  據我自己的實踐這種說法確實成立然而我也發現許多關於速度的懷疑都來自一些早期的實現方式由於這些方式並非特別有效所以沒有一個模型可供參考不能解釋Java速度快的原因
  我之所以想到速度部分原因是由於C++模型C++將自己的主要精力放在編譯期間靜態發生的所有事情上所以程序的運行期版本非常短小和快速C++也直接建立在C模型的基礎上(主要為了向後兼容)但有時僅僅由於它在C中能按特定的方式工作所以也是C++中最方便的一種方法最重要的一種情況是C和C++對內存的管理方式它是某些人覺得Java速度肯定慢的重要依據在Java中所有對象都必須在內存裡創建
  而在C++中對象是在堆棧中創建的這樣可達到更快的速度因為當我們進入一個特定的作用域時堆棧指針會向下移動一個單位為那個作用域內創建的以堆棧為基礎的所有對象分配存儲空間而當我們離開作用域的時候(調用完畢所有局部構建器後)堆棧指針會向上移動一個單位然而在C++裡創建內存堆(Heap)對象通常會慢得多因為它建立在C的內存堆基礎上這種內存堆實際是一個大的內存池要求必須進行再循環(再生)在C++裡調用delete以後釋放的內存會在堆裡留下一個洞所以再調用new的時候存儲分配機制必須進行某種形式的搜索使對象的存儲與堆內任何現成的洞相配否則就會很快用光堆的存儲空間之所以內存堆的分配會在C++裡對性能造成如此重大的性能影響對可用內存的搜索正是一個重要的原因所以創建基於堆棧的對象要快得多
  同樣地由於C++如此多的工作都在編譯期間進行所以必須考慮這方面的因素但在Java的某些地方事情的發生卻要顯得動態得多它會改變模型創建對象的時候垃圾收集器的使用對於提高對象創建的速度產生了顯著的影響從表面上看這種說法似乎有些奇怪——存儲空間的釋放會對存儲空間的分配造成影響但它正是JVM采取的重要手段之一這意味著在Java中為堆對象分配存儲空間幾乎能達到與C++中在堆棧裡創建存儲空間一樣快的速度
  可將C++的堆(以及更慢的Java堆)想象成一個庭院每個對象都擁有自己的一塊地皮在以後的某個時間這種不動產會被拋棄而且必須再生但在某些JVM裡Java堆的工作方式卻是頗有不同的它更象一條傳送帶每次分配了一個新對象後都會朝前移動這意味著對象存儲空間的分配可以達到非常快的速度堆指針簡單地向前移至處女地所以它與C++的堆棧分配方式幾乎是完全相同的(當然在數據記錄上會多花一些開銷但要比搜索存儲空間快多了)
  現在大家可能注意到了堆事實並非一條傳送帶如按那種方式對待它最終就要求進行大量的頁交換(這對性能的發揮會產生巨大干擾)這樣終究會用光內存出現內存分頁錯誤所以這兒必須采取一個技巧那就是著名的垃圾收集器它在收集垃圾的同時也負責壓縮堆裡的所有對象堆指針移至盡可能靠近傳送帶開頭的地方遠離發生(內存)分頁錯誤的地點垃圾收集器會重新安排所有東西使其成為一個高速無限自由的堆模型同時游刃有余地分配存儲空間
  為真正掌握它的工作原理我們首先需要理解不同垃圾收集器(GC)采取的工作方案一種簡單但速度較慢的GC技術是引用計數這意味著每個對象都包含了一個引用計數器每當一個句柄同一個對象連接起來時引用計數器就會增值每當一個句柄超出自己的作用域或者設為null時引用計數就會減值這樣一來只要程序處於運行狀態就需要連續進行引用計數管理——盡管這種管理本身的開銷比較少垃圾收集器會在整個對象列表中移動巡視一旦它發現其中一個引用計數成為就釋放它占據的存儲空間但這樣做也有一個缺點若對象相互之間進行循環引用那麼即使引用計數不是仍有可能屬於應收掉的垃圾為了找出這種自引用的組要求垃圾收集器進行大量額外的工作引用計數屬於垃圾收集的一種類型但它看起來並不適合在所有JVM方案中采用
  在速度更快的方案裡垃圾收集並不建立在引用計數的基礎上相反它們基於這樣一個原理所有非死鎖的對象最終都肯定能回溯至一個句柄該句柄要麼存在於堆棧中要麼存在於靜態存儲空間這個回溯鏈可能經歷了幾層對象所以如果從堆棧和靜態存儲區域開始並經歷所有句柄就能找出所有活動的對象對於自己找到的每個句柄都必須跟蹤到它指向的那個對象然後跟隨那個對象中的所有句柄跟蹤追擊到它們指向的對象……等等直到遍歷了從堆棧或靜態存儲區域中的句柄發起的整個鏈接網路為止中途移經的每個對象都必須仍處於活動狀態注意對於那些特殊的自引用組並不會出現前述的問題由於它們根本找不到所以會自動當作垃圾處理
  在這裡闡述的方法中JVM采用一種自適應的垃圾收集方案對於它找到的那些活動對象具體采取的操作取決於當前正在使用的是什麼變體其中一個變體是停止和復制這意味著由於一些不久之後就會非常明顯的原因程序首先會停止運行(並非一種後台收集方案)隨後已找到的每個活動對象都會從一個內存堆復制到另一個留下所有的垃圾除此以外隨著對象復制到新堆它們會一個接一個地聚焦在一起這樣可使新堆顯得更加緊湊(並使新的存儲區域可以簡單地抽離末尾就象前面講述的那樣)
  當然將一個對象從一處挪到另一處時指向那個對象的所有句柄(引用)都必須改變對於那些通過跟蹤內存堆的對象而獲得的句柄以及那些靜態存儲區域都可以立即改變但在遍歷過程中還有可能遇到指向這個對象的其他句柄一旦發現這個問題就當即進行修正(可想象一個散列表將老地址映射成新地址)
  有兩方面的問題使復制收集器顯得效率低下第一個問題是我們擁有兩個堆所有內存都在這兩個獨立的堆內來回移動要求付出的管理量是實際需要的兩倍為解決這個問題有些JVM根據需要分配內存堆並將一個堆簡單地復制到另一個
  第二個問題是復制隨著程序變得越來越健壯它幾乎不產生或產生很少的垃圾盡管如此一個副本收集器仍會將所有內存從一處復制到另一處這顯得非常浪費為避免這個問題有些JVM能偵測是否沒有產生新的垃圾並隨即改換另一種方案(這便是自適應的緣由)另一種方案叫作標記和清除Sun公司的JVM一直采用的都是這種方案對於常規性的應用標記和清除顯得非常慢但一旦知道自己不產生垃圾或者只產生很少的垃圾它的速度就會非常快
  標記和清除采用相同的邏輯從堆棧和靜態存儲區域開始並跟蹤所有句柄尋找活動對象然而每次發現一個活動對象的時候就會設置一個標記為那個對象作上記號但此時尚不收集那個對象只有在標記過程結束清除過程才正式開始在清除過程中死鎖的對象會被釋放然而不會進行任何形式的復制所以假若收集器決定壓縮一個斷續的內存堆它通過移動周圍的對象來實現
  停止和復制向我們表明這種類型的垃圾收集並不是在後台進行的相反一旦發生垃圾收集程序就會停止運行在Sun公司的文檔庫中可發現許多地方都將垃圾收集定義成一種低優先級的後台進程但它只是一種理論上的實驗實際根本不能工作在實際應用中Sun的垃圾收集器會在內存減少時運行除此以外標記和清除也要求程序停止運行
  正如早先指出的那樣在這裡介紹的JVM中內存是按大塊分配的若分配一個大塊頭對象它會獲得自己的內存塊嚴格的停止和復制要求在釋放舊堆之前將每個活動的對象從源堆復制到一個新堆此時會涉及大量的內存轉換工作通過內存塊垃圾收集器通常可利用死塊復制對象就象它進行收集時那樣每個塊都有一個生成計數用於跟蹤它是否依然存活通常只有自上次垃圾收集以來創建的塊才會得到壓縮對於其他所有塊如果已從其他某些地方進行了引用那麼生成計數都會溢出這是許多短期的臨時的對象經常遇到的情況會周期性地進行一次完整清除工作——大塊頭的對象仍未復制(只是讓它們的生成計數溢出)而那些包含了小對象的塊會進行復制和壓縮JVM會監視垃圾收集器的效率如果由於所有對象都屬於長期對象造成垃圾收集成為浪費時間的一個過程就會切換到標記和清除方案類似地JVM會跟蹤監視成功的標記與清除工作若內存堆變得越來越散亂就會換回停止和復制方案自定義的說法就是從這種行為來的我們將其最後總結為根據情況自動轉換停止和復制/標記和清除這兩種模式
  JVM還采用了其他許多加速方案其中一個特別重要的涉及裝載器以及JIT編譯器若必須裝載一個類(通常是我們首次想創建那個類的一個對象時)會找到class文件並將那個類的字節碼送入內存此時一個方法是用JIT編譯所有代碼但這樣做有兩方面的缺點它會花更多的時間若與程序的運行時間綜合考慮編譯時間還有可能更長而且它增大了執行文件的長度(字節碼比擴展過的JIT代碼精簡得多)這有可能造成內存頁交換從而顯著放慢一個程序的執行速度另一種替代辦法是除非確有必要否則不經JIT編譯這樣一來那些根本不會執行的代碼就可能永遠得不到JIT的編譯
  由於JVM對浏覽器來說是外置的大家可能希望在使用浏覽器的時候從一些JVM的速度提高中獲得好處但非常不幸JVM目前不能與不同的浏覽器進行溝通為發揮一種特定JVM的潛力要麼使用內建了那種JVM的浏覽器要麼只有運行獨立的Java應用程序

From:http://tw.wingwit.com/Article/program/Java/Javascript/201311/25416.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.