1. 程式人生 > >面試總結------Java記憶體管理與多執行緒

面試總結------Java記憶體管理與多執行緒

Java記憶體管理與多執行緒

1. 什麼是執行緒?什麼是程序?同一程序下的執行緒共享

執行緒:程式在執行過程中,能夠執行程式程式碼的一個執行單元,一個執行緒可以建立和撤銷另一個執行緒;同一個程序中的多個執行緒之間可以併發執行。在Java語言中有4種狀態:執行、就緒、掛起、結束。

程序:指一段正在執行的程式。執行緒有時也被稱為輕量級程序,它是程式執行的最小單元,一個程序可以擁有多個執行緒,各個執行緒之間共享程式的記憶體空間及一些程序級的資源,但是各個執行緒擁有自己的棧空間。

程序的作用和定義:程序是為了提高CPU的執行效率,減少因為程式等待帶來的CPU空轉以及其他計算機軟硬體資源的浪費而提出來的。程序是為了完成使用者任務所需要的程式的一次執行過程和為其分配資源的一個基本單位,是一個具有獨立功能的程式段對某個資料集的一次執行活動。

執行緒和程序的區別:
A. 地址空間和其它資源:程序間相互獨立,同一程序的各執行緒間共享。某程序內的執行緒在其它程序不可見。
B. 通訊:程序間通訊IPC,執行緒間可以直接讀寫程序資料段(如全域性變數)來進行通訊——需要程序同步和互斥手段的輔助,以保證資料的一致性。
C. 排程和切換:執行緒上下文切換比程序上下文切換要快得多。
D. 在多執行緒OS中,程序不是一個可執行的實體。
E. 執行緒是程序的一部分,所以執行緒有的時候被稱為是輕權程序或者輕量級程序。

執行緒與程序資源分配:
執行緒共享的內容包括: 程序程式碼段、程序的公有資料(利用這些共享的資料,執行緒很容易的實現相互之間的通訊)、程序開啟的檔案描述符、訊號的處理器、程序的當前目錄和、程序使用者ID、程序組ID。
執行緒獨有的內容包括: 執行緒ID 、暫存器組的值 、執行緒的堆疊 、錯誤返回碼 、執行緒的訊號遮蔽碼 。

2. Java的記憶體機制

Java 把記憶體劃分成兩種:一種是棧記憶體,另一種是堆記憶體。
堆和棧相同點:
棧(stack)與堆(heap)都是Java用來在Ram中存放資料的地方。與C++不同,Java自動管理棧和堆,程式設計師不能直接地設定棧或堆。
堆和棧區別:
棧的優勢是,存取速度比堆要快,僅次於直接位於CPU中的暫存器。但缺點是,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性。另外,棧資料可以共享。堆的優勢是可以動態地分配記憶體大小,生存期也不必事先告訴編譯器,Java的垃圾收集器會自動收走這些不再使用的資料。但缺點是,由於要在執行時動態分配記憶體,存取速度較慢。

在函式中定義的一些基本型別的變數和物件的引用變數都是在函式的棧記憶體中分配,當在一段程式碼塊定義一個變數時,Java 就在棧中為這個變數分配記憶體空間,當超過變數的作用域後,Java 會自動釋放掉為該變數分配的記憶體空間,該記憶體空間可以立即被另作它用。

堆記憶體用來存放由 new 建立的物件和陣列,在堆中分配的記憶體,由 Java 虛擬機器的自動垃圾回收器來管理。在堆中產生了一個數組或者物件之後,還可以在棧中定義一個特殊的變數,讓棧中的這個變數的取值等於陣列或物件在堆記憶體中的 首地址,棧中的這個變數就成了陣列或物件的引用變數,以後就可以在程式中使用棧中的引用變數來訪問堆中的陣列或者物件,引用變數就相當於是為陣列或者物件 起的一個名稱。引用變數是普通的變數,定義時在棧中分配,引用變數在程式執行到其作用域之外後被釋放。而陣列和物件本身在堆中分配,即使程式執行到使用 new 產生陣列或者物件的語句所在的程式碼塊之外,陣列和物件本身佔據的記憶體不會被釋放,陣列和物件在沒有引用變數指向它的時候,才變為垃圾,不能在被使用,但仍 然佔據記憶體空間不放,在隨後的一個不確定的時間被垃圾回收器收走(釋放掉)。

3. java中變數在記憶體中的分配

(1)類變數(static修飾的變數):在程式載入時系統就為它在堆中開闢了記憶體,堆中的記憶體地址存放於棧以便於高速訪問。靜態變數的生命週期–一直持續到整個”系統”關閉

(2)例項變數:當你使用java關鍵字new的時候,系統在堆中開闢並不一定是連續的空間分配給變數(比如說類例項),然後根據零散的堆記憶體地址,通過雜湊演算法換算為一長串數字以表徵這個變數在堆中的”物理位置”。 例項變數的生命週期–當例項變數的引用丟失後,將被GC(垃圾回收器)列入可回收“名單”中,但並不是馬上就釋放堆中記憶體

(3)區域性變數:區域性變數,由宣告在某方法,或某程式碼段裡(比如for迴圈),執行到它的時候在棧中開闢記憶體,當局部變數一但脫離作用域,記憶體立即釋放

