面試阿里,字節(jié)跳動(dòng)必問JVM問題!你不進(jìn)來看看嗎?附答案

Java 內(nèi)存分配

? 寄存器:程序計(jì)數(shù)器,是線程私有的,就是一個(gè)指針,指向方法區(qū)中的方法字節(jié)碼。

? 靜態(tài)域:static 定義的靜態(tài)成員。

? 常量池:編譯時(shí)被確定并保存在 .class 文件中的(final)

常量值和一些文本修飾的符號(hào)引用(類和接口的全限定名,字段的名稱和描述符,方法和名稱和描述符)。

? 非 RAM 存儲(chǔ):硬盤等永久存儲(chǔ)空間。

? 堆內(nèi)存:new 創(chuàng)建的對象和數(shù)組,由 Java 虛擬機(jī)自動(dòng)垃圾回收器管理,存取速度慢。

? 棧內(nèi)存:基本類型的變量和對象的引用變量(堆內(nèi)存空間的訪問地址),速度快,可以共享,但是大小與生存期必須確定,缺乏靈活性。

串行(serial)收集器和吞吐量(throughput)收集器的區(qū)別是什么?

吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等規(guī)模和大規(guī)模數(shù)據(jù)的應(yīng)用程序。

而串行收集器對大多數(shù)的小應(yīng)用(在現(xiàn)代處理器上需要大概100M 左右的內(nèi)存)就足夠了。

在Java 中,對象什么時(shí)候可以被垃圾回收?

當(dāng)對象對當(dāng)前使用這個(gè)對象的應(yīng)用程序變得不可觸及的時(shí)候,這個(gè)對象就可以被回收了。

GC 是什么? 為什么要有 GC?

GC 是垃圾收集的意思(GabageCollection),內(nèi)存處理是編程人員容易出現(xiàn)問題的地方,忘記或者錯(cuò)誤的內(nèi)存回收會(huì)導(dǎo)致程序或

系統(tǒng)的不穩(wěn)定甚至崩潰,Java 提供的 GC 功能可以自動(dòng)監(jiān)測對象是否超過作用域從而達(dá)到自動(dòng)回收內(nèi)存的目的,Java 語言沒有提

供釋放已分配內(nèi)存的顯示操作方法。

簡述Java 垃圾回收機(jī)制。

在Java 中,程序員是不需要顯示的去釋放一個(gè)對象的內(nèi)存的,而是由虛擬機(jī)自行執(zhí)行。在 JVM 中,有一個(gè)垃圾回收線程,它是低優(yōu)先級的,在正常情況下是不會(huì)執(zhí)行的,只有在虛擬機(jī)空閑或者當(dāng)前堆內(nèi)存不足時(shí),才會(huì)觸發(fā)執(zhí)行,掃面那些沒有被任何引用的對象,并將它們添加到要回收的集合中,進(jìn)行回收。

如何判斷一個(gè)對象是否存活?(或者GC 對象的判定方法)

判斷一個(gè)對象是否存活有兩種方法:

引用計(jì)數(shù)法

所謂引用計(jì)數(shù)法就是給每一個(gè)對象設(shè)置一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用這個(gè)對象時(shí),就將計(jì)數(shù)器加一,引用失效時(shí),計(jì)數(shù)器就

減一。當(dāng)一個(gè)對象的引用計(jì)數(shù)器為零時(shí),說明此對象沒有被引用,也就是“死對象”,將會(huì)被垃圾回收.引用計(jì)數(shù)法有一個(gè)缺陷就是無法解決循環(huán)引用問題,也就是說當(dāng)對象 A 引用對象 B,對象 B 又引用者對象 A,那么此時(shí) A、B 對象的引用計(jì)數(shù)器都不為零,也就造成無法完成垃圾回收,所以主流的虛擬機(jī)都沒有采用這種算法。

可達(dá)性算法(引用鏈法)

該算法的思想是:從一個(gè)被稱為GC Roots 的對象開始向下搜索,如果一個(gè)對象到 GC Roots 沒有任何引用鏈相連時(shí),則說明此對

象不可用。

