日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Java 面试题(3)—— JVM

發(fā)布時間:2023/12/10 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 面试题(3)—— JVM 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

JVM的內(nèi)存結(jié)構(gòu)。


JVM主要結(jié)構(gòu):堆內(nèi)存、棧、方法區(qū),程序計數(shù)器,永久代)(jdk 8采用元空間替代)。
堆內(nèi)存又分成年輕代和年老代。
年輕代由三部分組成,Eden、From Survivor 和 To Survivor,這三者默認分配的比例是8:1:1。
方法區(qū)主要存儲類信息、常量、靜態(tài)變量等數(shù)據(jù)。
棧又分為java虛擬機棧和本地方法棧,每創(chuàng)建一個線程對應(yīng)一個java棧,
每調(diào)用一個方法就會向棧中創(chuàng)建并壓人一個棧幀,棧幀是用來存儲方法數(shù)據(jù)和部分過程結(jié)果的數(shù)據(jù)結(jié)構(gòu),
每一個方法從調(diào)用到最終返回結(jié)果的過程,就對應(yīng)一個棧幀從入棧到出棧的過程。
本地方法棧主要用于native方法的調(diào)用。
程序計數(shù)器(Program Counter Register)是JVM中一塊較小的內(nèi)存區(qū)域,保存著當前線程執(zhí)行的虛擬機字節(jié)碼指令的內(nèi)存地址。
所有線程共享的內(nèi)存數(shù)據(jù)區(qū):方法區(qū),堆。而虛擬機棧,本地方法棧和程序計數(shù)器都是線程私有的。


JVM方法棧的工作過程,方法棧和本地方法棧有什么區(qū)別。


每個線程擁有自己的棧,棧包含每個方法執(zhí)行的棧幀。
棧是一個后進先出(LIFO)的數(shù)據(jù)結(jié)構(gòu),因此當前執(zhí)行的方法在棧的頂部。
每次方法調(diào)用時,一個新的棧幀創(chuàng)建并壓棧到棧頂。
當方法正常返回或拋出未捕獲的異常時,棧幀就會出棧。
除了棧幀的壓棧和出棧,棧不能被直接操作。
所以可以在堆上分配棧幀,并且不需要連續(xù)內(nèi)存。


JVM的棧中引用如何和堆中的對象產(chǎn)生關(guān)聯(lián)。


