1、jvm記憶體結構
靜态編譯:把java源檔案編譯成位元組碼檔案class,這個時候class檔案以靜态方式存在。
類加載器:把java位元組碼檔案加載到記憶體中
方法區:将位元組碼放到方法區作為中繼資料(簡單名字+描述符)。
堆:對象(類的執行個體)
方法區和堆:運作時資料區在所有線程間共享
虛拟機棧、本地方法棧、程式計數器:運作時資料區線程私有
2、堆
(1)對于大多數應用來說,java堆是java虛拟機所管理的記憶體中的最大的一塊
(2)java堆是被所有線程共享的一塊記憶體區域,在虛拟機啟動時建立,需要考慮線程安全的問題
(3)此記憶體區域的唯一目的就是存放對象執行個體,幾乎所有的對象執行個體對在這裡配置設定記憶體
(4)如果在堆中沒有記憶體完成執行個體配置設定,并且堆也無法再擴充時,OutOfMemoryError異常
如果再配置設定數組的記憶體之前将jvm的數值調小後便會發生此異常。
(5)是垃圾收集器管理的主要區域
堆記憶體溢出(OutOfMemoryError)問題:
堆既然有垃圾回收機制,為什麼還會有記憶體溢出的問題呢?
因為垃圾回收器回收的是不被使用的對象,如果不停的産生對象而對象又都在被使用就會出現記憶體溢出
(6)JDK1.7開始,将StringTable從常量池移動到了堆中,String table又稱為StringPool(字元串常量池)
永久帶的記憶體回收效率較低,full GC才會觸發,也就是老年代的空間不足才會觸發。但是移動到堆中之後,隻需要Minor GC即可觸發垃圾回收,大大減輕了字元串對記憶體的占用
JVM中的堆一般分為三大部分(jdk1.7):新生代、老年代、永久代(java8以後永久代被元空間代替,元空間使用本地記憶體,是不與堆記憶體相連的。是以,預設情況下,元空間的大小僅受本地記憶體的限制)
(7)引起堆記憶體溢出的情況
死循環或不停地重複建立大量對象
3、方法區
是各個線程共享的記憶體區域,虛拟機啟動的時候建立,jdk1.8之前屬于堆的永久區的一部分,會被垃圾回收機制所回收隻不過回收的條件較為苛刻,也就造成了永久區較難被回收。1.8開始,将方法區從永久區剝離了出來,取而代之的是元空間,相較于永久區它使用的是實體記憶體,預設大小是實體記憶體的大小,但是也可以進行配置
(1)存放的資訊
已經被jvm加載的類的資訊
常量
靜态變量
及時編譯器編譯後的代碼(JIT)
(2)JIT:熱點代碼編譯後存儲到方法區
上面的代碼編譯後存放起來,避免反複編譯
(3)編譯的過程

