深入浅出 Java 中 JVM 内存管理
?
Java崗位面試,JVM是對程序員基本功考察,通常會問你對JVM了解嗎???
可以分幾部分回答這個問題,首先JVM內存劃分 | JVM垃圾回收的含義? |? 有哪些GC算法? 以及年輕代和老年代各自特點等等。
?
1) JVM內存劃分:
①? 方法區 (線程共享)? 常量? 靜態變量? JIT(即時編譯器)編譯后代碼也在方法區存放
②? 堆內存?(線程共享) 垃圾回收的主要場地
③? 程序計數器? 當前線程執行的字節碼的位置指示器
④? Java虛擬機棧(棧內存) :保存局部變量,基本數據類型以及堆內存中對象的引用變量
⑤ 本地方法棧? (C棧):為JVM提供使用native方法的服務
通過這幅圖了解一下
JDK 1.8同JDK 1.7 最大的區別是:元數據區取代了永久代.元數據區的本質和永久代類似,都是對JVM規范中的方法區的實現.元數據區和永久代之間的最大區別在于:元數據區不在虛擬機中,而是在本地內存中
?
詳細了解一下各個部分
01)程序計數器(PC寄存器)
程序計數器的定義:?程序計數器是一塊較小的內存空間,是當前線程正在執行的那一條字節碼指令的地址,若當前線程正在執行的是一個本地方法,那么此時程序計數器為Undefined
程序計數器的作用:
-
字節碼解釋器通過改變程序計數器來依次獲取指令,從而實現代碼的流程控制
-
在多線程情況下,程序計數器記錄的是當前線程執行的位置,從而當線程切換回來時,就知道上次線程執行到哪了
程序計數器的特點
-
是一塊較小的內存空間
-
線程私有,每個線程都有自己的程序計數器
-
生命周期:隨著線程的創建而創建,隨著線程的銷毀而銷毀
-
是一個唯一不會出現OutOfMemoryError的內存區域
?
02)Java虛擬機棧
定義:?描述Java方法運行過程的內存模型
Java虛擬機棧會為每一個即將運行的Java方法創建一塊叫做"棧幀"的區域,用于存放該方法運行過程中的一些信息,如 : 局部變量表? /操作數棧?/動態鏈接 /方法出口信息? .............
?
壓棧出棧過程:
當方法運行過程中需要創建局部變量時,就將局部變量的值存入棧幀的局部變量表中
Java虛擬機棧的棧頂是當前正在執行的活動棧,也就是當前正在執行的方法,PC寄存器也會指向這個地址,只有這個活動的棧幀的本地變量可以被操作數棧操作,當前這個棧幀中調用另一個方法,與之對應的棧幀又會被創建,新創建的棧幀壓入棧頂,變成當前的活動棧幀,方法結束后,當前棧幀的返回值變成新的活動棧幀中的操作數棧的一個操作數,如果沒有返回值,那么新的活動棧幀中操作數棧的操作數沒有變化
由于Java虛擬機棧是線程對應的,數據不是共享的,因此不用關心數據一致性問題,也不會存在同步鎖的問題
特點
-
局部變量表隨著棧幀的創建而創建,它的大小在編譯時確定,創建時只需分配事先規定的大小即可,在方法運行的過程中,局部變量表的大小不會發生變化
-
Java虛擬機棧會出現兩種異常:StackOverFlowError和OutOfMemoryError
-
StackOverFlowError:若Java虛擬機棧的大小不允許動態擴展,那么當前線程請求的棧的深度超過當前的Java虛擬機棧的最大深度時,就會拋出此異常
-
OutOfMemoryError:若允許動態擴展,那么當前線程的請求的棧內存用完了,無法再動態擴展時,拋出此異常
-
Java虛擬機棧也是線程私有,隨著線程創建而創建,隨著線程的結束而銷毀
?
03)本地方法棧(C棧)
定義:?是為了JVM運行native方法準備的空間,由于很多native方法都是用C語言實現的,所以通常又叫C棧,它與Java虛擬機棧實現的功能類似,只不過本地方法棧是描述本地方法運行過程的內存模型
棧幀變化過程:
本地方法被執行時,在本地方法棧也會創建一塊棧幀,用于存放該方法的局部變量表 /操作數棧 /動態鏈接 /方法出口 等信息; 方法結束后,相應的棧幀也會出棧,并釋放內存空間.也會拋出StackOverFlowError和OutOfMemoryError異常
?
04) 堆
定義:?堆是用來存放對象的內存空間,幾乎所有的對象都存儲在堆中
特點:
-
線程共享,整個Java虛擬機只有一個堆,所有線程都訪問同一個堆.
-
在虛擬機啟動時創建
-
是垃圾回收的主要場地
-
進一步可分為:新生代(Eden區 From Survior To Surviror)? 老年代
-
不同的區域存放了不同生命周期的對象,這樣可以根據不同區域使用不同的垃圾回收算法,更具有針對性. 堆的大小也可以固定也可以擴展,對于主流的虛擬機,堆大小是可擴展的,因此當線程請求分配的內存,但堆已滿,且內存已無法再擴展時,就拋出OutOfMemoryError異常
?
05)方法區
定義:?Java虛擬機規范中定義方法區是堆的一個邏輯部分,方法區存放以下信息:?已被虛擬機加載的類信息?/常量?/靜態變量 /即時編譯后代碼
特點:
-
線程共享.方法區是堆的一個邏輯部分,因此和堆一樣,都是線程共享,整個虛擬機中只有一個方法區
-
永久代 方法區中的信息一般需要長期存在,而且它又是堆的邏輯分區,因此用堆的劃分方法,把方法區稱為"永久代"
-
內存回收的效率低.方法區中的信息一般需要長期存在,回收一遍只有少量信息無效.主要回收的目標是: 對常量池的回收;對類型的卸載
-
Java虛擬機規范l對方法區的要求比較寬松,和堆一樣,允許固定大小.也允許動態擴展,還允許不實現垃圾回收
運行時常量池:
方法區中存放:類信息? 常量? 靜態變量? 即時編譯器編譯后的代碼.常量就存放在運行時常量池中.當類被Java虛擬機加載后,.class文件中的常量就存在方法區的運行時常量池,而且在運行期間,可以向常量池中添加新的常量,如String類的intern()方法就能在運行期間向常量池中添加字符串常量
?
06) 直接內存(堆外內存)
直接內存是除Java虛擬機之外的內存,但有可能被Java使用
操作直接內存:
在NIO中引入了一種基于通道和緩存的IO方式,他可以調用本地方法直接分配Java虛擬機之外的內存,然后通過一個存儲在堆中的DirectByteBuffer對象直接操作該內存,而無需將外部內存中數據復制到堆中再進行操作,從而提高數據操作的效率,直接內存的大小不受Java虛擬機限制,也會拋出OutOfMemoryError異常
直接內存和堆內存比較:
-
直接內存申請空間耗費更高的性能
-
直接內存讀取IO的性能優于普通的堆內存
-
直接內存的作用鏈:本地IO-->直接內存-->本地IO
-
堆內存的作用鏈:本地IO-->直接內存-->非直接內存-->直接內存--->本地IO
-
服務器管理員在配置虛擬機參數時,會根據實際內存設置 -Xmx等參數信息,但經常忽略直接內存,使得各個內存區域總和大于物理內存,從而導致動態擴展時出現OutOfMemoryError
?
2)類似 -Xms? -Xmn這些參數的含義
堆內存分配
① : JVM初始分配的內存由-Xms指定,默認是物理內存的1/64
②:? JVM最大分配的內存由-Xmx指定,默認是物理內存的1/4
③: 默認空余堆內存小于40%時,JVM就會增加堆直到-Xmx的最大限制;空余堆內存大于70%時,JVM會減少堆直到-Xms的最小限制
④: 因此服務器一般設置-Xms? -Xmx相等以避免在每次GC后調整堆大小. 對象的堆內存由垃圾回收器的自動內存管理系統回收
非堆內存分配:
①:JVM使用-XX:PermSize 設置非堆內存的初始值,默認是物理內存的1/64;
② :由XX:MaxPermSize設置最大非堆內存的大小
③: -Xmn2G :設置年輕代的大小為2G
④ :-XX:SurvivorRatio ,設置年輕代中Eden區與Survivor區的比值
?
3)垃圾回收的算法有哪些?
① 引用計數法:原理是在此對象有個引用,則增加一個計數,刪除一個引用則減少一個計數.垃圾回收時,只收集計數為0的對象.此算法最致命的是無法處理循環引用的問題
②: 標記-清除 :此算法分兩個階段,第一階段從引用的根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除,此算法需要暫停應用,同時產生內存碎片
③: 復制算法 此算法把內存劃分為兩個相等的區域,每次只使用一個區域,垃圾回收時,遍歷當前使用的區域,把正在使用的對象復制到另一個區域中,算法每次只處理正在使用的對象,因此復制的成本比較小,同時復制過去以后還能進行相應的內存整理,不會出現"碎片問題",此算法的缺點也很明顯,需要兩倍的內存空間
④: 標記-整理:此算法結合了"標記-清除"和"復制算法"的兩個的優點,也是分兩個階段,第一個階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,清除未標記的對象并且把存活的對象"壓縮"到堆的其中一塊,按順序排放,,此算法避免"標記-清除"的碎片問題,同時也避免"復制"的空間問題
?
4)root搜索算法中,哪些可以作為root?
-
被啟動類(bootstrap加載器)加載的類和創建的對象
-
JavaStack中引用的對象(棧內存中引用的對象)
-
方法區中靜態引用
總結
以上是生活随笔為你收集整理的深入浅出 Java 中 JVM 内存管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入理解Golang包导入
- 下一篇: 设计模式之单例模式实践