在堆中產(chǎn)生了一個數(shù)組或?qū)ο蠛?#xff0c;還可以在棧中定義一個特殊的變量,
讓棧中這個變量的取值等于數(shù)組或?qū)ο笤诙褍?nèi)存中的首地址,棧中的這個變量就成了數(shù)組或?qū)ο蟮囊米兞俊?br /> Java的堆是一個運行時數(shù)據(jù)區(qū),類的(對象從中分配空間。
這些對象通過new、newarray、anewarray和multianewarray等指令建立,它們不需要程序代碼來顯式的釋放。
堆是由垃圾回收來負責的,堆的優(yōu)勢是可以動態(tài)地分配內(nèi)存大小,生存期也不必事先告訴編譯器,
因為它是在運行時動態(tài)分配內(nèi)存的,Java的垃圾收集器會自動收走這些不再使用的數(shù)據(jù)。
但缺點是,由于要在運行時動態(tài)分配內(nèi)存,存取速度較慢。?

棧的優(yōu)勢是,存取速度比堆要快,僅次于寄存器,棧數(shù)據(jù)可以共享。
但缺點是,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性。
棧中主要存放一些基本類型的變量(, int, short, long, byte, float, double, boolean, char)和對象句柄。?

https://www.cnblogs.com/langren1992/p/4738391.html
https://www.jianshu.com/p/ac162726d7de

可以了解一下逃逸分析技術(shù)。


方法逃逸:當一個對象在方法中被定義后,因為可能被外部方法引用,比如作為調(diào)用參數(shù)被傳遞到其他的方法里。
線程逃逸:當一個對象在方法中被定義后,可能被外部線程訪問到,比如給類變量或者在其他線程中訪問的實例變量。
棧上分配:就是把沒發(fā)生逃逸的對象,在棧分配空間。(一般對象分配空間是在堆)
jvm根據(jù)對象是否發(fā)生逃逸,會分配到不同(堆或棧)的存儲空間。如果對象發(fā)生逃逸,那會分配到堆中。

https://blog.csdn.net/qq_32575047/article/details/81214178
https://blog.csdn.net/somnusrush/article/details/76027122
https://www.jianshu.com/p/3ecc626ce304


GC的常見算法,CMS以及G1的垃圾回收過程,CMS的各個階段哪兩個是Stop the world的,CMS會不會產(chǎn)生碎片,G1的優(yōu)勢。
幾種算法的區(qū)別體現(xiàn)在對年老代的回收。

PS
mark-copy(stp)
年輕代的垃圾算法
標記-復(fù)制,將Eden區(qū)的和Survior區(qū)復(fù)制到另一個S區(qū),并且標記存活次數(shù)。

CMS(current mark sweep)
年老代的垃圾算法
initial mark(stp): 第一次標記,從root節(jié)點出發(fā),尋找第一個節(jié)點停止
concurrent mark(不stp): 并發(fā)標記,從initial mark的節(jié)點開始標記非空節(jié)點
concurrent-preclean(不stp):預(yù)清理過程。
remark(stp):由于concurrent mark 不stp,沒有完全標記所有的可達對象,需要重新標記一次
concurrent sweep(不stp):將所有未標記的對象清理
concurrent-reset(不stp): 重置內(nèi)部數(shù)據(jù)結(jié)構(gòu)
cms是會產(chǎn)生內(nèi)存碎片的。
cms是基于“標記-清除”算法的收集器,這意味著垃圾回收完成后會產(chǎn)生大量的內(nèi)存碎片,
當大對象沒有足夠的連續(xù)空間來分配時,不得不提前觸發(fā)一次Full GC,增加stp的時間。

G1
garbage first的算法:
initial mark(stp): 和cms是一樣的操作,同時發(fā)生是minor gc。
root region scanning(stp):在初始標記的存活區(qū)掃描對老年代的引用,并標記被引用的對象。(共用Minor gc的操作)
concurrent mark(不stp): 并發(fā)標記,從initial mark的節(jié)點開始標記非空節(jié)點,
計算每個 region的對象存活率,方便后面的clean up階段使用
remark(stp):由于concurrent mark 不stp,沒有完全標記所有的可達對象,需要重新標記一次
cleanup(不stp): 清除空Region(沒有存活對象的),加入到free list。只是回收沒有對象的region,不需要stp。
g1和cms的區(qū)別:
g1的內(nèi)存組織方式變了,不是連續(xù)的內(nèi)存空間,
而是一個個region(分為E(Eden)、S(Survivor)、O(Old)、H(Humongous))。
remark階段再標記的算法變了,g1的算法是SATB(snapshot-at-the-begining)。
jdk 11 g1作為默認的垃圾回收器。
優(yōu)勢:
G1是一個有整理內(nèi)存過程的垃圾收集器,不會產(chǎn)生很多內(nèi)存碎片。
G1的Stop The World(STW)更可控,G1在停頓時間上添加了預(yù)測機制,用戶可以指定期望停頓時間。

https://tech.meituan.com/g1.html


標記清除和標記整理算法的理解以及優(yōu)缺點。

標記 -清除算法(Mark-Sweep)
? “標記-清除”算法,如它的名字一樣,算法分為“標記”和“清除”兩個階段:
首先標記出所有需要回收的對象,在標記完成后統(tǒng)一回收掉所有被標記的對象。
之所以說它是最基礎(chǔ)的收集算法,是因為后續(xù)的收集算法都是基于這種思路并對其缺點進行改進而得到的。
它的主要缺點有兩個:
(1)效率問題:標記和清除過程的效率都不高;
(2)空間問題:標記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導(dǎo)致,
碎片過多會導(dǎo)致大對象無法分配到足夠的連續(xù)內(nèi)存,從而不得不提前觸發(fā)GC,甚至Stop The World。
標記-整理(Mark-Compact)
???復(fù)制收集算法在對象存活率較高時就要執(zhí)行較多的復(fù)制操作,效率將會變低。
更關(guān)鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,
以應(yīng)對被使用的內(nèi)存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。????
根據(jù)老年代的特點,有人提出了另外一種“標記-整理”(Mark-Compact)算法,
標記過程仍然與“標記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象進行清理,
而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。

https://blog.csdn.net/wuzhiwei549/article/details/80563134

eden survivor區(qū)的比例,為什么是這個比例,eden survivor的工作過程。

默認比例是8:1:1。
因為年輕代中的對象基本都是朝生夕死的(80%以上),所以在年輕代的垃圾回收算法使用的是復(fù)制算法。
年輕代的出生在eden區(qū),隨著一次gc,由Eden區(qū)copy到From Survivor區(qū),年齡+1。
再次回收,由From Survivor和Eden區(qū)copy到To Survivor區(qū)。年齡超過8歲,進入年老代。

https://www.jianshu.com/p/534ab3c8335f
?

JVM如何判斷一個對象是否該被GC,可以視為root的都有哪幾種類型。

GC roots。如果從一個對象沒有到達根對象的路徑,或者說從根對象開始無法引用到該對象,該對象就是不可達的。
gc roots:
?? ?1. 虛擬機(JVM)棧中引用對象?
?? ??? ?每個方法執(zhí)行的時候,jvm都會創(chuàng)建一個相應(yīng)的棧幀(棧幀中包括操作數(shù)棧、局部變量表、運行時常量池的引用),
?? ??? ?棧幀中包含這在方法內(nèi)部使用的所有對象的引用(當然還有其他的基本類型數(shù)據(jù)),當方法執(zhí)行完后,該棧幀會從虛擬機棧中彈出
?? ?2.方法區(qū)中的類靜態(tài)屬性引用對象
?? ?3.方法區(qū)中常量引用的對象(final 的常量值)
?? ?4.本地方法棧JNI的引用對象

https://blog.csdn.net/u012941811/article/details/52427372

強軟弱虛引用的區(qū)別以及GC對他們執(zhí)行怎樣的操作。

強引用:
垃圾回收器絕不會回收它
當內(nèi)存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內(nèi)存不足問題。
軟引用:
如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存
可用來實現(xiàn)內(nèi)存敏感的高速緩存。如果軟引用所引用的對象被垃圾回收,Java虛擬機就會把這個軟引用加入到與之關(guān)聯(lián)的引用隊列中。
弱引用:
更短暫的生命周期,不管當前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。
如果軟引用所引用的對象被垃圾回收,Java虛擬機就會把這個軟引用加入到與之關(guān)聯(lián)的引用隊列中。
虛引用:
虛引用不會決定對象的生命周期,在任何時候都可能被垃圾回收器回收。
虛引用必須和引用隊列(ReferenceQueue)聯(lián)合使用。

https://blog.csdn.net/panyongcsd/article/details/46605613

Java是否可以GC直接內(nèi)存。

NIO的Buffer提供了一個可以不經(jīng)過JVM內(nèi)存直接訪問系統(tǒng)物理內(nèi)存的類——DirectBuffer。
?DirectBuffer類繼承自ByteBuffer,但和普通的ByteBuffer不同,普通的ByteBuffer仍在JVM堆上分配內(nèi)存,
?其最大內(nèi)存受到最大堆內(nèi)存的限制;而DirectBuffer直接分配在物理內(nèi)存中,并不占用堆空間,其可申請的最大內(nèi)存受操作系統(tǒng)限制。
(Note:DirectBuffer并沒有真正向OS申請分配內(nèi)存,其最終還是通過調(diào)用Unsafe的allocateMemory()來進行內(nèi)存分配。不過JVM對Direct Memory可申請的大小也有限制,可用-XX:MaxDirectMemorySize=1M設(shè)置,這部分內(nèi)存不受JVM垃圾回收管理。)
在JVM堆分配內(nèi)存(allocate)相比,直接內(nèi)存分配(allocateDirect)的訪問性能更好,但分配較慢。

https://www.cnblogs.com/z-sm/p/6235157.html?utm_source=itdadao&utm_medium=referral
http://www.importnew.com/21998.html

Java類加載的過程。

加載、鏈接(驗證、準備、解析)、初始化。
加載:把class字節(jié)碼文件從各個來源通過類加載器裝載入內(nèi)存中。
?? ?字節(jié)碼來源。一般的加載來源包括從本地路徑下編譯生成的.class文件,從jar包中的.class文件,從遠程網(wǎng)絡(luò),以及動態(tài)代理實時編譯
?? ?類加載器。一般包括啟動類加載器,擴展類加載器,應(yīng)用類加載器,以及用戶的自定義類加載器。
驗證:主要是為了保證加載進來的字節(jié)流符合虛擬機規(guī)范,不會造成安全錯誤。?? ?
準備:主要是為類變量(注意,不是實例變量)分配內(nèi)存,并且賦予初值。是Java虛擬機根據(jù)不同變量類型的默認初始值。
解析:將常量池內(nèi)的符號引用替換為直接引用的過程。
?? ?符號引用。即一個字符串,但是這個字符串給出了一些能夠唯一性識別一個方法,一個變量,一個類的相關(guān)信息。
?? ?直接引用。可以理解為一個內(nèi)存地址,或者一個偏移量。比如類方法,類變量的直接引用是指向方法區(qū)的指針;
?? ?而實例方法,實例變量的直接引用則是從實例的頭指針開始算起到這個實例變量位置的偏移量
初始化:主要是對類變量初始化,是執(zhí)行類構(gòu)造器的過程。對static修飾的變量或語句進行初始化。

https://blog.csdn.net/ln152315/article/details/79223441
https://www.cnblogs.com/xiaoxian1369/p/5498817.html

?

雙親委派模型的過程以及優(yōu)勢。

實現(xiàn)雙親委派模型的代碼都集中在java.lang.ClassLoader的loadClass()方法中: ?
首先會檢查請求加載的類是否已經(jīng)被加載過; ?
若沒有被加載過: ?
遞歸調(diào)用父類加載器的loadClass(); ?
父類加載器為空后就使用啟動類加載器加載; ?
如果父類加載器和啟動類加載器均無法加載請求,則調(diào)用自身的加載功能。
優(yōu)點;
Java類伴隨其類加載器具備了帶有優(yōu)先級的層次關(guān)系,確保了在各種加載環(huán)境的加載順序。 ?
保證了運行的安全性,防止不可信類扮演可信任的類。

https://blog.csdn.net/inspiredbh/article/details/74889654
https://blog.csdn.net/qq_38182963/article/details/78660779

?

常用的JVM調(diào)優(yōu)參數(shù)。


trace 跟蹤:
1. 打印GC的簡要信息:
-verbose:gc
-XX:+PrintGC
2. 打印GC詳細信息:(生產(chǎn)不要用)
-XX:+PrintGCDetails
3. 指定GC log的位置:
-Xloggc:log/gc.log
4. 類加載信息:
-XX:+TraceClassLoading
5. 堆內(nèi)存設(shè)置:
-Xms1g ? -Xmx1g
6. 導(dǎo)出OOM的路徑:
-XX:HeapDumpPath=d:/a.dump
7. 初始堆大小
-Xms
8. 最大堆大小
-Xmx


dump文件的分析。


jmap -dump:file=文件名.dump [pid]
jdk自帶的visualvm


Java有沒有主動觸發(fā)GC的方式(沒有)。


System.gc();
// 或者下面,兩者等價
Runtime.getRuntime().gc();
只是通知gc,并沒有執(zhí)行。


內(nèi)存泄漏和內(nèi)存溢出的區(qū)別和聯(lián)系

常發(fā)性內(nèi)存泄漏:
發(fā)生內(nèi)存泄漏的代碼會被多次執(zhí)行到,每次被執(zhí)行的時候都會導(dǎo)致一塊內(nèi)存泄漏。
偶發(fā)性內(nèi)存泄漏:
發(fā)生內(nèi)存泄漏的代碼只有在某些特定環(huán)境或操作過程下才會發(fā)生。常發(fā)性和偶發(fā)性是相對的。
對于特定的環(huán)境,偶發(fā)性的也許就變成了常發(fā)性的。所以測試環(huán)境和測試方法對檢測內(nèi)存泄漏至關(guān)重要。
一次性內(nèi)存泄漏:發(fā)生內(nèi)存泄漏的代碼只會被執(zhí)行一次,或者由于算法上的缺陷,導(dǎo)致總會有一塊僅且一塊內(nèi)存發(fā)生泄漏。
比如,在類的構(gòu)造函數(shù)中分配內(nèi)存,在析構(gòu)函數(shù)中卻沒有釋放該內(nèi)存,所以內(nèi)存泄漏只會發(fā)生一次。
隱式內(nèi)存泄漏:程序在運行過程中不停的分配內(nèi)存,但是直到結(jié)束的時候才釋放內(nèi)存。
嚴格的說這里并沒有發(fā)生內(nèi)存泄漏,因為最終程序釋放了所有申請的內(nèi)存。
不及時釋放內(nèi)存也可能導(dǎo)致最終耗盡系統(tǒng)的所有內(nèi)存。所以,我們稱這類內(nèi)存泄漏為隱式內(nèi)存泄漏。
內(nèi)存溢出的原因及解決方法:

內(nèi)存溢出原因:?
內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù);?
集合類中有對對象的引用,使用完后未清空,使得JVM不能回收;?
代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復(fù)的對象實體;?
使用的第三方軟件中的BUG;?
啟動參數(shù)內(nèi)存值設(shè)定的過小
內(nèi)存溢出的解決方案:?
修改JVM啟動參數(shù),直接增加內(nèi)存。(-Xms,-Xmx參數(shù)一定不要忘記加。)
檢查錯誤日志,查看“OutOfMemory”錯誤前是否有其 它異常或錯誤。
對代碼進行走查和分析,找出可能發(fā)生內(nèi)存溢出的位置。


https://blog.csdn.net/ruiruihahaha/article/details/70270574

總結(jié)

以上是生活随笔為你收集整理的Java 面试题(3)—— JVM的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。