標籤:
一、Java記憶體配置
1. Java有幾種儲存地區?寄存器 --在CPU內部,開發人員不能通過代碼來控制寄存器的分配,有編譯器來管理。堆 --在windows下,棧是向底地址擴充的資料結構,是一塊連續的記憶體的地區,即棧頂的地址和棧的最大容量是系統預先定好的。 --優點:由系統自動分配,速度較快。 --缺點:不夠靈活,程式員無法控制。 --存放基礎資料型別 (Elementary Data Type)、開發過程中就建立的對象(而不是運行過程中)。棧 --是向高地址擴充的資料結構,是不連續的記憶體地區。 --在堆中,沒有堆棧指標,為此也就無法直接從處理器那邊獲得支援。 --堆的好處是有很大的靈活性。如Java編譯器不需要知道從堆裡需要分配多少儲存地區,也不必知道儲存的資料在堆裡會存活多長時間。靜態儲存地區與常量儲存地區 --靜態儲存區用來存放static類型的變數 --常量儲存區用來存放常量類型(final)類型的值,一般在唯讀記憶體中。非RAM儲存 --如流對象,是要發送到另一台機器上。 --持久化的對象,存放在磁碟上。 2. Java記憶體配置 --基礎資料類型直接在棧空間分配。 --方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收。 --引用資料類型,需要用new來建立,既在棧空間分配一個地址空間,又在堆空間指派至的類變數。 --方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完後從棧空間回收。 --局部變數new出來時,在棧空間和堆空間中分配空間,當局部變數生命週期結束後,棧空間立即被回收,堆空間地區等待GC回收。
--方法調用時傳入的literal參數,先在棧空間分配,在方法調用完成後從棧空間釋放。 --字串常量在DATA地區分配,this在對空間分配 --數組既在棧空間分配數組名稱,又在堆空間分配數組實際的大小 3. Java記憶體模型 Java虛擬機器將其管轄的記憶體大致分三個邏輯部分:方法區、java棧、java堆。 --方法區是靜態分配的,編譯器將變數綁定在某個儲存位置上,而且這些綁定不會再運行時改變。 常數池,原始碼中的命名常量、string常量和static變數儲存在方法區。 --Java Stack是一個邏輯概念,特點是後進先出。一個棧的空間可能是連續的,也可能是不連續的。 最典型的stack應用是方法的調用,java虛擬機器每調用一次方法就建立一個方法幀(frame),退出時則對應的方法幀被彈出。棧中儲存的資料也是運行時確定的。 --Java堆分配 意味著以隨意的順序,在運行時進行儲存空間分配和收回的記憶體管理模型。 堆中儲存的資料常常是大小、數量和生命期在編譯時間無法確定的。java對象的記憶體總是在heao中分配。
二、Java記憶體回收(GC)1. JVM運行環境中垃圾對象的定義 一個對象建立後被放置在JVM的堆記憶體中,當永遠不再引用這個對象時,它將被JVM在堆記憶體中回收。或當對象在JVMRunspace中無法通過根集合(root set)到達時,這個對象就被稱為垃圾對象。 2. 堆記憶體 在JVM啟動時被建立;堆記憶體中所儲存的對象可以被JVM自動回收,不能通過其他外部手段回收。 堆記憶體可分為兩個地區:新對象區和老對象區 --新對象區可分為三個小區:Eden區、From區、To區 3. JVM中對象的生命週期 建立階段 --為對象分配儲存空間 --開始構造對象 --遞迴調用其超類的構造方法 --進行對象執行個體初始化與變數初始化 --執行構造方法體 應用階段 --特徵:系統至少維護著對象的一個強引用;所有對該對象引用強引用(除非顯示聲明為其他引用) 不可視階段 --如果一個對象已使用完,並且在其可視地區不再使用,應該主動將其設定為null,即obj=null;這樣可以協助JVM及時發現這個垃圾對象,並且可以及時地回收該對象所佔用的系統資源。 4. Java中的析構方法finalize finalize()方法常稱之為終止器 protected void finalize(){ // finalization code here } 對象即將被銷毀時,有時需要做一些善後工作,可以把這些操作寫在finalize()方法裡。 Java終止器卻是在對象被銷毀時調用。一旦垃圾收集器準備好釋放無用對象佔用的儲存空間,它首先調用那些對象的finalize()方法,然後才真正回收對象的記憶體。而被丟棄的對象何時被銷毀,應用是無法獲知的。大多數場合,被丟棄對象在應用終止或仍未銷毀。到程式結束的時候,並非所有收尾模組都會得到調用。 5. 應用能干預記憶體回收嗎? 在應用代碼裡控制JVM的記憶體回收運轉是不可能的事。 對記憶體回收有兩個途徑。第一個就是將指向某對象的所有引用變數全部移走。這就相當於向JVM發了一個訊息:這個對象不要了。第二個是調用庫方法System.gc()。第一個是一個告知,而調用System.gc()也僅僅是一個請求。JVM接收這個訊息後,並不是立即做記憶體回收,而只是對幾個記憶體回收演算法做了加權,使記憶體回收操作容易發生,或提早發生,或回收較多而已。 希望JVM及時回收垃圾,是一種需求。其實,還有相反的一種需要:在某段時間內最好不要回收垃圾。要求運行速度最快的即時系統,特別是嵌入式系統,往往希望如此。 Java的記憶體回收機制是為所有java應用進程服務的。因此,任何一個進程都不能命令記憶體回收機製做什麼、怎麼做、做多少。 6. 記憶體回收演算法
*引用計數 該演算法在java虛擬機器沒被使用過,主要是循環參考問題,因為計數並不記錄誰指向他,無法發現這些互動自引用對象。 --怎麼計數? 當引用串連到對象時,對象計數加1 當引用離開範圍或被置為null時減1 --怎麼回收? 遍曆對象列表,計數為0就釋放 --有什麼問題? 循環參考問題
*標記演算法標記演算法的思想是從堆棧和靜態儲存區的對象開始,遍曆所有引用,標記活的對象。對於標記後有兩種處理方式:(1)停止-複製 --所謂停止,就是停止在啟動並執行程式,進行記憶體回收 --所謂複製,就是將活的對象複製到另一個堆上,以使記憶體更緊湊 --優點在於,當大塊記憶體釋放時,有利於整個記憶體的重新分配 --有什麼問題? ->停止,幹擾程式的正常運行 ->複製,明顯耗費大量時間 ->如果程式比較穩定,垃圾比較少,那麼每次重新複製量是非常大的,非常不合算(2)標記-清除 --也就是將標記為非活的對象釋放,也必須暫停程式運行 --優點就是在程式比較穩定,垃圾比較少的時候,速度比較快 --有什麼問題? 很顯然停止程式運行是一個問題,只清楚也會造成很多記憶體片段 --為什麼這兩個演算法都要暫停程式運行? 如果不暫停,剛才的標記會被啟動並執行程式弄亂
Java記憶體結構