?本篇文章總結(jié)了目前JVM面試中最常問到的問題伐谈,以下是問題烂完,答案附在后面
Java 內(nèi)存分配
簡述 Java 垃圾回收機制
垃圾回收的優(yōu)點和原理并考慮 2 種回收機制
System.gc() 和 Runtime.gc() 會做什么事情?
Java 堆的結(jié)構(gòu)是什么樣子的诵棵?什么是堆中的永久代(****Perm Gen space****)****?
Java 中會存在內(nèi)存泄漏嗎抠蚣,請簡單描述
Java 中的內(nèi)存泄露的情況
finalize() 方法什么時候被調(diào)用?析構(gòu)函數(shù) (finalization) 的目的是什么履澳?
JVM 的永久代中會發(fā)生垃圾回收么嘶窄?
什么是類加載器怀跛,類加載器有哪些?
Java 類加載過程柄冲?
類加載的主要步驟吻谋?
Java 內(nèi)存分配
? 寄存器:程序計數(shù)器,是線程私有的,就是一個指針现横,指向方法區(qū)中的方法字節(jié)碼漓拾。
? 靜態(tài)域:static 定義的靜態(tài)成員。
? 常量池:編譯時被確定并保存在 .class 文件中的(final)
常量值和一些文本修飾的符號引用(類和接口的全限定名戒祠,字段的名稱和描述符骇两,方法和名稱和描述符)。
? 非 RAM 存儲:硬盤等永久存儲空間得哆。
? 堆內(nèi)存:new 創(chuàng)建的對象和數(shù)組脯颜,由 Java 虛擬機自動垃圾回收器管理,存取速度慢。
? 棧內(nèi)存:基本類型的變量和對象的引用變量(堆內(nèi)存空間的訪問地址)贩据,速度快栋操,可以共享,但是大小與生存期必須確定饱亮,缺乏靈活性矾芙。
串行(serial)收集器和吞吐量(throughput)收集器的區(qū)別是什么?
吞吐量收集器使用并行版本的新生代垃圾收集器近上,它用于中等規(guī)模和大規(guī)模數(shù)據(jù)的應用程序剔宪。
而串行收集器對大多數(shù)的小應用(在現(xiàn)代處理器上需要大概 100M 左右的內(nèi)存)就足夠了。
在 Java 中壹无,對象什么時候可以被垃圾回收葱绒?
當對象對當前使用這個對象的應用程序變得不可觸及的時候,這個對象就可以被回收了斗锭。
GC 是什么? 為什么要有 GC地淀?
GC 是垃圾收集的意思(GabageCollection),內(nèi)存處理是編程人員容易出現(xiàn)問題的地方岖是,忘記或者錯誤的內(nèi)存回收會導致程序或
系統(tǒng)的不穩(wěn)定甚至崩潰帮毁,Java 提供的 GC 功能可以自動監(jiān)測對象是否超過作用域從而達到自動回收內(nèi)存的目的,Java 語言沒有提
供釋放已分配內(nèi)存的顯示操作方法豺撑。
簡述 Java 垃圾回收機制
在 Java 中烈疚,程序員是不需要顯示的去釋放一個對象的內(nèi)存的,而是由虛擬機自行執(zhí)行聪轿。在 JVM 中爷肝,有一個垃圾回收線程,它是低優(yōu)先級的,在正常情況下是不會執(zhí)行的阶剑,只有在虛擬機空閑或者當前堆內(nèi)存不足時跃巡,才會觸發(fā)執(zhí)行,掃面那些沒有被任何引用的對象牧愁,并將它們添加到要回收的集合中素邪,進行回收。
如何判斷一個對象是否存活猪半?(或者 GC 對象的判定方法)
判斷一個對象是否存活有兩種方法:
引用計數(shù)法
所謂引用計數(shù)法就是給每一個對象設置一個引用計數(shù)器兔朦,每當有一個地方引用這個對象時,就將計數(shù)器加一磨确,引用失效時沽甥,計數(shù)器就
減一。當一個對象的引用計數(shù)器為零時乏奥,說明此對象沒有被引用渤刃,也就是“死對象”,將會被垃圾回收.引用計數(shù)法有一個缺陷就是無法解決循環(huán)引用問題贾铝,也就是說當對象 A 引用對象 B禽拔,對象 B 又引用者對象 A胜茧,那么此時 A、B 對象的引用計數(shù)器都不為零骗炉,也就造成無法完成垃圾回收照宝,所以主流的虛擬機都沒有采用這種算法。
可達性算法(引用鏈法)
該算法的思想是:從一個被稱為 GC Roots 的對象開始向下搜索句葵,如果一個對象到 GC Roots 沒有任何引用鏈相連時厕鹃,則說明此對
象不可用。
在 Java 中可以作為 GC Roots 的對象有以下幾種:
? 虛擬機棧中引用的對象
? 方法區(qū)類靜態(tài)屬性引用的對象
? 方法區(qū)常量池引用的對象
? 本地方法棧 JNI 引用的對象
雖然這些算法可以判定一個對象是否能被回收乍丈,但是當滿足上述條件時剂碴,一個對象并不一定會被回收。當一個對象不可達 GC Root
時轻专,這個對象并不會立馬被回收汗茄,而是處于一個死緩的階段,若要被真正的回收需要經(jīng)歷兩次標記.如果對象在可達性分析中沒有與 GC Root 的引用鏈铭若,那么此時就會被第一次標記并且進行一次篩選,篩選的條件是是否有必要執(zhí)行finalize() 方法递览。當對象沒有覆蓋 finalize() 方法或者已被虛擬機調(diào)用過叼屠,那么就認為是沒必要的。如果該對象有必要執(zhí)行finalize() 方法绞铃,那么這個對象將會放在一個稱為 F-Queue 的對
隊列中镜雨,虛擬機會觸發(fā)一個 Finalize() 線程去執(zhí)行,此線程是低優(yōu)先級的儿捧,并且虛擬機不會承諾一直等待它運行完荚坞,這是因為如果finalize() 執(zhí)行緩慢或者發(fā)生了死鎖挑宠,那么就會造成 F-Queue 隊列一直等待,造成了內(nèi)存回收系統(tǒng)的崩潰颓影。GC 對處于 F-Queue 中的對象進行第二次被標記各淀,這時,該對象將被移除” 即將回收”集合诡挂,等待回收碎浇。
垃圾回收的優(yōu)點和原理并考慮 2 種回收機制
Java 語言中一個顯著的特點就是引入了垃圾回收機制,使 C++ 程序員最頭疼的內(nèi)存管理的問題迎刃而解璃俗,它使得 Java 程序員在編寫程序的時候不再需要考慮內(nèi)存管理奴璃。由于有個垃圾回收機制,Java 中的對象不再有“作用域”的概念城豁,只有對象的引用才有"作用域"苟穆。垃圾回收可以有效的防止內(nèi)存泄露,有效的使用可以使用的內(nèi)存唱星。垃圾回收器通常是作為一個單獨的低級別的線程運行雳旅,不可預知的情況下對內(nèi)存堆中已經(jīng)死亡的或者長時間沒有使用的對象進行清楚和回收,程序員不能實時的調(diào)用垃圾回收器對某個對象或所有對象進行垃圾回收魏颓。
回收機制有分代復制垃圾回收和標記垃圾回收岭辣,增量垃圾回收。
垃圾回收器的基本原理是什么甸饱?垃圾回收器可以馬上回收內(nèi)存嗎沦童?有什么辦法主動通知虛擬機進行垃圾回收?
對于 GC 來說叹话,當程序員創(chuàng)建對象時偷遗,GC 就開始監(jiān)控這個對象的地址、大小以及使用情況驼壶。通常氏豌,GC 采用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是”可達的”热凹,哪些對象是”不可達的”泵喘。當 GC 確定一些對象為“不可達”時,GC 就有責任回收這些內(nèi)存空間般妙〖推蹋可以。程序員可以手動執(zhí)行 System.gc()碟渺,通知 GC 運行鲜锚,但是 Java 語言規(guī)范并不保證 GC 一定會執(zhí)行。
System.gc() 和 Runtime.gc() 會做什么事情?
這兩個方法用來提示 JVM 要進行垃圾回收芜繁。但是旺隙,立即開始還是延遲進行垃圾回收是取決于 JVM 的。
Java 堆的結(jié)構(gòu)是什么樣子的骏令?什么是堆中的永久代(****Perm Gen space****)****?
JVM 的堆是運行時數(shù)據(jù)區(qū)蔬捷,所有類的實例和數(shù)組都是在堆上分配內(nèi)存。它在 JVM 啟動的時候被創(chuàng)建伏社。對象所占的堆內(nèi)存是由自動
內(nèi)存管理系統(tǒng)也就是垃圾收集器回收抠刺。堆內(nèi)存是由存活和死亡的對象組成的。存活的對象是應用可以訪問的摘昌,不會被垃圾回收速妖。死亡的對象是應用不可訪問尚且還沒有被垃圾收集器回收掉的對象。一直到垃圾收集器把這些 對象回收掉之前聪黎,他們會一直占據(jù)堆內(nèi)存空間罕容。
Java 中會存在內(nèi)存泄漏嗎,請簡單描述稿饰。
所謂內(nèi)存泄露就是指一個不再被程序使用的對象或變量一直被占據(jù)在內(nèi)存中锦秒。Java 中有垃圾回收機制,它可以保證一對象不再被引用的時候喉镰,即對象變成了孤兒的時候旅择,對象將自動被垃圾回收器從內(nèi)存中清除掉。由于 Java 使用有向圖的方式進行垃圾回收管理侣姆,可以消除引用循環(huán)的問題生真,例如有兩個對象,相互引用捺宗,只要它們和根進程不可達的柱蟀,那么 GC 也是可以回收它們的,
下面的代碼可以看到這種情況的內(nèi)存回收:
import java.io.IOException; **public** **class** **GarbageTest** {**public** **static** **void** **main**(String[] args) throws IOException {
// TODO Auto-generated method stub **try** {
gcTest();
} **catch** (IOException e) {// TODO Auto-generated catch block e.printStackTrace();
}
System.**out**.println("has exited gcTest!");
System.**in**.read();
System.**in**.read();
System.**out**.println("out begin gc!");
**for**(**int** i=0;i<100;i++){
System.gc();
System.**in**.read();
System.**in**.read();
}
}
**private** **static** **void** **gcTest**() throws IOException {
System.**in**.read();
System.**in**.read();
Person p1 = **new** Person();
System.**in**.read();
System.**in**.read();
Person p2 = **new** Person();
p1.setMate(p2);
p2.setMate(p1);
System.**out**.println("before exit gctest!");
System.**in**.read();
System.**in**.read();
System.gc();
System.**out**.println("exit gctest!");
}
**private** **static** **class** **Person**{ **byte**[] data = **new** **byte**[20000000];
Person mate = null;
**public** **void** **setMate**(Person other){
mate = other;
}
}
}
Java 中的內(nèi)存泄露的情況:
長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內(nèi)存泄露蚜厉,盡管短生命周期對象已經(jīng)不再需要长已,但是因為長生命周期對象持有它的引用而導致不能被回收,這就是 Java 中內(nèi)存泄露的發(fā)生場景昼牛,通俗地說术瓮,就是程序員可能創(chuàng)建了一個對象,以后一直不再使用這個對象贰健,這個對象卻一直被引用胞四,即這個對象無用但是卻無法被垃圾回收器回收的,這就是 java 中可能出現(xiàn)內(nèi)存泄露的情況霎烙,例如,緩存系統(tǒng),我們加載了一個對象放在緩存中 (例如放在一個全局 map 對象中)悬垃,然后一直不再使用它游昼,這個對象一直被緩存引用,但卻不再被使用尝蠕。檢查 Java 中的內(nèi)存泄露烘豌,一定要讓程序?qū)⒏鞣N分支情況都完整執(zhí)行到程序結(jié)束,然后看某個對象是否被使用過看彼,如果沒有廊佩,則才能
判定這個對象屬于內(nèi)存泄露。如果一個外部類的實例對象的方法返回了一個內(nèi)部類的實例對象靖榕,這個內(nèi)部類對象被長期引用了标锄,即使那個外部類實例對象不再被使用,但由于內(nèi)部類持久外部類的實例對象茁计,這個外部類對象將不會被垃圾回收料皇,這也會造成內(nèi)存泄露。
**public** **class** Stack {**private** Object[] elements=**new** Object[10];
**private** **int** **size** = 0;
**public** **void** **push**(Object e){
ensureCapacity();
elements[**size**++] = e;
}
**public** Object **pop**(){
**if**( **size** == 0) **throw** **new** EmptyStackException();**return** elements[--**size**];
}
**private** **void** ensureCapacity(){**if**(elements.length == **size**){
Object[] oldElements = elements;
elements = **new** Object[2 * elements.length+1];
System.arraycopy(oldElements,0, elements, 0, **size**);
}
} }
上面的原理應該很簡單星压,假如堆棧加了 10 個元素践剂,然后全部彈出來,雖然堆棧是空的娜膘,沒有我們要的東西逊脯,但是這是個對象是無法回收的,這個才符合了內(nèi)存泄露的兩個條件:無用竣贪,無法回收军洼。但是就是存在這樣的東西也不一定會導致什么樣的后果,如果這個
堆棧用的比較少贾富,也就浪費了幾個 K 內(nèi)存而已歉眷,反正我們的內(nèi)存都上 G 了,哪里會有什么影響颤枪,再說這個東西很快就會被回收的汗捡,
有什么關(guān)系。下面看兩個例子畏纲。
public class Bad{public static Stack s=Stack();static{
s.push(new Object());
s.pop(); //這里有一個對象發(fā)生內(nèi)存泄露 s.push(new Object()); //上面的對象可以被回收了扇住,等于是自 愈了 } }
因為是 static,就一直存在到程序退出盗胀,但是我們也可以看到它有自愈功能艘蹋,就是說如果你的 Stack 最多有 100 個對象,那么最
多也就只有 100 個對象無法被回收其實這個應該很容易理解票灰,Stack 內(nèi)部持有 100 個引用女阀,最壞的情況就是他們都是無用的宅荤,
因為我們一旦放新的進去,以前的引用自然消失浸策!內(nèi)存泄露的另外一種情況:當一個對象被存儲進 HashSet 集合中以后冯键,就不能修改這對象中的那些參與計算哈希值的字段了,否則庸汗,對象修改后的哈希值與最初存儲進 HashSet 集合中時的哈希值就不同了惫确,在這種情況下,即使在 contains 方法使用該對象的當前引用作為的參數(shù)去 HashSet 集合中檢索對象蚯舱,也將返回找不到對象的結(jié)果改化,這也會導致無法從HashSet 集合中單獨刪除當前對象,造成內(nèi)存泄露枉昏。
深拷貝和淺拷貝陈肛。
簡單來講就是復制、克隆凶掰。
Person p=new Person(“張三”);
淺拷貝就是對對象中的數(shù)據(jù)成員進行簡單賦值燥爷,如果存在動態(tài)成員或者指針就會報錯。深拷貝就是對對象中存在的動態(tài)成員或指針重新開辟內(nèi)存空間懦窘。
finalize() 方法什么時候被調(diào)用前翎?析構(gòu)函數(shù) (finalization) 的目的是什么?
垃圾回收器(garbage colector)決定回收某對象時畅涂,就會運行該對象的 finalize() 方法 但是在 Java 中很不幸港华,如果內(nèi)存總是充
足的,那么垃圾回收可能永遠不會進行午衰,也就是說 filalize() 可能永遠不被執(zhí)行立宜,顯然指望它做收尾工作是靠不住的。
那么****finalize() 究竟是做什么的呢臊岸?
它最主要的用途是回收特殊渠道申請的內(nèi)存橙数。Java 程序有垃圾回收器,所以一般情況下內(nèi)存問題不用程序員操心帅戒。但有一種 JNI(Java Native Interface)調(diào)用non-Java 程序(C 或 C++)灯帮, finalize() 的工作就是回收這部分的內(nèi)存。
如果對象的引用被置為 null****逻住,垃圾收集器是否會立即釋放對象占用的內(nèi)存钟哥?
不會,在下一個垃圾回收周期中瞎访,這個對象將是可被回收的腻贰。
什么是分布式垃圾回收(****DGC****)?它是如何工作的扒秸?
DGC 叫做分布式垃圾回收播演。RMI 使用 DGC 來做自動垃圾回收冀瓦。因為 RMI 包含了跨虛擬機的遠程對象的引用,垃圾回收是很困難的写烤。DGC 使用引用計數(shù)算法來給遠程對象提供自動內(nèi)存管理咕幻。
簡述 Java 內(nèi)存分配與回收策率以及 Minor GC 和 Major GC****。
? 對象優(yōu)先在堆的 Eden 區(qū)分配
? 大對象直接進入老年代
? 長期存活的對象將直接進入老年代
當 Eden 區(qū)沒有足夠的空間進行分配時顶霞,虛擬機會執(zhí)行一次Minor GC。Minor GC 通常發(fā)生在新生代的 Eden 區(qū)锣吼,在這個區(qū)的對象生存期短选浑,往往發(fā)生 Gc 的頻率較高,回收速度比較快玄叠;
Full GC/Major GC 發(fā)生在老年代古徒,一般情況下,觸發(fā)老年代 GC 的時候不會觸發(fā) Minor GC读恃,但是通過配置隧膘,可以在 Full GC 之
前進行一次 Minor GC 這樣可以加快老年代的回收速度。
JVM 的永久代中會發(fā)生垃圾回收么寺惫?
垃圾回收不會發(fā)生在永久代疹吃,如果永久代滿了或者是超過了臨界值,
會觸發(fā)完全垃圾回收(Full GC)西雀。
注:Java 8 中已經(jīng)移除了永久代萨驶,新加了一個叫做元數(shù)據(jù)區(qū)的
native 內(nèi)存區(qū)。
Java 中垃圾收集的方法有哪些艇肴?
標記 - 清除:這是垃圾收集算法中最基礎(chǔ)的腔呜,根據(jù)名字就可以知
道,它的思想就是標記哪些要被回收的對象再悼,然后統(tǒng)一回收核畴。這種
方法很簡單,但是會有兩個主要問題:
效率不高冲九,標記和清除的效率都很低谤草;
會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,導致以后程序在分配較大的 對象時娘侍,由于沒有充足的連續(xù)內(nèi)存而提前觸發(fā)一次 GC 動作咖刃。
復制算法:
為了解決效率問題,復制算法將可用內(nèi)存按容量劃分為相等的兩部分憾筏,然后每次只使用其中的一塊嚎杨,當一塊內(nèi)存用完時,就將還存活的對象復制到第二塊內(nèi)存上氧腰,然后一次性清楚完第一塊內(nèi)存枫浙,再將第二塊上的對象復制到第一塊刨肃。但是這種方式,內(nèi)存的代價太高箩帚,每次基本上都要浪費一般的內(nèi)存真友。于是將該算法進行了改進,內(nèi)存區(qū)域不再是按照 1:1 去劃分紧帕,而是將內(nèi)存劃分為 8:1:1 三部分盔然,較大那份內(nèi)存交 Eden 區(qū),其余是兩塊較小的內(nèi)存區(qū)叫 Survior 區(qū)是嗜。每次都會優(yōu)先使用 Eden 區(qū)愈案,若 Eden 區(qū)滿,就將對象復制到第二塊內(nèi)存區(qū)上鹅搪,然后清除 Eden區(qū)站绪,如果此時存活的對象太多,以至于 Survivor 不夠時丽柿,會將這些對象通過分配擔保機制復制到老年代中恢准。(java 堆又分為新生代和老年代)
標記 - 整理:
該算法主要是為了解決標記 - 清除,產(chǎn)生大量內(nèi)存碎片的問題甫题;當對象存活率較高時馁筐,也解決了復制算法的效率問題。它的不同之處就是在清除對象的時候現(xiàn)將可回收對象移動到一端坠非,然后清除掉端邊界以外的對象眯漩,這樣就不會產(chǎn)生內(nèi)存碎片了。
分代收集:
現(xiàn)在的虛擬機垃圾收集大多采用這種方式麻顶,它根據(jù)對象的生存周期赦抖,將堆分為新生代和老年代。在新生代中辅肾,由于對象生存期短队萤,每次回收都會有大量對象死去,那么這時就采用復制算法矫钓。老年代里的對象存活率較高要尔,沒有額外的空間進行分配擔保。
什么是類加載器新娜,類加載器有哪些赵辕?
實現(xiàn)通過類的權(quán)限定名獲取該類的二進制字節(jié)流的代碼塊叫做類加載器。
主要有以下四種類加載器:
? 啟動類加載器(Bootstrap ClassLoader)用來加載 Java 核心類庫概龄,無法被 Java 程序直接引用还惠。
? 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現(xiàn)會提供一個擴展庫目錄私杜。該類加載器在此目錄里面查找并加載 Java 類蚕键。
? 系統(tǒng)類加載器(system class loader):它根據(jù) Java 應用的類路徑(CLASSPATH)來加載 Java 類救欧。一般來說,Java 應用的類都是由它來完成加載的锣光“实。可以通過ClassLoader.getSystemClassLoader() 來獲取它。
? 用戶自定義類加載器誊爹,通過繼承 java.lang.ClassLoader 類的方式實現(xiàn)蹬刷。
類加載器雙親委派模型機制?
當一個類收到了類加載請求時频丘,不會自己先去加載這個類箍铭,而是將其委派給父類,由父類去加載椎镣,如果此時父類不能加載,反饋給子類兽赁,由子類去完成類的加載状答。
1. 內(nèi)存模型以及分區(qū),需要詳細到每個區(qū)放什么刀崖。
JVM 分為堆區(qū)和棧區(qū)惊科,還有方法區(qū),初始化的對象放在堆里面亮钦,引用放在棧里面馆截,class 類信息常量池(static 常量和 static 變量)等放在方法區(qū)
· 方法區(qū):主要是存儲類信息,常量池(static 常量和 static 變量)蜂莉,編譯后的代碼(字節(jié)碼)等數(shù)據(jù)
· 堆:初始化的對象蜡娶,成員變量 (那種非 static 的變量),所有的對象實例和數(shù)組都要在堆上分配
· 棧:棧的結(jié)構(gòu)是棧幀組成的映穗,調(diào)用一個方法就壓入一幀窖张,幀上面存儲局部變量表,操作數(shù)棧蚁滋,方法出
· 本地方法棧:主要為 Native 方法服務
· 程序計數(shù)器:記錄當前線程執(zhí)行的行號
2. 堆里面的分區(qū):Eden宿接,survival (from+ to),老年代辕录,各自的特點睦霎。
堆里面分為新生代和老生代(java8 取消了永久代,采用了 Metaspace)走诞,新生代包含 Eden+Survivor 區(qū)副女,survivor 區(qū)里面分為 from 和 to 區(qū),內(nèi)存回收時蚣旱,如果用的是復制算法肮塞,從 from 復制到 to襟齿,當經(jīng)過一次或者多次 GC 之后,存活下來的對象會被移動到老年區(qū)枕赵,當 JVM 內(nèi)存不夠用的時候猜欺,會觸發(fā) Full GC,清理 JVM 老年區(qū)當新生區(qū)滿了之后會觸發(fā) YGC,先把存活的對象放到其中一個 Survice區(qū)拷窜,然后進行垃圾清理开皿。因為如果僅僅清理需要刪除的對象,這樣會導致內(nèi)存碎片篮昧,因此一般會把 Eden 進行完全的清理赋荆,然后整理內(nèi)存。那么下次 GC 的時候懊昨,就會使用下一個 Survive窄潭,這樣循環(huán)使用。如果有特別大的對象酵颁,新生代放不下嫉你,就會使用老年代的擔保,直接放到老年代里面躏惋。因為 JVM 認為幽污,一般大對象的存活時間一般比較久遠。
3. 對象創(chuàng)建方法簿姨,對象的內(nèi)存分配距误,對象的訪問定位。new 一個對象
4. GC 的兩種判定方法:
引用計數(shù)法:指的是如果某個地方引用了這個對象就+1扁位,如果失效了就-1准潭,當為 0 就會回收但是 JVM 沒有用這種方式,因為無法判定相互循環(huán)引用(A 引用 B,B 引用 A)的情況
引用鏈法:通過一種 GC ROOT 的對象(方法區(qū)中靜態(tài)變量引用的對象等-static 變量)來判斷域仇,如果有一條鏈能夠到達 GC ROOT 就說明惋鹅,不能到達 GC ROOT 就說明可以回收
SafePoint 是什么
比如 GC 的時候必須要等到 Java 線程都進入到 safepoint 的時候 VMThread 才能開始執(zhí)行 GC,
循環(huán)的末尾 (防止大循環(huán)的時候一直不進入 safepoint殉簸,而其他線程在等待它進入safepoint)
方法返回前
調(diào)用方法的 call 之后
拋出異常的位置
6. GC 的三種收集方法:標記清除闰集、標記整理、復制算法的原理與特點般卑,分別用在什么地方武鲁,如果讓你優(yōu)化收集方法,有什么思路蝠检?
先標記沐鼠,標記完畢之后再清除,效率不高,會產(chǎn)生碎片復制算法:分為 8:1 的 Eden 區(qū)和 survivor 區(qū)饲梭,就是上面談到的 YGC標記整理:標記完畢之后乘盖,讓所有存活的對象向一端移動
GC 收集器有哪些?CMS 收集器與 G1 收集器的特點憔涉。
并行收集器:串行收集器使用一個單獨的線程進行收集订框,GC 時服務有停頓時間
串行收集器:次要回收中使用多線程來執(zhí)行CMS 收集器是基于“標記—清除”算法實現(xiàn)的,經(jīng)過多次標記才會被清除
G1 從整體來看是基于“標記—整理”算法實現(xiàn)的收集器兜叨,從局部(兩個 Region 之間)上來看是基于“復制”算法實現(xiàn)的
Minor GC 與 Full GC 分別在什么時候發(fā)生
新生代內(nèi)存不夠用時候發(fā)生 MGC 也叫 YGC穿扳,JVM 內(nèi)存不夠的時候發(fā)生 FGC
幾種常用的內(nèi)存調(diào)試工具:jmap、jstack国旷、jconsole矛物、jhat
jstack 可以看當前棧的情況,jmap 查看內(nèi)存跪但,jhat 進行 dump 堆的信息
簡述 java 內(nèi)存分配與回收策率以及 Minor GC 和
Major GC
對象優(yōu)先在堆的 Eden 區(qū)分配履羞。
大對象直接進入老年代.
長期存活的對象將直接進入老年代. 當 Eden 區(qū)沒有足夠的空間進行分配時,虛擬機會執(zhí)行一次 Minor GC.Minor Gc 通 常發(fā)生在新生代的 Eden 區(qū)屡久,在這個區(qū)的對象生存期短忆首,往往發(fā)生 Gc 的頻率較高,回收速度比較快;Full Gc/Major GC 發(fā)生在老年代涂身,一般情況下,觸發(fā)老年代 GC的時候不會觸發(fā) Minor GC,但是通過配置搓蚪,可以在 Full GC 之前進行一次 Minor GC 這樣可以加快老年代的回收速度蛤售。
Java 類加載過程?
Java 類加載需要經(jīng)歷一下 7 個過程:
1. 加載
加載是類加載的第一個過程妒潭,在這個階段悴能,將完成一下三件事情:
? 通過一個類的全限定名獲取該類的二進制流。
? 將該二進制流中的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法去運行時數(shù)據(jù)結(jié)
構(gòu)雳灾。
? 在內(nèi)存中生成該類的 Class 對象漠酿,作為該類的數(shù)據(jù)訪問入口。
2. 驗證
驗證的目的是為了確保 Class 文件的字節(jié)流中的信息不會危害到虛擬機.在該階段主要完成以下四種驗證: ? 文件格式驗證:驗證字節(jié)流是否符合 Class 文件的規(guī)范谎亩,如主次版本號是否在當前虛擬機范圍內(nèi)炒嘲,常量池中的常量是否有不被支持的類型. ? 元數(shù)據(jù)驗證:對字節(jié)碼描述的信息進行語義分析,如這個類是否有父類匈庭,是否集成了不被繼承的類等夫凸。
? 字節(jié)碼驗證:是整個驗證過程中最復雜的一個階段,通過驗證數(shù)據(jù)流和控制流的分析阱持,確定程序語義是否正確夭拌,主要針對方法體的驗證。如:方法中的類型轉(zhuǎn)換是否正確,跳轉(zhuǎn)指令是否正確等鸽扁。
? 符號引用驗證:這個動作在后面的解析過程中發(fā)生蒜绽,主要是為了確保解析動作能正確執(zhí)行。
3. 準備
準備階段是為類的靜態(tài)變量分配內(nèi)存并將其初始化為默認值桶现,這些內(nèi)存都將在方法區(qū)中進行分配躲雅。準備階段不分配類中的實例變量的內(nèi)存,實例變量將會在對象實例化時隨著對象一起分配在 Java 堆中巩那。
public static int value=123;//在準備階段 value 初始值為 0 吏夯。在初始化階段才會變?yōu)?123 。
4. 解析
該階段主要完成符號引用到直接引用的轉(zhuǎn)換動作即横。解析動作并不一定在初始化動作完成之前噪生,也有可能在初始化之后。
5. 初始化
初始化時類加載的最后一步东囚,前面的類加載過程跺嗽,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機主導和控制页藻。到了初始化階段桨嫁,才真正開始執(zhí)行類中定義的Java 程序代碼。
6. 使用
7. 卸載
描述一下 JVM 加載 Class 文件的原理機制****?
Java 語言是一種具有動態(tài)性的解釋型語言份帐,類(Class)只有被加載到 JVM 后才能運行璃吧。當運行指定程序時,JVM 會將編譯生成
的 .class 文件按照需求和一定的規(guī)則加載到內(nèi)存中废境,并組織成為一個完整的 Java 應用程序畜挨。這個加載過程是由類加載器完成,具
體來說噩凹,就是由 ClassLoader 和它的子類來實現(xiàn)的巴元。類加載器本身也是一個類,其實質(zhì)是把類文件從硬盤讀取到內(nèi)存中驮宴。
類的加載方式分為隱式加載和顯示加載逮刨。
隱式加載指的是程序在使用 new 等方式創(chuàng)建對象時,會隱式地調(diào)用類的加載器把對應的類加載到 JVM 中堵泽。顯示加載指的是通過直接調(diào)用 class.forName() 方法來把所需的類加載到 JVM 中修己。任何一個工程項目都是由許多類組成的,當程序啟動時迎罗,只把需要的類加載到 JVM 中箩退,其他類只有被使用到的時候才會被加載,采用這種方法一方面可以加快加載速度佳谦,另一方面可以節(jié)約程序運行時對內(nèi)存的開銷戴涝。此外,在 Java 語言中,每個類或接口都對應一個 .class 文件啥刻,這些文件可以被看成是一個個可以被動態(tài)加載的單元奸鸯,因此當只有部分類被修改時,只需要重新編譯變化的類即可可帽,而不需要重新編譯所有文件娄涩,因此加快了編譯速度。在 Java 語言中映跟,類的加載是動態(tài)的蓄拣,它并不會一次性將所有類全部加載后再運行,而是保證程序運行的基礎(chǔ)類(例如基類)完全加載到 JVM 中努隙,至于其他類球恤,則在需要的時候才加載。
類加載的主要步驟:
? 裝載荸镊。根據(jù)查找路徑找到相應的 class 文件咽斧,然后導入。
? 鏈接躬存。鏈接又可分為 3 個小步:
? 檢查张惹,檢查待加載的 class 文件的正確性。
? 準備岭洲,給類中的靜態(tài)變量分配存儲空間宛逗。
? 解析,將符號引用轉(zhuǎn)換為直接引用(這一步可選)
? 初始化盾剩。對靜態(tài)變量和靜態(tài)代碼塊執(zhí)行初始化工作雷激。