在Java 中可以作為 GC Roots 的對象有以下幾種:

? 虛擬機(jī)棧中引用的對象

? 方法區(qū)類靜態(tài)屬性引用的對象

? 方法區(qū)常量池引用的對象

? 本地方法棧 JNI 引用的對象

雖然這些算法可以判定一個(gè)對象是否能被回收,但是當(dāng)滿足上述條件時(shí),一個(gè)對象比不一定會(huì)被回收。當(dāng)一個(gè)對象不可達(dá)GC Root

時(shí),這個(gè)對象并不會(huì)立馬被回收,而是出于一個(gè)死緩的階段,若要被真正的回收需要經(jīng)歷兩次標(biāo)記.如果對象在可達(dá)性分析中沒有與 GC Root 的引用鏈,那么此時(shí)就會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選,篩選的條件是是否有必要執(zhí)行finalize() 方法。當(dāng)對象沒有覆蓋 finalize() 方法或者已被虛擬機(jī)調(diào)用過,那么就認(rèn)為是沒必要的。 如果該對象有必要執(zhí)行finalize() 方法,那么這個(gè)對象將會(huì)放在一個(gè)稱為 F-Queue 的對

隊(duì)列中,虛擬機(jī)會(huì)觸發(fā)一個(gè)Finalize() 線程去執(zhí)行,此線程是低優(yōu)先級的,并且虛擬機(jī)不會(huì)承諾一直等待它運(yùn)行完,這是因?yàn)槿绻鹒inalize() 執(zhí)行緩慢或者發(fā)生了死鎖,那么就會(huì)造成 F-Queue 隊(duì)列一直等待,造成了內(nèi)存回收系統(tǒng)的崩潰。GC 對處于 F-Queue 中的對象進(jìn)行第二次被標(biāo)記,這時(shí),該對象將被移除” 即將回收”集合,等待回收。

垃圾回收的優(yōu)點(diǎn)和原理。并考慮2 種回收機(jī)制。

Java 語言中一個(gè)顯著的特點(diǎn)就是引入了垃圾回收機(jī)制,使 C++ 程序員最頭疼的內(nèi)存管理的問題迎刃而解,它使得 Java 程序員在編寫程序的時(shí)候不再需要考慮內(nèi)存管理。由于有個(gè)垃圾回收機(jī)制,Java 中的對象不再有“作用域”的概念,只有對象的引用才有"作用域"。垃圾回收可以有效的防止內(nèi)存泄露,有效的使用可以使用的內(nèi)存。垃圾回收器通常是作為一個(gè)單獨(dú)的低級別的線程運(yùn)行,不可預(yù)知的情況下對內(nèi)存堆中已經(jīng)死亡的或者長時(shí)間沒有使用的對象進(jìn)行清楚和回收,程序員不能實(shí)時(shí)的調(diào)用垃圾回收器對某個(gè)對象或所有對象進(jìn)行垃圾回收。

回收機(jī)制有分代復(fù)制垃圾回收和標(biāo)記垃圾回收,增量垃圾回收。

垃圾回收器的基本原理是什么?垃圾回收器可以馬上回收內(nèi)存嗎?有什么辦法主動(dòng)通知虛擬機(jī)進(jìn)行垃圾回收?

對于GC 來說,當(dāng)程序員創(chuàng)建對象時(shí),GC 就開始監(jiān)控這個(gè)對象的地址、大小以及使用情況。通常,GC 采用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是”可達(dá)的”,哪些對象是”不可達(dá)的”。當(dāng) GC 確定一些對象為“不可達(dá)”時(shí),GC 就有責(zé)任回收這些內(nèi)存空間??梢?。程序員可以手動(dòng)執(zhí)行 System.gc(),通知 GC 運(yùn)行,但是 Java 語言規(guī)范并不保證 GC 一定會(huì)執(zhí)行。

System.gc() 和 Runtime.gc() 會(huì)做什么事情?

這兩個(gè)方法用來提示JVM 要進(jìn)行垃圾回收。但是,立即開始還是延遲進(jìn)行垃圾回收是取決于 JVM 的。

