本菜雞參加的大部分java後端面的面試題都有考到。給本身作個總結!java
本文介紹內存區域和各類內存溢出狀況。程序員
Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域。面試
具體分爲如下幾個部分:數據庫
程序計數器(Program Counter Register)是一塊較小的內存空間,它能夠看做是當前線程所執行的字節碼的行號指示器。後端
因爲Java虛擬機的多線程是經過線程輪流切換、分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器(對於多核處理器來講是一個內核)都只會執行一條線程中的指令。數組
此內存區域是惟一一個在《Java虛擬機規範》中沒有規定任何OutOfMemoryError狀況的區域。緩存
虛擬機棧描述的是Java方法執行的線程內存模型:服務器
局部變量表內容:編譯期可知的各類Java虛擬機的:網絡
局部變量表中的存儲空間以局部變量槽(Slot)來表示,其中64位長度的long和double類型的數據會佔用兩個變量槽。(由虛擬機決定大小)多線程
內存分配時機:局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法須要在棧幀中分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表的大小。
本地方法棧與虛擬機棧所發揮的做用是很是類似的。其區別是:
StackOverflowError和OutOfMemoryError異常:本地方法棧也會在棧深度溢出或者棧擴展失敗時分別拋出StackOverflowError和OutOfMemoryError異常。
Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。
根據《Java虛擬機規範》的規定,Java堆能夠處於物理上不連續的內存空間中,但在邏輯上它應該被視爲連續的。
但對於大對象(典型的如數組對象),多數虛擬機實現出於實現簡單、存儲高效的考慮,極可能會要求連續的內存空間。
Java堆既能夠被實現成固定大小的,也能夠是可擴展的。
OutOfMemoryError異常:若是在Java堆中沒有內存完成實例分配,而且堆也沒法再擴展時,Java虛擬機將會拋出。
方法區是各個線程共享的內存區域。
在JDK 8之前,Java程序員都習慣在HotSpot虛擬機上開發、部署程序,因此更願意把方法區稱呼爲永久代。
本質上這二者並非等價的,由於僅僅是當時的HotSpot虛擬機設計團隊選擇把收集器的分代設計擴展至方法區,或者說使用永久代來實現方法區而已,這樣使得HotSpot的垃圾收集器可以像管理Java堆同樣管理這部份內存,省去專門爲方法區編寫內存管理代碼的工做。
在JDK 8中,徹底廢棄了永久代的概念,改用與JRockit、J9同樣在本地內存中實現的元空間來代替,把JDK 7中永久代還剩餘的內容(主要是類型信息)所有移到元空間中。
JDK 7的HotSpot,已經把本來放在永久代的字符串常量池、靜態變量等移出。
運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池表(Constant Pool Table)。
OutOfMemoryError異常:在方法區沒法知足新的內存分配需求時
直接內存(Direct Memory)並非虛擬機運行時數據區的一部分,也不是《Java虛擬機規範》中定義的內存區域。
在JDK 1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可使用Native函數庫直接分配堆外內存,而後經過一個存儲在Java堆裏面的DirectByteBuffer對象做爲這塊內存的引用進行操做。這樣能在一些場景中顯著提升性能,由於避免了在Java堆和Native堆中來回複製數據。
OutOfMemoryError異常:通常服務器管理員配置虛擬機參數時,會根據實際內存去設置-Xmx等參數信息,但常常忽略掉直接內存,使得各個內存區域總和大於物理內存限制(包括物理的和操做系統級的限制),從而致使動態擴展時出現OutOfMemoryError異常。
當HashMap、LinkedList等等這些容器爲靜態的。
總結:長生命週期的對象持有短生命週期對象的引用,儘管短生命週期的對象再也不使用,可是由於長生命週期對象持有它的引用而致使不能被回收。
例如:數據庫鏈接、網絡鏈接和IO鏈接等。
在對數據庫進行操做的過程當中,首先須要創建與數據庫的鏈接。當再也不使用時,須要調用close方法來釋放與數據庫的鏈接。
一個變量的定義的做用範圍大於其使用範圍,頗有可能會形成內存泄漏。
若是一個外部類的實例對象的方法返回了一個內部類的實例對象,這個內部類對象被長期引用了,即便那個外部類實例對象再也不被使用,但因爲內部類持有外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會形成內存泄露。
當一個對象被存儲進HashSet集合中之後,就不能修改這個對象中的那些參與計算哈希值的字段了。
建議使用final類型的類(String、Integer)。
這種狀況看下面程序。
import java.util.Arrays;
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
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)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
複製代碼
內存泄漏的另外一個常見來源是緩存,一旦把對象引用放入到緩存中就很容易遺忘。
此種Map的特色是,當除了自身有對key的引用外,此key沒有其餘引用那麼此map會自動丟棄此值。
內存泄漏常見來源是監聽器和其餘回調,若是客戶端在你實現的API中註冊回調,卻沒有顯示的取消,那麼就會積聚。
本篇文章參考自:《深刻理解Java虛擬機(第3版)》和部分其餘博客。