日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java 面试题(3)—— JVM

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

JVM的內存結構。


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


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


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


JVM的棧中引用如何和堆中的對象產生關聯。


在堆中產生了一個數組或對象后,還可以在棧中定義一個特殊的變量,
讓棧中這個變量的取值等于數組或對象在堆內存中的首地址,棧中的這個變量就成了數組或對象的引用變量。
Java的堆是一個運行時數據區,類的(對象從中分配空間。
這些對象通過new、newarray、anewarray和multianewarray等指令建立,它們不需要程序代碼來顯式的釋放。
堆是由垃圾回收來負責的,堆的優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,
因為它是在運行時動態分配內存的,Java的垃圾收集器會自動收走這些不再使用的數據。
但缺點是,由于要在運行時動態分配內存,存取速度較慢。?

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

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

可以了解一下逃逸分析技術。


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

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會不會產生碎片,G1的優勢。
幾種算法的區別體現在對年老代的回收。

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

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

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

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


標記清除和標記整理算法的理解以及優缺點。

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

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

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

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

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

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

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

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

強軟弱虛引用的區別以及GC對他們執行怎樣的操作。

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

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

Java是否可以GC直接內存。

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

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

Java類加載的過程。

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

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

?

雙親委派模型的過程以及優勢。

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

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

?

常用的JVM調優參數。


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


dump文件的分析。


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


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


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


內存泄漏和內存溢出的區別和聯系

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

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


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

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。