Java 堆的結(jié)構(gòu)是什么樣子的?什么是堆中的永久代(Perm Gen space)?

JVM 的堆是運(yùn)行時(shí)數(shù)據(jù)區(qū),所有類的實(shí)例和數(shù)組都是在堆上分配內(nèi)存。它在 JVM 啟動(dòng)的時(shí)候被創(chuàng)建。對象所占的堆內(nèi)存是由自動(dòng)

內(nèi)存管理系統(tǒng)也就是垃圾收集器回收。堆內(nèi)存是由存活和死亡的對象組成的。存活的對象是應(yīng)用可以訪問的,不會(huì)被垃圾回收。死亡的對象是應(yīng)用不可訪問尚且還沒有被垃圾收集器回收掉的對象。一直到垃圾收集器把這些對象回收掉之前,他們會(huì)一直占據(jù)堆內(nèi)存空間。

Java 中會(huì)存在內(nèi)存泄漏嗎,請簡單描述。

所謂內(nèi)存泄露就是指一個(gè)不再被程序使用的對象或變量一直被占據(jù)在內(nèi)存中。Java 中有垃圾回收機(jī)制,它可以保證一對象不再被引用的時(shí)候,即對象變成了孤兒的時(shí)候,對象將自動(dòng)被垃圾回收器從內(nèi)存中清除掉。由于 Java 使用有向圖的方式進(jìn)行垃圾回收管理,可以消除引用循環(huán)的問題,例如有兩個(gè)對象,相互引用,只要它們和根進(jìn)程不可達(dá)的,那么 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)不再需要,但是因?yàn)殚L生命周期對象持有它的引用而導(dǎo)致不能被回收,這就是Java 中內(nèi)存泄露的發(fā)生場景,通俗地說,就是程序員可能創(chuàng)建了一個(gè)對象,以后一直不再使用這個(gè)對象,這個(gè)對象卻一直被引用,即這個(gè)對象無用但是卻無法被垃圾回收器回收的,這就是 java 中可能出現(xiàn)內(nèi)存泄露的情況,例如,緩存系統(tǒng),我們加載了一個(gè)對象放在緩存中 (例如放在一個(gè)全局 map 對象中),然后一直不再使用它,這個(gè)對象一直被緩存引用,但卻不再被使用。檢查 Java 中的內(nèi)存泄露,一定要讓程序?qū)⒏鞣N分支情況都完整執(zhí)行到程序結(jié)束,然后看某個(gè)對象是否被使用過,如果沒有,則才能

判定這個(gè)對象屬于內(nèi)存泄露。如果一個(gè)外部類的實(shí)例對象的方法返回了一個(gè)內(nèi)部類的實(shí)例對象,這個(gè)內(nèi)部類對象被長期引用了,即使那個(gè)外部類實(shí)例對象不再被使用,但由于內(nèi)部類持久外部類的實(shí)例對象,這個(gè)外部類對象將不會(huì)被垃圾回收,這也會(huì)造成內(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);

?}

?} }

上面的原理應(yīng)該很簡單,假如堆棧加了10 個(gè)元素,然后全部彈出來,雖然堆棧是空的,沒有我們要的東西,但是這是個(gè)對象是無法回收的,這個(gè)才符合了內(nèi)存泄露的兩個(gè)條件:無用,無法回收。但是就是存在這樣的東西也不一定會(huì)導(dǎo)致什么樣的后果,如果這個(gè)

堆棧用的比較少,也就浪費(fèi)了幾個(gè)K 內(nèi)存而已,反正我們的內(nèi)存都上 G 了,哪里會(huì)有什么影響,再說這個(gè)東西很快就會(huì)被回收的,

有什么關(guān)系。下面看兩個(gè)例子。

public?class?Bad{

?public?static?Stack s=Stack();

?static{

?s.push(new?Object());

?s.pop(); //這里有一個(gè)對象發(fā)生內(nèi)存泄露

?s.push(new?Object()); //上面的對象可以被回收了,等于是自

愈了

?} }

因?yàn)槭莝tatic,就一直存在到程序退出,但是我們也可以看到它有自愈功能,就是說如果你的 Stack 最多有 100 個(gè)對象,那么最