4. JVM記憶體分配
JVM 將記憶體區域劃分為: Method Are(Non-Heap)(方法區),Heap(堆),Program Counter Register(程式計數器),VM Stack(虛擬機器棧,也有翻譯成JAVA 方法棧的),Native Method Stack(本地方法棧)。
方法區和堆是執行緒共享的,虛擬機器棧,程式計數器和本地方法棧是非執行緒共享的。
一般性的 Java 程式的工作過程:一個 Java 源程式檔案,會被編譯為位元組碼檔案(以 class 為副檔名),每個java程式都需要執行在自己的JVM上,然後告知 JVM 程式的執行入口,再被 JVM 通過位元組碼直譯器載入執行。那麼程式開始執行後,都是如何涉及到各記憶體區域的呢?
概括地說來,JVM初始執行的時候都會分配好方法區和堆,而JVM每遇到一個執行緒,就為其分配一個程式計數器,虛擬機器棧和本地方法棧,當執行緒終止時,三者(虛擬機器棧,本地方法棧和程式計數器)所佔用的記憶體空間也會被釋放掉。這也是為什麼我把記憶體區域分為執行緒共享和非執行緒共享的原因,非執行緒共享的那三個區域的生命週期與所屬執行緒相同,而執行緒共享的區域與JAVA程式執行的生命週期相同,所以這也是系統垃圾回收的場所只發生線上程共享的區域(實際上對大部分虛擬機器來說知發生在Heap上)的原因。

5. Java實現多執行緒,建立並啟動執行緒的過程

(1)定義執行緒:
1)擴充套件java.lang.Thread類。 2)實現java.lang.Runnable介面。

(2)例項化執行緒:
1)如果是擴充套件java.lang.Thread類的執行緒,則直接new即可。
2)如果是實現了java.lang.Runnable介面的類,則用Thread的構造方法:
Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

(3)啟動執行緒:線上程的Thread物件上呼叫start()方法,而不是run()或者別的方法。

通過繼承Thread類建立執行緒:
public class ThreadDemo02 extends Thread{
public void run(){
System.out.println(“執行緒啟動!”);
}
public static void main(String[] args) {
ThreadDemo02 thread = new ThreadDemo02();
thread.start();
}
}
通過實現Runnable介面建立執行緒:
public class ThreadDemo03 implements Runnable{
public void run() {
System.out.println(“執行緒啟動02”);
}
public static void main(String[] args) {
Thread thread01 = new Thread(new ThreadDemo03());
thread01.start();
}
}
start()方法和run()方法的區別:
(1)啟動一個執行緒是start()方法,啟動執行緒之後start()方法會去呼叫run方法內容。
(2)start是建立並啟動一個執行緒,而run是要執行執行緒中的程式碼。
(3)run()方法 : 在本執行緒內呼叫該Runnable物件的run()方法,可以重複多次呼叫;
start()方法 : 啟動一個執行緒,呼叫該Runnable物件的run()方法,不能多次啟動一個執行緒;

7. 在JAVA中,有六個不同的地方可以儲存資料

(1) 暫存器(register):這是最快的儲存區,因為它位於不同於其他儲存區的地方——處理器內部。但是暫存器的數量極其有限,所以暫存器由編譯器根據需求進行分配。你不能直接控制,也不能在程式中感覺到暫存器存在的任何跡象。
(2) 堆疊(stack):位於通用RAM中,但通過它的“堆疊指標”可以從處理器哪裡獲得支援。堆疊指標若向下移動,則分配新的記憶體;若向上移動,則釋放那些記憶體。這是一種快速有效的分配儲存方法,僅次於暫存器。建立程式時候,JAVA編譯器必須知道儲存在堆疊內所有資料的確切大小和生命週期,因為它必須生成相應的程式碼,以便上下移動堆疊指標。這一約束限制了程式的靈活性,所以雖然某些JAVA資料儲存在堆疊中——特別是物件引用,但是JAVA物件不儲存其中。
(3)堆(heap):一種通用性的記憶體池(也存在於RAM中),用於存放所以的JAVA物件。堆不同於堆疊的好處是:編譯器不需要知道要從堆裡分配多少儲存區域,也不必知道儲存的資料在堆裡存活多長時間。因此,在堆裡分配儲存有很大的靈活性。當你需要建立一個物件的時候,只需要new寫一行簡單的程式碼,當執行這行程式碼時,會自動在堆裡進行儲存分配。當然,為這種靈活性必須要付出相應的程式碼。用堆進行儲存分配比用堆疊進行儲存儲存需要更多的時間。
(4)靜態儲存(static storage):這裡的“靜態”是指“在固定的位置”。靜態儲存裡存放程式執行時一直存在的資料。你可用關鍵字static來標識一個物件的特定元素是靜態的,但JAVA物件本身從來不會存放在靜態儲存空間裡。
(5)常量儲存(constant storage):常量值通常直接存放在程式程式碼內部,這樣做是安全的,因為它們永遠不會被改變。有時,在嵌入式系統中,常量本身會和其他部分分割離開,所以在這種情況下,可以選擇將其放在ROM中
(6)非RAM儲存:如果資料完全存活於程式之外,那麼它可以不受程式的任何控制,在程式沒有執行時也可以存在。

8. java concurrent包下的4個類,選出差別最大的一個 (C)
A. Semaphore B. ReentrantLock C. Future D. CountDownLatch
別的類都處理執行緒間的關係,處理併發機制,但Future 只用於獲取執行緒結果。
Future是個介面,表示獲取一個正在指定的執行緒的結果。對該執行緒有取消和判斷是否執行完畢等操作。
CountDownLatch 是個鎖存器,他表示我要佔用給定的多少個執行緒且我優先執行,我執行完之前其他要使用該資源的都要等待。
Semaphore,就像是一個許可證發放者,也像一個數據庫連線池。證就這麼多,如果池中的證沒換回來,其他人就不能用。
ReentrantLock 和 synchronized一樣,用於鎖定執行緒。