Java 內存分配
• 寄存器:程序計數器,是線程私有的,就是一個指針,指向方法區中的方法字節碼。
• 靜態域:static 定義的靜態成員。
• 常量池:編譯時被肯定並保存在 .class 文件中的(final)
常量值和一些文本修飾的符號引用(類和接口的全限定名,字段的名稱和描述符,方法和名稱和描述符)。
• 非 RAM 存儲:硬盤等永久存儲空間。
• 堆內存:new 建立的對象和數組,由 Java 虛擬機自動垃圾回收器管理,存取速度慢。
• 棧內存:基本類型的變量和對象的引用變量(堆內存空間的訪問地址),速度快,能夠共享,可是大小與生存期必須肯定,缺少靈活性。
串行(serial)收集器和吞吐量(throughput)收集器的區別是什麼?
吞吐量收集器使用並行版本的新生代垃圾收集器,它用於中等規模和大規模數據的應用程序。
而串行收集器對大多數的小應用(在現代處理器上須要大概 100M 左右的內存)就足夠了。
在 Java 中,對象何時能夠被垃圾回收?
當對象對當前使用這個對象的應用程序變得不可觸及的時候,這個對象就能夠被回收了。
GC 是什麼? 爲何要有 GC?
GC 是垃圾收集的意思(GabageCollection),內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會致使程序或
系統的不穩定甚至崩潰,Java 提供的 GC 功能能夠自動監測對象是否超過做用域從而達到自動回收內存的目的,Java 語言沒有提
供釋放已分配內存的顯示操做方法。
簡述 Java 垃圾回收機制。
在 Java 中,程序員是不須要顯示的去釋放一個對象的內存的,而是由虛擬機自行執行。在 JVM 中,有一個垃圾回收線程,它是低優先級的,在正常狀況下是不會執行的,只有在虛擬機空閒或者當前堆內存不足時,纔會觸發執行,掃面那些沒有被任何引用的對象,並將它們添加到要回收的集合中,進行回收。
如何判斷一個對象是否存活?(或者 GC 對象的斷定方法)
判斷一個對象是否存活有兩種方法:
引用計數法
所謂引用計數法就是給每個對象設置一個引用計數器,每當有一個地方引用這個對象時,就將計數器加一,引用失效時,計數器就
減一。當一個對象的引用計數器爲零時,說明此對象沒有被引用,也就是「死對象」,將會被垃圾回收.引用計數法有一個缺陷就是沒法解決循環引用問題,也就是說當對象 A 引用對象 B,對象 B 又引用者對象 A,那麼此時 A、B 對象的引用計數器都不爲零,也就形成沒法完成垃圾回收,因此主流的虛擬機都沒有采用這種算法。
可達性算法(引用鏈法)
該算法的思想是:從一個被稱爲 GC Roots 的對象開始向下搜索,若是一個對象到 GC Roots 沒有任何引用鏈相連時,則說明此對
象不可用。
在 Java 中能夠做爲 GC Roots 的對象有如下幾種:
• 虛擬機棧中引用的對象
• 方法區類靜態屬性引用的對象
• 方法區常量池引用的對象
• 本地方法棧 JNI 引用的對象
雖然這些算法能夠斷定一個對象是否能被回收,可是當知足上述條件時,一個對象比不必定會被回收。當一個對象不可達 GC Root
時,這個對象並不會立馬被回收,而是出於一個死緩的階段,若要被真正的回收須要經歷兩次標記.若是對象在可達性分析中沒有與 GC Root 的引用鏈,那麼此時就會被第一次標記而且進行一次篩選,篩選的條件是是否有必要執行finalize() 方法。當對象沒有覆蓋 finalize() 方法或者已被虛擬機調用過,那麼就認爲是不必的。 若是該對象有必要執行finalize() 方法,那麼這個對象將會放在一個稱爲 F-Queue 的對
隊列中,虛擬機會觸發一個 Finalize() 線程去執行,此線程是低優先級的,而且虛擬機不會承諾一直等待它運行完,這是由於若是finalize() 執行緩慢或者發生了死鎖,那麼就會形成 F-Queue 隊列一直等待,形成了內存回收系統的崩潰。GC 對處於 F-Queue 中的對象進行第二次被標記,這時,該對象將被移除」 即將回收」集合,等待回收。
垃圾回收的優勢和原理。並考慮 2 種回收機制。
Java 語言中一個顯著的特色就是引入了垃圾回收機制,使 C++ 程序員最頭疼的內存管理的問題迎刃而解,它使得 Java 程序員在編寫程序的時候再也不須要考慮內存管理。因爲有個垃圾回收機制,Java 中的對象再也不有「做用域」的概念,只有對象的引用纔有"做用域"。垃圾回收能夠有效的防止內存泄露,有效的使用可使用的內存。垃圾回收器一般是做爲一個單獨的低級別的線程運行,不可預知的狀況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清楚和回收,程序員不能實時的調用垃圾回收器對某個對象或全部對象進行垃圾回收。
回收機制有分代複製垃圾回收和標記垃圾回收,增量垃圾回收。
垃圾回收器的基本原理是什麼?垃圾回收器能夠立刻回收內存嗎?有什麼辦法主動通知虛擬機進行垃圾回收?
對於 GC 來講,當程序員建立對象時,GC 就開始監控這個對象的地址、大小以及使用狀況。一般,GC 採用有向圖的方式記錄和管理堆(heap)中的全部對象。經過這種方式肯定哪些對象是」可達的」,哪些對象是」不可達的」。當 GC 肯定一些對象爲「不可達」時,GC 就有責任回收這些內存空間。能夠。程序員能夠手動執行 System.gc(),通知 GC 運行,可是 Java 語言規範並不保證 GC 必定會執行。
System.gc() 和 Runtime.gc() 會作什麼事情?
這兩個方法用來提示 JVM 要進行垃圾回收。可是,當即開始仍是延遲進行垃圾回收是取決於 JVM 的。
Java 堆的結構是什麼樣子的?什麼是堆中的永久代(Perm Gen space)?
JVM 的堆是運行時數據區,全部類的實例和數組都是在堆上分配內存。它在 JVM 啓動的時候被建立。對象所佔的堆內存是由自動
內存管理系統也就是垃圾收集器回收。堆內存是由存活和死亡的對象組成的。存活的對象是應用能夠訪問的,不會被垃圾回收。死亡的對象是應用不可訪問尚且尚未被垃圾收集器回收掉的對象。一直到垃圾收集器把這些 對象回收掉以前,他們會一直佔據堆內存空間。
Java 中會存在內存泄漏嗎,請簡單描述。
所謂內存泄露就是指一個再也不被程序使用的對象或變量一直被佔據在內存中。Java 中有垃圾回收機制,它能夠保證一對象再也不被引用的時候,即對象變成了孤兒的時候,對象將自動被垃圾回收器從內存中清除掉。因爲 Java 使用有向圖的方式進行垃圾回收管理,能夠消除引用循環的問題,例若有兩個對象,相互引用,只要它們和根進程不可達的,那麼 GC 也是能夠回收它們的,
下面的代碼能夠看到這種狀況的內存回收:
import java.io.IOException;public class GarbageTest {java
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 中的內存泄露的狀況:
長生命週期的對象持有短生命週期對象的引用就極可能發生內存泄露,儘管短生命週期對象已經再也不須要,可是由於長生命週期對象持有它的引用而致使不能被回收,這就是 Java 中內存泄露的發生場景,通俗地說,就是程序員可能建立了一個對象,之後一直再也不使用這個對象,這個對象卻一直被引用,即這個對象無用可是卻沒法被垃圾回收器回收的,這就是 java 中可能出現內存泄露的狀況,例如,緩存系統,咱們加載了一個對象放在緩存中 (例如放在一個全局 map 對象中),而後一直再也不使用它,這個對象一直被緩存引用,但卻再也不被使用。檢查 Java 中的內存泄露,必定要讓程序將各類分支狀況都完整執行到程序結束,而後看某個對象是否被使用過,若是沒有,則才能
斷定這個對象屬於內存泄露。若是一個外部類的實例對象的方法返回了一個內部類的實例對象,這個內部類對象被長期引用了,即便那個外部類實例對象再也不被使用,但因爲內部類持久外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會形成內存泄露。
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 個元素,而後所有彈出來,雖然堆棧是空的,沒有咱們要的東西,可是這是個對象是沒法回收的,這個才符合了內存泄露的兩個條件:無用,沒法回收。可是就是存在這樣的東西也不必定會致使什麼樣的後果,若是這個
堆棧用的比較少,也就浪費了幾個 K 內存而已,反正咱們的內存都上 G 了,哪裏會有什麼影響,再說這個東西很快就會被回收的,
有什麼關係。下面看兩個例子。
public class Bad{
public static Stack s=Stack();
static{
s.push(new Object());
s.pop(); //這裏有一個對象發生內存泄露
s.push(new Object()); //上面的對象能夠被回收了,等因而自
愈了
} }
由於是 static,就一直存在到程序退出,可是咱們也能夠看到它有自愈功能,就是說若是你的 Stack 最多有 100 個對象,那麼最
多也就只有 100 個對象沒法被回收其實這個應該很容易理解,Stack 內部持有 100 個引用,最壞的狀況就是他們都是無用的,
由於咱們一旦放新的進取,之前的引用天然消失!內存泄露的另一種狀況:當一個對象被存儲進 HashSet 集合中之後,就不能修改這對象中的那些參與計算哈希值的字段了,不然,對象修改後的哈希值與最初存儲進 HashSet 集合中時的哈希值就不一樣了,在這種狀況下,即便在 contains 方法使用該對象的當前引用做爲的參數去 HashSet 集合中檢索對象,也將返回找不到對象的結果,這也會致使沒法從HashSet 集合中單獨刪除當前對象,形成內存泄露。
深拷貝和淺拷貝。
簡單來說就是複製、克隆。
Person p=new Person(「張三」);
淺拷貝就是對對象中的數據成員進行簡單賦值,若是存在動態成員或者指針就會報錯。深拷貝就是對對象中存在的動態成員或指針從新開闢內存空間。
finalize() 方法何時被調用?析構函數 (finalization) 的目的是什麼?
垃圾回收器(garbage colector)決定回收某對象時,就會運行該對象的 finalize() 方法 可是在 Java 中很不幸,若是內存老是充
足的,那麼垃圾回收可能永遠不會進行,也就是說 filalize() 可能永遠不被執行,顯然期望它作收尾工做是靠不住的。
那麼finalize() 到底是作什麼的呢?
它最主要的用途是回收特殊渠道申請的內存。Java 程序有垃圾回收器,因此通常狀況下內存問題不用程序員操心。但有一種 JNI(Java Native Interface)調用non-Java 程序(C 或 C++), finalize() 的工做就是回收這部分的內存。
若是對象的引用被置爲 null,垃圾收集器是否會當即釋放對象佔用的內存?
不會,在下一個垃圾回收週期中,這個對象將是可被回收的。
什麼是分佈式垃圾回收(DGC)?它是如何工做的?
DGC 叫作分佈式垃圾回收。RMI 使用 DGC 來作自動垃圾回收。由於 RMI 包含了跨虛擬機的遠程對象的引用,垃圾回收是很困難的。DGC 使用引用計數算法來給遠程對象提供自動內存管理。
簡述 Java 內存分配與回收策率以及 Minor GC 和 Major GC。
• 對象優先在堆的 Eden 區分配
• 大對象直接進入老年代
• 長期存活的對象將直接進入老年代
當 Eden 區沒有足夠的空間進行分配時,虛擬機會執行一次Minor GC。Minor GC 一般發生在新生代的 Eden 區,在這個區的對象生存期短,每每發生 Gc 的頻率較高,回收速度比較快;
Full GC/Major GC 發生在老年代,通常狀況下,觸發老年代 GC 的時候不會觸發 Minor GC,可是經過配置,能夠在 Full GC 之
前進行一次 Minor GC 這樣能夠加快老年代的回收速度。
JVM 的永久代中會發生垃圾回收麼?
垃圾回收不會發生在永久代,若是永久代滿了或者是超過了臨界值,
會觸發徹底垃圾回收(Full GC)。
注:Java 8 中已經移除了永久代,新加了一個叫作元數據區的
native 內存區。
Java 中垃圾收集的方法有哪些?
標記 - 清除:這是垃圾收集算法中最基礎的,根據名字就能夠知
道,它的思想就是標記哪些要被回收的對象,而後統一回收。這種
方法很簡單,可是會有兩個主要問題:
1.效率不高,標記和清除的效率都很低;
2.會產生大量不連續的內存碎片,致使之後程序在分配較大的
對象時,因爲沒有充足的連續內存而提早觸發一次 GC 動做。
複製算法:
爲了解決效率問題,複製算法將可用內存按容量劃分爲相等的兩部分,而後每次只使用其中的一塊,當一塊內存用完時,就將還存活的對象複製到第二塊內存上,而後一次性清楚完第一塊內存,再將第二塊上的對象複製到第一塊。可是這種方式,內存的代價過高,每次基本上都要浪費通常的內存。因而將該算法進行了改進,內存區域再也不是按照 1:1 去劃分,而是將內存劃分爲 8:1:1 三部分,較大那分內存交 Eden 區,其他是兩塊較小的內存區叫 Survior 區。每次都會優先使用 Eden 區,若 Eden 區滿,就將對象複製到第二塊內存區上,而後清除 Eden
區,若是此時存活的對象太多,以致於 Survivor 不夠時,會將這些對象經過分配擔保機制複製到老年代中。(java 堆又分爲新生代和老年代)
標記 - 整理:
該算法主要是爲了解決標記 - 清除,產生大量內存碎片的問題;當對象存活率較高時,也解決了複製算法的效率問題。它的不一樣之處就是在清除對象的時候現將可回收對象移動到一端,而後清除掉端邊界之外的對象,這樣就不會產生內存碎片了。
分代收集:
如今的虛擬機垃圾收集大多采用這種方式,它根據對象的生存週期,將堆分爲新生代和老年代。在新生代中,因爲對象生存期短,每次回收都會有大量對象死去,那麼這時就採用複製算法。老年代裏的對象存活率較高,沒有額外的空間進行分配擔保。
什麼是類加載器,類加載器有哪些?
實現經過類的權限定名獲取該類的二進制字節流的代碼塊叫作類加載器。
主要有一下四種類加載器:
• 啓動類加載器(Bootstrap ClassLoader)用來加載 Java 核心類庫,沒法被 Java 程序直接引用。
• 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。
• 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。通常來講,Java 應用的類都是由它來完成加載的。能夠經過ClassLoader.getSystemClassLoader() 來獲取它。
• 用戶自定義類加載器,經過繼承 java.lang.ClassLoader 類的方式實現。
類加載器雙親委派模型機制?
當一個類收到了類加載請求時,不會本身先去加載這個類,而是將其委派給父類,由父類去加載,若是此時父類不能加載,反饋給子類,由子類去完成類的加載。程序員