多也就只有100 個(gè)對象無法被回收其實(shí)這個(gè)應(yīng)該很容易理解,Stack 內(nèi)部持有 100 個(gè)引用,最壞的情況就是他們都是無用的,

因?yàn)槲覀円坏┓判碌倪M(jìn)取,以前的引用自然消失!內(nèi)存泄露的另外一種情況:當(dāng)一個(gè)對象被存儲(chǔ)進(jìn)HashSet 集合中以后,就不能修改這對象中的那些參與計(jì)算哈希值的字段了,否則,對象修改后的哈希值與最初存儲(chǔ)進(jìn) HashSet 集合中時(shí)的哈希值就不同了,在這種情況下,即使在 contains 方法使用該對象的當(dāng)前引用作為的參數(shù)去 HashSet 集合中檢索對象,也將返回找不到對象的結(jié)果,這也會(huì)導(dǎo)致無法從HashSet 集合中單獨(dú)刪除當(dāng)前對象,造成內(nèi)存泄露。

深拷貝和淺拷貝。

簡單來講就是復(fù)制、克隆。

Person p=new Person(“張三”);

淺拷貝就是對對象中的數(shù)據(jù)成員進(jìn)行簡單賦值,如果存在動(dòng)態(tài)成員或者指針就會(huì)報(bào)錯(cuò)。深拷貝就是對對象中存在的動(dòng)態(tài)成員或指針重新開辟內(nèi)存空間。

finalize() 方法什么時(shí)候被調(diào)用?析構(gòu)函數(shù) (finalization) 的目的是什么?

垃圾回收器(garbage colector)決定回收某對象時(shí),就會(huì)運(yùn)行該對象的 finalize() 方法 但是在 Java 中很不幸,如果內(nèi)存總是充

足的,那么垃圾回收可能永遠(yuǎn)不會(huì)進(jìn)行,也就是說filalize() 可能永遠(yuǎn)不被執(zhí)行,顯然指望它做收尾工作是靠不住的。

那么finalize() 究竟是做什么的呢?

它最主要的用途是回收特殊渠道申請的內(nèi)存。Java 程序有垃圾回收器,所以一般情況下內(nèi)存問題不用程序員操心。但有一種 JNI(Java Native Interface)調(diào)用non-Java 程序(C 或 C++), finalize() 的工作就是回收這部分的內(nèi)存。

如果對象的引用被置為null,垃圾收集器是否會(huì)立即釋放對象占用的內(nèi)存?

不會(huì),在下一個(gè)垃圾回收周期中,這個(gè)對象將是可被回收的。

什么是分布式垃圾回收(DGC)?它是如何工作的?

DGC 叫做分布式垃圾回收。RMI 使用 DGC 來做自動(dòng)垃圾回收。因?yàn)?RMI 包含了跨虛擬機(jī)的遠(yuǎn)程對象的引用,垃圾回收是很困難的。DGC 使用引用計(jì)數(shù)算法來給遠(yuǎn)程對象提供自動(dòng)內(nèi)存管理。

簡述Java 內(nèi)存分配與回收策率以及 Minor GC 和 Major GC。

? 對象優(yōu)先在堆的 Eden 區(qū)分配

? 大對象直接進(jìn)入老年代

? 長期存活的對象將直接進(jìn)入老年代

當(dāng)Eden 區(qū)沒有足夠的空間進(jìn)行分配時(shí),虛擬機(jī)會(huì)執(zhí)行一次Minor GC。Minor GC 通常發(fā)生在新生代的 Eden 區(qū),在這個(gè)區(qū)的對象生存期短,往往發(fā)生 Gc 的頻率較高,回收速度比較快;

Full GC/Major GC 發(fā)生在老年代,一般情況下,觸發(fā)老年代 GC 的時(shí)候不會(huì)觸發(fā) Minor GC,但是通過配置,可以在 Full GC 之

前進(jìn)行一次Minor GC 這樣可以加快老年代的回收速度。

JVM 的永久代中會(huì)發(fā)生垃圾回收么?

