本文內容來自《Java編程思想(第四版)》第二章《一切都是對象》和第五章《初始化與清理》。做爲一個使用了好幾年的Javaer,再次看編程思想的前面章節(不要問我爲何用再,儘管我第一遍看的啥,一點都不記得了。)編程
-----------------正文分割線---------------------微信
一個程序須要在計算機中運行,其本質是CPU操做內存中[1]的數據,進行某些運算的過程。因此這個問題是,計算機是如何操做這些數據的。網絡
要解答這個問題,必須知道1.這些數據指的是什麼?2.這些數據是如何存儲在內存中的?數據結構
a) 數據能夠存在哪裏?this
l 寄存器。存在這裏的讀取和寫入速度都是最快的,由於寄存器是CPU的一部分,而CPU是幹活的所有力量,因此CPU和寄存器的交互速度很是快,比內存快上百來倍(未考證)。至於爲何寄存器更快呢?由於寄存器用的是SRAM,而內存是DRAM,SRAM貴多了。至於爲何SRAM比DRAM快,這個我也不知道(喂,在這麼下去,要跑題了)。spa
l 堆棧:堆棧是存在內存的。用的是棧的數據結構,它快的緣由是棧的空間分配速度很快,只須要將指針上移下移就好了。(這種上下其手的事情慢了怎麼行。)指針
l 堆:堆也是放在內存的,並且只比堆棧少一個字!不過它的速度慢多了,這是由於它是堆的數據結構,堆天生就比棧慢。可是堆棧太大了效率就吃不消,你去試試對樂山大佛·上上下下啊!而堆就不同了,能存的東西就多多了。對象
l 其餘:包括常量堆,流、持久化對象(這個是存在磁盤和網絡上的)。內存
b) 而後呢?(如何操做存在內存的數據)資源
對內存中數據的操做,彙編是直接操做的,所以你能夠在組成原理中看到,內存尋址是一塊很重要的內容。C/C++是採用「指針」來間接操做的,它要負責內存的分配和回收,例如malloc就是要內存的節奏。
在Java中,一切都是對象。對象有可能很龐大也可能很小。Java將對象的引用存儲在棧上,這樣能夠快速找到定位,將實際的對象存儲在堆中。引用的本質是內存地址,它直接指向堆裏相應的對象。因此你在Java中輸出this的時候,其實輸出的是Java對象的內存地址。
前面說了,堆的操做比較慢。可是實際上,Java的堆並不慢,根據編程之美的說法,Java堆的效率能夠媲美某些語言(我也不知道某些是哪些)的棧。這主要得益於JVM良好的垃圾回收機制。
c) JVM垃圾回收機制
程序在運行過程當中,須要不斷的申請新的內存空間,而後釋放掉不用的對象。程序刪除不用的內容後,原來這些內容佔着的坑(內存空間)就會空出來。如此便會形成大量的內存碎片存在。內存碎片會下降內存使用率,並致使一些大內存對象的分配顯得困難。整合這些內存碎片,是很是消耗資源的行爲(可是又不得不整合)。JVM的內存回收機制(垃圾回收機制)乾的就是這個活。
那麼,JVM是如何進行垃圾回收的呢?
簡言之,就是根據某些策略,找到那些不用的對象,並將它們釋放掉。固然若是能順便解決內存碎片就更好了,麼麼噠。
d) 如何找到失活對象?(根據什麼策略呢)
有個理論上很是簡單的方式就是,若是一個對象被引用了,那麼給它的計數增長1,對象被釋放掉了,就減去1,那麼當一個對象的引用是0的時候,他就絕不留情的被垃圾回收機制回收了。這個方式叫作「引用計數」。可是這個方式有個致命的問題:當兩個對象相互引用的時候,計數就永遠不爲0,即使這些對象是須要被釋放掉的。
因此,JVM就不用這個方式啦。
JVM的作法是:遍歷棧裏的全部引用,有引用的對象即是活的對象,沒引用的對象,就是被當作孤魂野鬼來處理。爲了解決內存碎片的問題,JVM使用了一種叫作「中止-複製(stop-and-copy)」的作法,即暫停當前程序,而後將活的對象複製到另外一片內存區域;如此,既解決了內存碎片(新複製的對象在內存上是連續的),有解決了垃圾回收(沒有引用的對象就被拋棄了)。可是這樣子,效率上並不過高,另外,還須要一大片內存來作備胎。另外,程序穩定運行後,內存碎片可能並很少。
這時候,JVM的另外一個套機制,叫作標記-清掃(mark-and-sweep)的方式。這種方式也是從棧出發,遍歷全部引用,爲有引用的對象標記存活標記;標記以後,再開始清掃,即清理沒被標記的對象。這時候獲得的結果,其內存是不連續的。
JVM會監視內存使用狀態,在程序穩定的時候,啓動標記-清掃方式,當堆碎片過多的時候,啓動中止-複製方式。JVM管這個過程叫作自適應的垃圾回收機制。
在上面所述的過程當中,內存是以塊爲單位進行分配的。較大的對象會佔據一整個塊。每一個塊都有一個參數叫作代數(generation count),標記其是否存活。對於大型對象,在中止-複製過程當中,也不會被再複製一遍,只是代數增長。(這個是爲了減小複製的內容,從而減小內存佔用。)