(4)運作時常量池
常量池:是一個常量表
運作時常量池:
常量池是*.class檔案中的,當該類被加載,它的常量池資訊就會放入到運作時常量池,并将裡面的符号位址變為真實位址
4、程式計數器(PC Register)
(1)一塊較小的記憶體空間,它的作用是目前線程所執行的位元組碼行号訓示器(記錄下一條jvm指令的執行位址)
(2)一個處理器隻會執行一條線程中的指令。是以,為了線程切換後能恢複到正确的執行位置,每條線程都需要有一個獨立的程式計數器(線程私有)
(3)唯一一個在jvm中沒有規定任何OutOfMemoryError的區域(java的規範所規定)
5、本地方法棧
為本地方法(不是由java代碼編寫的方法)的運作提供的記憶體空間
該方法沒有方法實作,底層是c或c++實作的,是C或c++程式提供給java程式的接口,也存在兩種異常。
虛拟機規範中對本地方法棧中的方法使用的語言、使用方式與資料結構并沒有強制規定,是以具體的虛拟機可以自由實作它。
(1)棧是有深度的:
每調用一次占用一個棧幀:
棧的預設大小為5248,預設1M。
函數的調用過程:
6、虛拟機棧(每一個線程運作的時候所需要的記憶體)
每一個方法在執行的時候都會建立一個棧幀(一個棧幀對應一個方法的調用,棧幀即每一個方法需要的記憶體)用于存儲配置設定基本類型和自定義對象的引用,用于存放,局部變量表、操作數棧、動态連結\方法的傳回位址
每一個線程隻有一個活動棧幀,對應着目前線程正在執行的那個方法
垃圾回收不涉及棧記憶體
可以通過指令來指定棧的大小,但是并不是棧的記憶體越大越好,棧的記憶體變大可能引起線程數量的減少程式反而會變慢
局部變量沒有逃離方法的作用域是線程安全的(線程私有),當作為參數傳遞的變量或是局部變量作為傳回值都不是線程安全的,因為可能被别的線程通路到
(1)一個棧中的多個棧幀
棧幀1先入棧,然後是棧幀2、棧幀3,相當于方法1調用方法2,方法2又調用了方法3,出棧的時候棧幀3先出棧,然後是棧幀2和棧幀3
(2)異常
StackOverflowError:
線程請求的深度大于虛拟機棧的深度(棧記憶體溢出),無限制的方法的遞歸調用容易出現
棧幀過大,直接将棧記憶體存滿,發生的情況極其少
例如:定義一個學生類和一個班級類,一個學生隻屬于一個班級,在學生類裡面有一個班級編号屬性,一個班級有多個學生,班級類裡面可以定義一個集合代表多個學生,在進行JSON轉換的時候就會出現循環引用的現象,即一個學生對應一個班級,一個班級又有多個學生......,要把學生和班級的引用改為單向的,就不會出現循環引用的現象了。
OutOfMemoryError:擴充時無法申請到足夠的記憶體
7、堆、棧、方法區
(1)字元串相關:
JDK1.7開始,将StringTable從常量池移動到了堆中
後面的三個字元隻建立了一個對象,因為存在字元串的折疊
當常量池中已經有了“hello”字元串後在常量池中就不必再建立了,隻需在棧記憶體中建立一個對象即可;但是,如果在常量池中沒有“hello”字元串的話,就需要建立兩個字元串對象了。
(2)JVM執行流程
JVM去方法區尋找Person類資訊如果我不到,Classloader加載Person類資訊進入記憶體方法區
在堆記憶體中建立Person對象,并持有方法區中Person類的類型資訊的引用
把person添加到執行main0方法的主線程java調用棧中,指向堆空間中的記憶體對象
執行person.sayHello0時,JVM根據person定位到堆空間的Person執行個體
根據Person執行個體在方法區持有的引用,定位到方法區Person類型資訊,獲得sayHello0位元組碼,執行此方法執行,列印出結果。
8、局部變量表
(1)存放了各種基本資料類型、對象引用和returnAddress類型(指向了一條位元組碼指令的位址)
(2)long和double類型的資料會占用2個局部變量空間(Slot),其餘的資料類型隻占用1個
9、常量池與運作時常量池、字元串常量池
(1)常量池存儲字面量和符号引用(是class檔案中的常量池,編譯的時候産生)
字面量:字元串、被聲明為final的常量值、基本資料類型等
符号引用:類的完全限定名、字段名稱和描述符、方法名稱和描述符
程式中的數字并未放入到常量池中,這是因為隻有數字超過一定的值以後才會放入到常量值中。常量池在堆中
(2)運作時常量池(類加載到記憶體中後産生)
将符号引用轉換為實際的位址
将class加載到記憶體之後經過驗證、連結等之後,将符号替換為真正的位址。jdk1.8放在元空間裡面,和堆相獨立
(3)字元串常量池(編譯時)
存儲字元串,在堆中
常量池是為了避免頻繁的建立和銷毀對象而影響系統性能,其實作了對象的共享。常量池中所有相同的字元串常量被合并,隻占用一個空間,節省了空間。
每個人都會有一段異常艱難的時光 。 生活的壓力 , 工作的失意 , 學業的壓力。 愛的惶惶不可終日。 挺過來的 ,人生就會豁然開朗。 挺不過來的 ,時間也會教你 ,怎麼與它們握手言和 ,是以不必害怕的。 ——楊绛