垃圾回收不會(huì)發(fā)生在永久代,如果永久代滿了或者是超過了臨界值,

會(huì)觸發(fā)完全垃圾回收(Full GC)。

注:Java 8 中已經(jīng)移除了永久代,新加了一個(gè)叫做元數(shù)據(jù)區(qū)的

native 內(nèi)存區(qū)。

Java 中垃圾收集的方法有哪些?

標(biāo)記- 清除:這是垃圾收集算法中最基礎(chǔ)的,根據(jù)名字就可以知

道,它的思想就是標(biāo)記哪些要被回收的對象,然后統(tǒng)一回收。這種

方法很簡單,但是會(huì)有兩個(gè)主要問題:

[if !supportLists]1.?[endif]效率不高,標(biāo)記和清除的效率都很低;

[if !supportLists]2.?[endif]會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,導(dǎo)致以后程序在分配較大的對象時(shí),由于沒有充足的連續(xù)內(nèi)存而提前觸發(fā)一次GC 動(dòng)作。

復(fù)制算法:

為了解決效率問題,復(fù)制算法將可用內(nèi)存按容量劃分為相等的兩部分,然后每次只使用其中的一塊,當(dāng)一塊內(nèi)存用完時(shí),就將還存活的對象復(fù)制到第二塊內(nèi)存上,然后一次性清楚完第一塊內(nèi)存,再將第二塊上的對象復(fù)制到第一塊。但是這種方式,內(nèi)存的代價(jià)太高,每次基本上都要浪費(fèi)一般的內(nèi)存。于是將該算法進(jìn)行了改進(jìn),內(nèi)存區(qū)域不再是按照1:1 去劃分,而是將內(nèi)存劃分為 8:1:1 三部分,較大那份內(nèi)存交 Eden 區(qū),其余是兩塊較小的內(nèi)存區(qū)叫 Survior 區(qū)。每次都會(huì)優(yōu)先使用 Eden 區(qū),若 Eden 區(qū)滿,就將對象復(fù)制到第二塊內(nèi)存區(qū)上,然后清除 Eden

區(qū),如果此時(shí)存活的對象太多,以至于Survivor 不夠時(shí),會(huì)將這些對象通過分配擔(dān)保機(jī)制復(fù)制到老年代中。(java 堆又分為新生代和老年代)

標(biāo)記- 整理:

該算法主要是為了解決標(biāo)記- 清除,產(chǎn)生大量內(nèi)存碎片的問題;當(dāng)對象存活率較高時(shí),也解決了復(fù)制算法的效率問題。它的不同之處就是在清除對象的時(shí)候現(xiàn)將可回收對象移動(dòng)到一端,然后清除掉端邊界以外的對象,這樣就不會(huì)產(chǎn)生內(nèi)存碎片了。

分代收集:

現(xiàn)在的虛擬機(jī)垃圾收集大多采用這種方式,它根據(jù)對象的生存周期,將堆分為新生代和老年代。在新生代中,由于對象生存期短,每次回收都會(huì)有大量對象死去,那么這時(shí)就采用復(fù)制算法。老年代里的對象存活率較高,沒有額外的空間進(jìn)行分配擔(dān)保。

什么是類加載器,類加載器有哪些?

實(shí)現(xiàn)通過類的權(quán)限定名獲取該類的二進(jìn)制字節(jié)流的代碼塊叫做類加載器。

主要有一下四種類加載器:

? 啟動(dòng)類加載器(Bootstrap ClassLoader)用來加載 Java 核心類庫,無法被 Java 程序直接引用。

? 擴(kuò)展類加載器(extensions class loader):它用來加載 Java 的擴(kuò)展庫。Java 虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。

? 系統(tǒng)類加載器(system class loader):它根據(jù) Java 應(yīng)用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應(yīng)用的類都是由它來完成加載的??梢酝ㄟ^ClassLoader.getSystemClassLoader() 來獲取它。

? 用戶自定義類加載器,通過繼承 java.lang.ClassLoader 類的方式實(shí)現(xiàn)。

類加載器雙親委派模型機(jī)制?

