1.JVM組成
JVM由那些部分組成,運行流程是什么?
JVM是Java程序的運行環境
組成部分:
類加載器:加載字節碼文件到內存
運行時數據區:包括方法區,堆,棧,程序計數器,本地方法棧
執行引擎:執行字節碼,優化代碼
垃圾回收器:管理堆內存
運行流程:
加載字節碼,準備運行環境,執行字節碼,垃圾回收,程序結束
什么是程序計數器
用于記錄每個線程正在執行的字節碼指令的地址,用于保存字節碼行號。當一個線程執行一段字節碼到某個位置時,CUP使用權被另一個線程奪走,當前執行的地址會記錄下來,當執行權回到當前線程時,會接著上次記錄的位置繼續執行
介紹Java堆
堆是一個線程共享的區域,主要用于保存對象實例,數組等,堆中內存不夠會拋出OOM異常
堆區分為年輕代和老年代,年輕代被化為三部分,一個Eden區和兩個S區,一個對象創建后會先到Eden區,如果對象被垃圾回收后還能存活就移動到S0或S1,再經過幾次垃圾回收后還能存活則移動到老年代區中,老年代中存的是生命周期比較長的對象,
JDK1.7和1.8堆的區別
1.7中有一個永久代,存類信息,靜態變量,常量,編譯后代碼,1.8移除了永久代,將數據存到本地內存中的元空間區,防止內存溢出
什么是虛擬機棧
每個線程運行時所需要的內存稱為虛擬機棧,是一個先進后出結構,每個棧由多個棧幀組成,每個對應著每次方法調用時所占用的內存和數據,每個線程只能有一個活動棧幀,對應了當前正在執行的方法
垃圾回收是否設計棧內存
垃圾回收主要指堆內存,當棧幀彈棧以后,內存就會釋放
棧內存分配越大越好嗎
未必,默認的棧內存通常為1024k,棧幀過大會導致線程數減少
方法內的局部變量是否線程安全
如果方法內局部變量沒有逃離方法的作用范圍,那就是線程安全的,變量的創建和銷毀都是在當前線程的虛擬機棧中完成的
如果局部變量引用了其他對象并逃離的方法的作用范圍,那就要考慮線程安全
棧內存溢出情況
棧幀過多導致內存溢出,如遞歸調用
棧幀過大導致內存溢出
堆和棧的區別
棧內存一般用來存儲局部變量和方法調用,堆用來存儲Java對象和數組,堆會用垃圾回收,棧不會
棧內存時線程私有的,堆內存是線程共享的
異常錯誤不同,內存不足時,棧報StackOverFlow,堆報OutOfMemory
解釋方法區
方法區主要存類的信息,運行時常量池,是各個線程共享的內存區域,在虛擬機啟動時創建,虛擬機關閉時銷毀
方法區在JDK 1.7時在堆區的永久代中,JDK1.8后取消了永久代,單獨存在元空間中,避免了在堆區的OOM
解釋一下運行時常量池
常量池可以看作一張表,虛擬機指令可以根據這張表找到要執行的類名,方法名,參數類型等信息
當類被加載時,常量池的信息就會放入運行時常量池,并把符號地址轉為真實地址
直接內存
不屬于JVM的內存結構,是虛擬機的系統內存,常見于NIO操作,用于數據緩沖區,直接內存相當于一塊操作系統和Java代碼都可以訪問到的共享區域,比如我們在文件IO操作中,使用傳統的BIO,需要調用操作系統的文件API,涉及到CPU用戶態和內核態的切換,資源開銷很大,引入直接內存之后,可以通過直接內存建立起系統內存和Java內存的交互傳輸
?
2.類加載器
什么是類加載器
類加載器的作用是將字節碼文件加載到JVM中,從而使Java程序能夠運行起來
類加載器有哪些:
啟動類加載器,擴展類加載器,應用類加載器(用戶自己編寫的Java類),自定義類加載器
?
什么是雙親委派機制
加載一個類,先委托上一級的加載器進行加載,如果上級加載器也有上級,則繼續向上委托,如果委托上級都沒有加載,則子加載器嘗試加載該類
JVM為什么采用雙親委派機制
可以避免一個類被重復加載,當父類加載后則無需重復加載,保證唯一性
為了安全,保證類庫API不會被修改
類裝載的執行過程
加載:根據類的全名獲取類的字節碼文件,將其轉換為方法區內的運行時數據結構
驗證:對字節碼進行校驗,確保符合JVM規范
準備:為類的靜態變量分配內存,設置默認初始值
靜態變量是final修飾的基本類型或字符串常量,賦值在準備階段完成
靜態變量是final修飾的引用類型,復制也初始化階段完成
解析:將符號引用轉為直接引用,即將類,方法,字段等解析為內存地址
初始化:執行類的初始化代碼,包括靜態變量賦值和靜態代碼塊的執行
使用:JVM開始從入口方法執行用戶的程序代碼,如調用靜態類的成員信息,使用new關鍵字創建對象實例
卸載:當用戶程序代碼執行完畢后,JVM開始銷毀創建的Class對象
3.垃圾回收
簡述垃圾回收機制
在Java語言中,有自動的垃圾回收機制,開發者只需要關注內存的申請,內存的釋放由系統自動識別完成,不同的對象引用會有不同的回收機制
?
對象什么時候可以被垃圾回收器回收
如果一個對象或多個對象沒有任何引用指向它了,那么這個對象現在就是垃圾,就有可能被GC回收
垃圾定位方法
引用計數法:一個對象被引用一次,則在當前對象頭上遞增一次引用次數,如果引用次數為0,則代表該對象可回收,如果出現循環引用的畫計數法就會失效
可達性分析算法:會存在一個根節點,其引用指向下一個節點,依次向下類推,直到所有節點遍歷完畢
判斷如果某對象和根對象無直接或間接引用則可以被垃圾回收
?
JVM垃圾回收算法
標記清除算法:分標記和清除兩個階段,根據可達性算法通過GCRoot得出垃圾并進行標記,對這些標記為可回收的內容進行垃圾回收,效率高,有磁盤碎片,內存不連續
標記整理算法:標記清除的過程一樣,但是會將清理后存活的對象都向內存的一端移動,然后清理邊界之外的垃圾,無內存碎片,對象需要移動,效率低
復制算法:將原有的空間一分為二,每次只用其中一塊,垃圾回收時,將正在使用的對象復制到另一塊內存空間中,然后將當前空間清空,交換兩塊內存的角色,完成垃圾回收,無碎片,內存使用率低
?
JVM的分代回收
堆被分成兩份,新生代和老年代(比例為1:2),對于新生代內部又分Eden區,兩個幸存區:From,to
新創建的對象首先被分到Eden區,當Eden區內存不足時,標記Eden區和From區的存活對象并將其復制到to區中,復制完成后,Eden和from中的內存被釋放,經過一段時間Eden區內存又不足,標記Eden和to區存活的對象,將存活的對象復制到from區,當幸存區對象熬過幾次回收就會晉升到老年代
MinorGC、 Mixed GC 、 FullGC的區別是什么
MinorGC發生在新生代的垃圾回收,暫停時間短
MixedGC發生在新生代和部分老年代區域的垃圾回收。G1收集器持有
FullGC發生在新生代和老年代的完整垃圾回收,暫停時間長,應盡量避免
?
JVM有哪些垃圾回收器
串行垃圾回收器:垃圾回收時,只有一個線程在工作,其他線程都要阻塞等待垃圾回收完成。Serial作用于新生代,采用復制算法,Serial作用于老年代,采用標記整理算法
并行垃圾回收器:多個線程完成垃圾回收工作,其他線程同樣阻塞,Parallel New用于新生代,采用復制算法,Parallel Old作用老年代,采用標記整理算法
CMS(并發)垃圾回收器:針對老年代進行垃圾回收,不會造成線程阻塞,會追蹤標記整個GCRoot,將其直接關聯和間接關聯的對象
G1垃圾回收器,作用于新生代和老年代
?
詳細聊一下G1垃圾回收器
用于新生代和老年代的垃圾回收,在JDK9后JVM默認用的G1垃圾回收,采用的回收算法是復制算法,分為三個階段
首先G1將內存劃分為多個區域,每個區域都可以充當Elen區,幸存者區。jumongous等
新生代回收:
初始時,所有區域處于空閑狀態,創建了一些對象,挑出一部分空閑區左Eden區存儲這些對象,當Eden區需要垃圾回收時,挑一個空閑區域做幸存區,用復制算法復制存活對象,需要暫停用戶線程,再往后,Eden區內存又不足了,將Eden區以及幸存區的存活對象復制到新的幸存區,將較老的對象晉升至老年代
并發標記:
當老年代占用內存超過閾值(默認45%)后,觸發并發標記,無需暫停用戶線程,并發標記后會沖標解決漏標問題(此時需要暫停線程),這些都完成后就知道了老年代有哪些存活對象,之后進入混合手機階段,此時不會堆老年代區域進行回收,而是根據暫停時間目標優先回收價值高的區域
混合收集:
復制完成,內存釋放,進入下一輪垃圾回收,如果對象非常大,會開出Jupmgous區存儲巨型對象
?
強引用,弱引用,軟引用,虛引用
強引用:只要所有的GCRoots能找到,就不會被回收
軟引用:需要配合SoftReference使用,當垃圾多次回收,內存依然不夠用時會回收軟引用對象
弱引用:需要配合WeakReference使用,只要進行垃圾回收,就會把弱引用回收
虛引用:必須配合引用隊列使用,被引用對象回收時,會將虛引用入隊,由Reference Handler線程調用虛引用相關方法釋放內存
?
4.JVM實踐
JVM調優參數設置
war包部署在tomcat中設置
jar包部署在啟動參數中設置
設置堆空間:-XMS(初始) -XMX(最大)
最大大小為默認物理內存的四分之一,初始大小時物理內存的六十四分之一
堆太小,會導致頻繁的年輕代和老年代和垃圾回收,產生stw,用戶線程阻塞,太大,可能會導致FullGC,會掃描整個堆空間,暫停用戶線程的時間長
虛擬機棧設置:-XSS 每個線程默認1M
新生代中Eden區和兩個幸存區的大小比例:默認8:1:1 -XXSus**=9 設置比例
年輕代晉升老年代的閾值: 默認為15,范圍0-15
設置垃圾回收期
?
JVM調優工具
命令:
jps:查看進程狀態信息
jstack:查看進程內線程堆棧信息
jmap:查詢堆棧信息
jhatL堆轉存快照工具
jstat:JVM統計監測工具
可視化工具:jconsole:JVM內存,線程,類監控 VisualVM:監控線程,內存情況
?
Java內存泄漏排查思路
使用jmap查詢堆棧信息,生成dump文件
通過VisualVM,加載dump文件分析堆棧信息定位到代碼排查
?
CPU跑到百分之百,解決思路是啥
通過top命令,定位到占用CPU高的線程
ps -T -p 進程ID找到進程中占比較高的線程
通過jstask查詢線程的堆棧信息去定位代碼
?