在Java虛擬機中,JVM 內存區域主要分為線程私有、線程共享、直接內存三個區域,具體詳情如下:
線程私有(Theard Local Region): 數據區域生命周期與線程相同, 依賴用戶線程的啟動/結束 而 創建/銷毀(在Hotspot VM內, 每個線程都與操作系統的本地線程直接映射, 因此這部分內存區域的存/否跟隨本地線程的生/死對應)。
線程共享(Theard Shared Region): 隨虛擬機的啟動/關閉而創建/銷毀
直接內存(Direct Memory) : 非Java 虛擬機中JVM運行時數據區的一部分, 但也會被頻繁的使用: 在JDK 1.4引入的NIO提供了基于Channel與Buffer的IO方式, 它可以使用Native函數庫直接分配堆外內存, 然后使用DirectByteBuffer對象作為這塊內存的引用進行操作(詳見: Java I/O 擴展), 這樣就避免了在Java堆和Native堆中來回復制數據, 因此在一些場景中可以顯著提高性能。
由此可見,在Java 虛擬機JVM運行時數據區中,【程序計數器、虛擬機棧、本地方法區】屬于線程私有區域,【 JAVA 堆、方法區】屬于線程共享區域,都需要JVM GC管理的,而直接內存不受JVM GC管理的。
首先,對于線程私有區域中的【程序計數器、虛擬機棧、本地方法區】,主要詳情如下:
程序計數器:一塊較小的內存空間, 是當前線程所執行的字節碼的行號指示器,每條線程都要有一個獨立的程序計數器,這類內存也稱為“線程私有”的內存。正在執行java方法的話,計數器記錄的是虛擬機字節碼指令的地址(當前指令的地址)。如果還是Native方法,則為空。這個內存區域是唯一一個在虛擬機中沒有規定任何OutOfMemoryError情況的區域。
虛擬機棧:是描述java方法執行的內存模型,每個方法在執行的同時都會創建一個棧幀(Stack Frame)用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。棧幀( Frame)是用來存儲數據和部分過程結果的數據結構,同時也被用來處理動態鏈接 (Dynamic Linking)、 方法返回值和異常分派( Dispatch Exception)。棧幀隨著方法調用而創建,隨著方法結束而銷毀——無論方法是正常完成還是異常完成(拋出了在方法內未被捕獲的異常)都算作方法結束。
本地方法區:本地方法區和Java Stack作用類似, 區別是虛擬機棧為執行Java方法服務, 而本地方法棧則為Native方法服務, 如果一個VM實現使用C-linkage模型來支持Native調用, 那么該棧將會是一個C棧,但HotSpot VM直接就把本地方法棧和虛擬機棧合二為一。
其次,對于線程共享區域中的【 JAVA 堆、方法區】,主要詳情如下:
Java 堆(Java Heap): 是Java 虛擬機JVM運行時數據區中,被線程共享的一塊內存區域,創建的對象和數組都保存在Java堆內存中,也是垃圾收集器進行垃圾收集的最重要的內存區域。由于現代VM采用分代收集算法, 因此Java堆從GC的角度還可以細分為: 新生代(Eden區、From Survivor區和To Survivor區)和老年代。
方法區(Method Area)/永久代(Permanent Generation):我們常說的永久代, 用于存儲被JVM加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據. HotSpot VM把GC分代收集擴展至方法區, 即使用Java堆的永久代來實現方法區, 這樣HotSpot的垃圾收集器就可以像管理Java堆一樣管理這部分內存, 而不必為方法區開發專門的內存管理器(永久帶的內存回收的主要目標是針對常量池的回收和類型的卸載, 因此收益一般很小)。運行時常量池(Runtime Constant Pool)是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后存放到方法區的運行時常量池中。 Java虛擬機對Class文件的每一部分(自然也包括常量池)的格式都有嚴格的規定,每一個字節用于存儲哪種數據都必須符合規范上的要求,這樣才會被虛擬機認可、裝載和執行。
其中對于Java虛擬機JVM中的Java 堆主要分為【 新生代 、老年代 、永久代、元數據區】:
新生代(Young Generation):用來存放新生的對象。一般占據堆的1/3空間。由于頻繁創建對象,所以新生代會頻繁觸發MinorGC進行垃圾回收。新生代又分為 Eden區、ServivorFrom、ServivorTo三個區。
老年代(Old Generation):主要存放應用程序中生命周期長的內存對象。老年代的對象比較穩定,所以MajorGC不會頻繁執行。在進行MajorGC前一般都先進行了一次MinorGC,使得有新生代的對象晉身入老年代,導致空間不夠用時才觸發。當無法找到足夠大的連續空間分配給新創建的較大對象時也會提前觸發一次MajorGC進行垃圾回收騰出空間。MajorGC采用標記清除算法:首先掃描一次所有老年代,標記出存活的對象,然后回收沒有標記的對象。MajorGC的耗時比較長,因為要掃描再回收。MajorGC會產生內存碎片,為了減少內存損耗,我們一般需要進行合并或者標記出來方便下次直接分配。當老年代也滿了裝不下的時候,就會拋出OOM(Out of Memory)異常。
永久代(Permanent Generation):指內存的永久保存區域,主要存放Class和Meta(元數據)的信息,Class在被加載的時候被放入永久區域,它和和存放實例的區域不同,GC不會在主程序運行期對永久區域進行清理。所以這也導致了永久代的區域會隨著加載的Class的增多而脹滿,最終拋出OOM異常。
元數據區(Metaspace): 在Java8中,永久代已經被移除,被一個稱為“元數據區”(元空間)的區域所取代。元空間的本質和永久代類似,元空間與永久代之間最大的區別在于:元空間并不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制。類的元數據放入 native memory, 字符串池和類的靜態變量放入java堆中,這樣可以加載多少類的元數據就不再由MaxPermSize控制, 而由系統的實際可用空間來控制。
更多關于“java培訓”的問題,歡迎咨詢千鋒教育在線名師。千鋒教育多年辦學,課程大綱緊跟企業需求,更科學更嚴謹,每年培養泛IT人才近2萬人。不論你是零基礎還是想提升,都可以找到適合的班型,千鋒教育隨時歡迎你來試聽。