當(dāng)一個(gè)類收到了類加載請求時(shí),不會(huì)自己先去加載這個(gè)類,而是將其委派給父類,由父類去加載,如果此時(shí)父類不能加載,反饋給子類,由子類去完成類的加載。

1. 內(nèi)存模型以及分區(qū),需要詳細(xì)到每個(gè)區(qū)放什么。

JVM 分為堆區(qū)和棧區(qū),還有方法區(qū),初始化的對象放在堆里面,引用放在棧里面,class 類信息常量池(static 常量和 static 變量)等放在方法區(qū)

· 方法區(qū):主要是存儲(chǔ)類信息,常量池(static 常量和 static 變量),編譯后的代碼(字節(jié)碼)等數(shù)據(jù)

· 堆:初始化的對象,成員變量 (那種非 static 的變量),所有的對象實(shí)例和數(shù)組都要在堆上分配

· 棧:棧的結(jié)構(gòu)是棧幀組成的,調(diào)用一個(gè)方法就壓入一幀,幀上面存儲(chǔ)局部變量表,操作數(shù)棧,方法出

· 本地方法棧:主要為 Native 方法服務(wù)

· 程序計(jì)數(shù)器:記錄當(dāng)前線程執(zhí)行的行號(hào)

2. 堆里面的分區(qū):Eden,survival (from+ to),老年代,各自的特點(diǎn)。

堆里面分為新生代和老生代(java8 取消了永久代,采用了 Metaspace),新生代包含 Eden+Survivor 區(qū),survivor 區(qū)里面分為 from 和 to 區(qū),內(nèi)存回收時(shí),如果用的是復(fù)制算法,從 from 復(fù)制到 to,當(dāng)經(jīng)過一次或者多次 GC 之后,存活下來的對象會(huì)被移動(dòng)到老年區(qū),當(dāng) JVM 內(nèi)存不夠用的時(shí)候,會(huì)觸發(fā) Full GC,清理 JVM 老年區(qū)當(dāng)新生區(qū)滿了之后會(huì)觸發(fā) YGC,先把存活的對象放到其中一個(gè) Survice區(qū),然后進(jìn)行垃圾清理。因?yàn)槿绻麅H僅清理需要?jiǎng)h除的對象,這樣會(huì)導(dǎo)致內(nèi)存碎片,因此一般會(huì)把 Eden 進(jìn)行完全的清理,然后整理內(nèi)存。那么下次 GC 的時(shí)候,就會(huì)使用下一個(gè) Survive,這樣循環(huán)使用。如果有特別大的對象,新生代放不下,就會(huì)使用老年代的擔(dān)保,直接放到老年代里面。因?yàn)?JVM 認(rèn)為,一般大對象的存活時(shí)間一般比較久遠(yuǎn)。

3. 對象創(chuàng)建方法,對象的內(nèi)存分配,對象的訪問定位。new 一個(gè)對象

4. GC 的兩種判定方法:

引用計(jì)數(shù)法:指的是如果某個(gè)地方引用了這個(gè)對象就+1,如果失效了就-1,當(dāng)為 0 就會(huì)回收但是 JVM 沒有用這種方式,因?yàn)闊o法判定相互循環(huán)引用(A 引用 B,B 引用 A)的情況

引用鏈法:通過一種GC ROOT 的對象(方法區(qū)中靜態(tài)變量引用的對象等-static 變量)來判斷,如果有一條鏈能夠到達(dá) GC ROOT 就說明,不能到達(dá) GC ROOT 就說明可以回收

SafePoint 是什么

比如GC 的時(shí)候必須要等到 Java 線程都進(jìn)入到 safepoint 的時(shí)候 VMThread 才能開始執(zhí)行 GC,

[if !supportLists]1.?[endif]循環(huán)的末尾(防止大循環(huán)的時(shí)候一直不進(jìn)入 safepoint,而其他線程在等待它進(jìn)入safepoint)

[if !supportLists]2.?[endif]方法返回前

[if !supportLists]3.?[endif]調(diào)用方法的call 之后

[if !supportLists]4.?[endif]拋出異常的位置

6. GC 的三種收集方法:標(biāo)記清除、標(biāo)記整理、復(fù)制算法的原理與特點(diǎn),分別用在什么地方,如果讓你優(yōu)化收集方法,有什么思路?

先標(biāo)記,標(biāo)記完畢之后再清除,效率不高,會(huì)產(chǎn)生碎片復(fù)制算法:分為8:1 的 Eden 區(qū)和 survivor 區(qū),就是上面談到的 YGC標(biāo)記整理:標(biāo)記完畢之后,讓所有存活的對象向一端移動(dòng)

GC 收集器有哪些?CMS 收集器與 G1 收集器的特點(diǎn)。

并行收集器:串行收集器使用一個(gè)單獨(dú)的線程進(jìn)行收集,GC 時(shí)服務(wù)有停頓時(shí)間

串行收集器:次要回收中使用多線程來執(zhí)行CMS 收集器是基于“標(biāo)記—清除”算法實(shí)現(xiàn)的,經(jīng)過多次標(biāo)記才會(huì)被清除

G1 從整體來看是基于“標(biāo)記—整理”算法實(shí)現(xiàn)的收集器,從局部(兩個(gè) Region 之間)上來看是基于“復(fù)制”算法實(shí)現(xiàn)的

Minor GC 與 Full GC 分別在什么時(shí)候發(fā)生

新生代內(nèi)存不夠用時(shí)候發(fā)生MGC 也叫 YGC,JVM 內(nèi)存不夠的時(shí)候發(fā)生 FGC

幾種常用的內(nèi)存調(diào)試工具:jmap、jstack、jconsole、jhat

jstack 可以看當(dāng)前棧的情況,jmap 查看內(nèi)存,jhat 進(jìn)行 dump 堆的信息

簡述java 內(nèi)存分配與回收策率以及 Minor GC 和

Major GC

[if !supportLists]1.?[endif]對象優(yōu)先在堆的Eden 區(qū)分配。

[if !supportLists]2.?[endif]大對象直接進(jìn)入老年代.

[if !supportLists]3.?[endif]長期存活的對象將直接進(jìn)入老年代. 當(dāng) Eden 區(qū)沒有足夠的空間進(jìn)行分配時(shí),虛擬機(jī)會(huì)執(zhí)行一次 Minor GC.Minor Gc 通常發(fā)生在新生代的Eden 區(qū),在這個(gè)區(qū)的對象生存期短,往往發(fā)生 Gc 的頻率較高,回收速度比較快;Full Gc/Major GC 發(fā)生在老年代,一般情況下,觸發(fā)老年代 GC的時(shí)候不會(huì)觸發(fā) Minor GC,但是通過配置,可以在 Full GC 之前進(jìn)行一次 Minor GC 這樣可以加快老年代的回收速度。

Java 類加載過程?

Java 類加載需要經(jīng)歷一下 7 個(gè)過程:

1. 加載

加載是類加載的第一個(gè)過程,在這個(gè)階段,將完成一下三件事情:

? 通過一個(gè)類的全限定名獲取該類的二進(jìn)制流。

? 將該二進(jìn)制流中的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法去運(yùn)行時(shí)數(shù)據(jù)結(jié)

構(gòu)。

? 在內(nèi)存中生成該類的 Class 對象,作為該類的數(shù)據(jù)訪問入口。

2. 驗(yàn)證

驗(yàn)證的目的是為了確保Class 文件的字節(jié)流中的信息不回危害到虛擬機(jī).在該階段主要完成以下四鐘驗(yàn)證: ? 文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合 Class 文件的規(guī)范,如主次版本號(hào)是否在當(dāng)前虛擬機(jī)范圍內(nèi),常量池中的常量是否有不被支持的類型. ? 元數(shù)據(jù)驗(yàn)證:對字節(jié)碼描述的信息進(jìn)行語義分析,如這個(gè)類是否有父類,是否集成了不被繼承的類等。

? 字節(jié)碼驗(yàn)證:是整個(gè)驗(yàn)證過程中最復(fù)雜的一個(gè)階段,通過驗(yàn)證數(shù)據(jù)流和控制流的分析,確定程序語義是否正確,主要針對方法體的驗(yàn)證。如:方法中的類型轉(zhuǎn)換是否正確,跳轉(zhuǎn)指令是否正確等。

? 符號(hào)引用驗(yàn)證:這個(gè)動(dòng)作在后面的解析過程中發(fā)生,主要是為了確保解析動(dòng)作能正確執(zhí)行。

3. 準(zhǔn)備

準(zhǔn)備階段是為類的靜態(tài)變量分配內(nèi)存并將其初始化為默認(rèn)值,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。準(zhǔn)備階段不分配類中的實(shí)例變量的內(nèi)存,實(shí)例變量將會(huì)在對象實(shí)例化時(shí)隨著對象一起分配在Java 堆中。

public static int value=123;//在準(zhǔn)備階段 value 初始值為 0 。在初始化階段才會(huì)變?yōu)?123 。

4. 解析

該階段主要完成符號(hào)引用到直接引用的轉(zhuǎn)換動(dòng)作。解析動(dòng)作并不一定在初始化動(dòng)作完成之前,也有可能在初始化之后。

5. 初始化

初始化時(shí)類加載的最后一步,前面的類加載過程,除了在加載階段用戶應(yīng)用程序可以通過自定義類加載器參與之外,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段,才真正開始執(zhí)行類中定義的Java 程序代碼。

6. 使用

7. 卸載

描述一下JVM 加載 Class 文件的原理機(jī)制?

Java 語言是一種具有動(dòng)態(tài)性的解釋型語言,類(Class)只有被加載到 JVM 后才能運(yùn)行。當(dāng)運(yùn)行指定程序時(shí),JVM 會(huì)將編譯生成

的.class 文件按照需求和一定的規(guī)則加載到內(nèi)存中,并組織成為一個(gè)完整的 Java 應(yīng)用程序。這個(gè)加載過程是由類加載器完成,具

體來說,就是由ClassLoader 和它的子類來實(shí)現(xiàn)的。類加載器本身也是一個(gè)類,其實(shí)質(zhì)是把類文件從硬盤讀取到內(nèi)存中。

類的加載方式分為隱式加載和顯示加載。

隱式加載指的是程序在使用new 等方式創(chuàng)建對象時(shí),會(huì)隱式地調(diào)用類的加載器把對應(yīng)的類加載到 JVM 中。顯示加載指的是通過直接調(diào)用 class.forName() 方法來把所需的類加載到 JVM 中。任何一個(gè)工程項(xiàng)目都是由許多類組成的,當(dāng)程序啟動(dòng)時(shí),只把需要的類加載到 JVM 中,其他類只有被使用到的時(shí)候才會(huì)被加載,采用這種方法一方面可以加快加載速度,另一方面可以節(jié)約程序運(yùn)行時(shí)對內(nèi)存的開銷。此外,在 Java 語言中,每個(gè)類或接口都對應(yīng)一個(gè) .class 文件,這些文件可以被看成是一個(gè)個(gè)可以被動(dòng)態(tài)加載的單元,因此當(dāng)只有部分類被修改時(shí),只需要重新編譯變化的類即可,而不需要重新編譯所有文件,因此加快了編譯速度。在 Java 語言中,類的加載是動(dòng)態(tài)的,它并不會(huì)一次性將所有類全部加載后再運(yùn)行,而是保證程序運(yùn)行的基礎(chǔ)類(例如基類)完全加載到 JVM 中,至于其他類,則在需要的時(shí)候才加載。

類加載的主要步驟:

? 裝載。根據(jù)查找路徑找到相應(yīng)的 class 文件,然后導(dǎo)入。

? 鏈接。鏈接又可分為 3 個(gè)小步:

? 檢查,檢查待加載的 class 文件的正確性。

? 準(zhǔn)備,給類中的靜態(tài)變量分配存儲(chǔ)空間。

? 解析,將符號(hào)引用轉(zhuǎn)換為直接引用(這一步可選)

? 初始化。對靜態(tài)變量和靜態(tài)代碼塊執(zhí)行初始化工作。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容