1. 程式人生 > >Java基礎總結(二)----集合、多執行緒、io、虛擬機器等

Java基礎總結(二)----集合、多執行緒、io、虛擬機器等

Java集合

java集合框架的結構

List介面

List介面通常表示一個列表(陣列、佇列、連結串列、棧等),其中的元素可以重複,常用實現類為ArrayList和LinkedList,另外還有不常用的Vector。另外,LinkedList還是實現了Queue介面,因此也可以作為佇列使用。

Set介面

Set介面通常表示一個集合,其中的元素不允許重複(通過hashcode和equals函式保證),常用實現類有HashSet和TreeSet,HashSet是通過Map中的HashMap實現的,而TreeSet是通過Map中的TreeMap實現的。另外,TreeSet還實現了SortedSet介面,因此是有序的集合(集合中的元素要實現Comparable介面,並覆寫Compartor函式才行)。 我們看到,抽象類AbstractCollection、AbstractList和AbstractSet分別實現了Collection、List和Set介面,這就是在Java集合框架中用的很多的介面卡設計模式(這應該是模板模式吧?求確認

,或者說只是繼承而已。怎麼能扯到介面卡),用這些抽象類去實現介面,在抽象類中實現介面中的若干或全部方法,這樣下面的一些類只需直接繼承該抽象類,並實現自己需要的方法即可,而不用實現介面中的全部抽象方法。

Map介面

 Map是一個對映介面,其中的每個元素都是一個key-value鍵值對,同樣抽象類AbstractMap通過介面卡模式實現了Map介面中的大部分函式,TreeMap、HashMap、WeakHashMap等實現類都通過繼承AbstractMap來實現,另外,不常用的HashTable直接實現了Map介面,它和Vector都是JDK1.0就引入的集合類。

Iterator迭代器

Iterator是遍歷集合的迭代器(不能遍歷Map,只用來遍歷Collection),Collection的實現類都實現了iterator()函式,它返回一個Iterator物件,用來遍歷集合,ListIterator則專門用來遍歷List。而Enumeration則是JDK1.0時引入的,作用與Iterator相同,但它的功能比Iterator要少,它只能在Hashtable、Vector和Stack中使用。

哪些是執行緒安全,哪些不安全

  • 在集合框架中,有些類是執行緒安全的,這些都是jdk1.1中的出現的。在jdk1.2之後,就出現許許多多非執行緒安全的類。 下面是這些執行緒安全的同步的類:
  • vector:就比arraylist多了個同步化機制(執行緒安全),因為效率較低,現在已經不太建議使用。在web應用中,特別是前臺頁面,往往效率(頁面響應速度)是優先考慮的。
  • statck:堆疊類,先進後出。
  • hashtable:就比hashmap多了個執行緒安全。加鎖時機在方法上,不建議使用。可以考慮用CurrentHashMap替代。
  • enumeration:列舉,相當於迭代器

hashmap

  • hashmap能否用null作為鍵或值
    HashMap允許key和value為null,而Hashtable不允許。

  • hashmap原始碼

    1. 原始碼閱讀:TODO:請推薦一篇好的分析文章。
    2. 關鍵點:
    3. 初始容量和載入因子;
    4. 陣列+連結串列,拉鍊法;
    5. resize():resize函式中新建一個散列表陣列,容量為舊錶的2倍,接著需要把舊錶的鍵值對遷移到新表。
    6. 資料定位的方式總結:線性探測法(步長為1)、線性補償探測法(步長從1改為Q)、偽隨機探測法(步長從1改為隨機數);拉鍊法(重點);

快速失敗與安全失敗

快速失敗:當你在迭代一個集合的時候,如果有另一個執行緒正在修改你正在訪問的那個集合時,就會丟擲一個ConcurrentModification異常。在java.util包下的都是快速失敗。
安全失敗:你在迭代的時候會去底層集合做一個拷貝,所以你在修改上層集合的時候是不會受影響的,不會丟擲ConcurrentModification異常。在java.util.concurrent包下的全是安全失敗的。

arraylist,linkedlist底層實現區別,如何擴容

  • arraylist底層是陣列,linkedlist底層是雙向連結串列。
  • arraylist擴容到原來的1.5倍int newCapacity = oldCapacity + (oldCapacity >> 1);,然後把舊的資料copy到新的陣列中。linedList更簡單,直接在原來連結串列上加一個新的指標,指向上一個指標即可。特別注意和hashmap擴容對比,hashmap擴容為原來的2倍,並且在達到0.75即開始擴容,原因:為了增加命中的概率,如果太滿的話,可能導致陣列+連結串列的結構中,連結串列太長,導致查詢慢。

treemap,hashmap,linkedhashmap區別和特點,底層實現的區別

  • hashmap:陣列+連結串列;快速失敗,非執行緒同步,可以使用ConcurrentHashMap替代實現執行緒同步與安全失敗,或者使用Collections的synchronizedMap方法使HashMap具有同步的能力。特別注意:在jdk1.8中對於連結串列過長情況下,或轉換為紅黑樹提高查詢效率。
  • linkedhashmap:雜湊表與雙向連結串列;快速失敗,非執行緒同步;有序。
  • treemap:紅黑樹;快速失敗,非執行緒安全;可以自定義排序。
  • 如何解決不安全的集合的安全性問題
    • 使用java.util.concurrent相應的安全集合類進行替換使用
    • 使用Collections下的工具類進行同步處理。

多執行緒和併發包

執行緒生命週期

  • 新建(new Thread)當建立Thread類的一個例項(物件)時,此執行緒進入新建狀態(未被啟動)。例如:Thread t1=new Thread();
  • 就緒(runnable)執行緒已經被啟動,正在等待被分配給CPU時間片,也就是說此時執行緒正在就緒佇列中排隊等候得到CPU資源。例如:t1.start();
  • 執行(running)執行緒獲得CPU資源正在執行任務(run()方法),此時除非此執行緒自動放棄CPU資源或者有優先順序更高的執行緒進入,執行緒將一直執行到結束。
  • 死亡(dead)當執行緒執行完畢或被其它執行緒殺死,執行緒就進入死亡狀態,這時執行緒不可能再進入就緒狀態等待執行。自然終止:正常執行run()方法後終止異常終止:呼叫stop()方法讓一個執行緒終止執行
  • 堵塞(blocked)由於某種原因導致正在執行的執行緒讓出CPU並暫停自己的執行,即進入堵塞狀態。正在睡眠:用sleep(long t) 方法可使執行緒進入睡眠方式。一個睡眠著的執行緒在指定的時間過去可進入就緒狀態。正在等待:呼叫wait()方法。(呼叫motify()方法回到就緒狀態)被另一個執行緒所阻塞:呼叫suspend()方法。(呼叫resume()方法恢復)

java如何使用多執行緒(runnable和Thread),實現Runnable介面比繼承Thread類所具有的優勢

  1. 適合多個相同的程式程式碼的執行緒去處理同一個資源
  2. 可以避免java中的單繼承的限制
  3. 增加程式的健壯性,程式碼可以被多個執行緒共享,程式碼和資料獨立

stop,resume,suspend的缺點

  1. stop
    stop方法不推薦呼叫,官方說發是“可能發生不可預測的問題”。其實執行緒在呼叫stop方法後,會停止自己。執行緒停止的時候,會直接停止執行,並釋放自己正在使用的鎖資源。問題出在這裡,如果執行緒拿到鎖,只執行了幾步,還剩幾步沒有執行完,如果此時釋放鎖,其他執行緒重新接管,可能導致執行緒不安全的事情發生。並且這種問題幾乎無法Debug。

  2. suspend和resume方法
    這兩個方法必須要成對出現,否則非常容易發生死鎖,因為suspend方法並不會釋放鎖。如果不能保證之後會有人呼叫resume方法,會導致執行緒永遠掛起。其次,suspend和resume方法通常不一定是一個執行緒來順序執行。有可能一個執行緒來suspend另一個執行緒來resume,而如果沒有處理好執行緒間的呼叫順序,非常可能發生,resume發生在suspend之前,這樣又會導致執行緒suspend之後,永遠沒有人來resume他,發生“執行緒凍結”的場景。

終止執行緒有哪些方法

  1. 使用退出標誌,使執行緒正常退出,也就是當run方法完成後執行緒終止。
  2. 使用stop方法強行終止執行緒(這個方法不推薦使用,因為stop和suspend、resume一樣,也可能發生不可預料的結果)。
  3. 使用interrupt方法中斷執行緒。

守護執行緒

  • 在Java中有兩類執行緒:User Thread(使用者執行緒)、Daemon Thread(守護執行緒)
  • 用個比較通俗的比如,任何一個守護執行緒都是整個JVM中所有非守護執行緒的保姆:
    只要當前JVM例項中尚存在任何一個非守護執行緒沒有結束,守護執行緒就全部工作;只有當最後一個非守護執行緒結束時,守護執行緒隨著JVM一同結束工作。
  • Daemon的作用是為其他執行緒的執行提供便利服務,守護執行緒最典型的應用就是 GC (垃圾回收器),它就是一個很稱職的守護者。User和Daemon兩者幾乎沒有區別,唯一的不同之處就在於虛擬機器的離開:如果 User Thread已經全部退出執行了,只剩下Daemon Thread存在了,虛擬機器也就退出了。 因為沒有了被守護者,Daemon也就沒有工作可做了,也就沒有繼續執行程式的必要了。
    (1) thread.setDaemon(true)必須在thread.start()之前設定,否則會跑出一個IllegalThreadStateException異常。你不能把正在執行的常規執行緒設定為守護執行緒。
    (2) 在Daemon執行緒中產生的新執行緒也是Daemon的。
    (3) 不要認為所有的應用都可以分配給Daemon來進行服務,比如讀寫操作或者計算邏輯。

wait,notify,notifyAll關鍵字

  • wait()、notify()、notifyAll()是三個定義在Object類裡的方法,可以用來控制執行緒的狀態。這三個方法最終呼叫的都是jvm級的native方法。隨著jvm執行平臺的不同可能有些許差異。如果物件呼叫了wait方法就會使持有該物件的執行緒把該物件的控制權交出去,然後處於等待狀態。如果物件呼叫了notify方法就會通知某個正在等待這個物件的控制權的執行緒可以繼續執行。如果物件呼叫了notifyAll方法就會通知所有等待這個物件控制權的執行緒繼續執行。最原始的執行緒間通過新的方式。

lock與condition,Java中的幾種不同鎖

  • volatile 只保證可見性,不保證原子性。可見性:總是保持資料的為最新的,虛擬機器不做任何優化處理。不會將該變數上的操作與其他記憶體操作一起重排序。volatile變數不會被快取在暫存器或者對其他處理器不可見的地方,因此在讀取volatile型別的變數時總會返回最新寫入的值。
  • synchronized 方法鎖、程式碼塊鎖;保證:原子性、可見性。
  • ReentrantLock 效果同synchronized,必須保證unlock。新增:鎖投票,定時鎖等候和中斷鎖等候【排它鎖】
  • CountDownLatch:共享鎖(讀鎖)
  • ReentrantReadWriteLock 多個Thread可以同時進行讀取操作,但是同一時刻只允許一個Thread進行寫入操作。【讀:共享鎖;寫:排它鎖】
  • Condition 執行緒間通訊,Condition可 以替代傳統的執行緒間通訊,用await()替換wait(),用signal()替換notify(),用signalAll()替換notifyAll()。

執行緒死鎖情景

  • 死鎖形成的原因:

    1. 系統資源不足
    2. 程序(執行緒)推進的順序不恰當;
    3. 資源分配不當
  • 鎖形成的條件:

    1. 互斥條件:所謂互斥就是程序在某一時間內獨佔資源。
    2. 請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放。
    3. 不剝奪條件:程序已獲得資源,在末使用完之前,不能強行剝奪。
  • 迴圈等待條件:

    1. 若干程序之間形成一種頭尾相接的迴圈等待資源關係。
  • 從程式設計經驗上來講,形成死鎖的一般原因有以下幾種:

    1. 個人使用鎖的經驗差異。
    2. 程式模組使用鎖的差異。
    3. 工程程式碼版本之間的差異。
    4. 工程程式碼分支之間的差異。
    5. 修改程式碼和重構程式碼帶來的差異。
  • 死鎖的代價是非常大的,有時候很難檢測排查,因此需要在程式設計過程中儘可能的避免發生死鎖。程式設計中為了避免死鎖應該遵循如下策略:

    1. 在編寫多執行緒程式之前,首先編寫正確的程式,然後再移植到多執行緒。
    2. 時刻檢查自己寫的程式有沒有在跳出時忘記釋放鎖。
    3. 如果自己的模組可能重複使用一個鎖,建議使用巢狀鎖。 對於某些鎖程式碼,不要臨時重新編寫,建議使用庫裡面的鎖,或者自己曾經編寫的鎖。 如果某項業務需要獲取多個鎖,必須保證鎖的按某種順序獲取,否則必定死鎖。
    4. 寫簡單的測試用例,驗證有沒有死鎖。
    5. 編寫驗證死鎖的程式,從源頭避免死鎖。

消費者生產者模型

volatile關鍵字,是否保證原子性,優缺點

volatile 只保證可見性,不保證原子性!對volatile變數所有的寫操作都能立刻被其他執行緒得知。但是這並不代表基於volatile變數的運算在併發下是安全的,因為volatile只能保證記憶體可見性,卻沒有保證對變數操作的原子性。

Java transient關鍵字

Java的serialization提供了一種持久化物件例項的機制。當持久化物件時,可能有一個特殊的物件資料成員,我們不想用serialization機制來儲存它。為了在一個特定物件的一個域上關閉serialization,可以在這個域前加上關鍵字transient。transient是Java語言的關鍵字,用來表示一個域不是該物件序列化的一部分。當一個物件被序列化的時候,transient型變數的值不包括在序列化的表示中,然而非transient型的變數是被包括進去的。注意static變數也是可以序列化的

ThreadLocal的特點和使用

  • ThreadLocal 和synchronized的策略剛好相反,ThreadLocal通過對操作的物件儲存一份副本,任何操作都會只是針對副本,所以不用加任何鎖就可以實現synchronized的效果,以空間換時間。
  • 有一個問題:一個執行緒修改了Thread包裝的物件,如何及時和原有物件資料保持同步?因為修改的是副本。還是說執行緒之間操作資料(主要指修改)互不影響?

SimpleDateFormat的安全性問題

java的併發容器包Concurrent。阻塞佇列,CopyOnWriteList等

  • Concurrent:Java 5 添加了一個新的包到 Java 平臺,java.util.concurrent 包。這個包包含有一系列能夠讓 Java 的併發程式設計變得更加簡單輕鬆的類。在這個包被新增以前,你需要自己去動手實現自己的相關工具類。
  • BlockingQueue:佇列中沒有資料的情況下,消費者端的所有執行緒都會被自動阻塞(掛起),直到有資料放入佇列;當佇列中填滿資料的情況下,生產者端的所有執行緒都會被自動阻塞(掛起),直到佇列中有空的位置,執行緒被自動喚醒。所謂的生產者-消費者模式就是藉助這個東西進行資料互動的。
  • CopyOnWriteList:讀寫分離,CopyOnWrite容器即寫時複製的容器。通俗的理解是當我們往一個容器新增元素的時候,不直接往當前容器新增,而是先將當前容器進行Copy,複製出一個新的容器,然後新的容器裡新增元素,新增完元素之後,再將原容器的引用指向新的容器。這樣做的好處是我們可以對CopyOnWrite容器進行併發的讀,而不需要加鎖,因為當前容器不會新增任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。缺點:記憶體佔用問題;資料一致性問題(只能保證最終一致,不能保證實時一致)。

ConcurrentHashMap的源理


* 左邊便是Hashtable的實現方式—鎖整個hash表;而右邊則是ConcurrentHashMap的實現方式—鎖桶(或段)。ConcurrentHashMap將hash表分為16個桶(預設值),諸如get,put,remove等常用操作只鎖當前需要用到的桶。試想,原來只能一個執行緒進入,現在卻能同時16個寫執行緒進入(寫執行緒才需要鎖定,而讀執行緒幾乎不受限制,之後會提到),併發性的提升是顯而易見的。
* 更令人驚訝的是ConcurrentHashMap的讀取併發,因為在讀取的大多數時候都沒有用到鎖定,所以讀取操作幾乎是完全的併發操作,而寫操作鎖定的粒度又非常細,比起之前又更加快速(這一點在桶更多時表現得更明顯些)。只有在求size等操作時才需要鎖定整個表。而在迭代時,ConcurrentHashMap使用了不同於傳統集合的快速失敗迭代器(見之前的文章《JAVA API備忘—集合》)的另一種迭代方式,我們稱為弱一致迭代器。在這種迭代方式中,當iterator被建立後集合再發生改變就不再是丟擲ConcurrentModificationException,取而代之的是在改變時new新的資料從而不影響原有的資料,iterator完成後再將頭指標替換為新的資料,這樣iterator執行緒可以使用原來老的資料,而寫執行緒也可以併發的完成改變,更重要的,這保證了多個執行緒併發執行的連續性和擴充套件性,是效能提升的關鍵。
* 何為弱一致迭代器我知道了。關鍵怎麼實現?網上沒有找到好的資料,求解答
* see:http://blog.csdn.net/liuzhengkang/article/details/2916620

java的執行緒池原理和自帶的四大執行緒池

  • Java通過Executors提供四種執行緒池,分別為:
    • newCachedThreadPool建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
    • newFixedThreadPool 建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
    • newScheduledThreadPool 建立一個定長執行緒池,支援定時及週期性任務執行。
    • newSingleThreadExecutor 建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。

Executor框架

執行緒間通訊【重點】

  • 當執行緒呼叫了wait()方法時,它會釋放掉物件的鎖。另一個會導致執行緒暫停的方法:Thread.sleep(),它會導致執行緒睡眠指定的毫秒數,但執行緒在睡眠的過程中是不會釋放掉物件的鎖的。
  • 多執行緒間通訊方式:
    1. 共享變數
    2. wait/notify機制
    3. Lock/Condition機制
    4. 管道
  • 程序與程序間通訊
    1. 管道(Pipe):管道可用於具有親緣關係程序間的通訊,允許一個程序和另一個與它有共同祖先的程序之間進行通訊。
    2. 命名管道(named pipe):命名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關 系 程序間的通訊。命名管道在檔案系統中有對應的檔名。命名管道通過命令mkfifo或系統呼叫mkfifo來建立。
    3. 訊號(Signal):訊號是比較複雜的通訊方式,用於通知接受程序有某種事件發生,除了用於程序間通訊外,程序還可以傳送 訊號給程序本身;Linux除了支援Unix早期訊號語義函式sigal外,還支援語義符合Posix.1標準的訊號函式sigaction(實際上,該函式是基於BSD的,BSD為了實現可靠訊號機制,又能夠統一對外介面,用sigaction函式重新實現了signal函式)。
    4. 訊息(Message)佇列:訊息佇列是訊息的連結表,包括Posix訊息佇列system V訊息佇列。有足夠許可權的程序可以向佇列中新增訊息,被賦予讀許可權的程序則可以讀走佇列中的訊息。訊息佇列克服了訊號承載資訊量少,管道只能承載無格式位元組流以及緩衝區大小受限等缺
    5. 共享記憶體:使得多個程序可以訪問同一塊記憶體空間,是最快的可用IPC形式。是針對其他通訊機制執行效率較低而設計的。往往與其它通訊機制,如訊號量結合使用,來達到程序間的同步及互斥。
    6. 記憶體對映(mapped memory):記憶體對映允許任何多個程序間通訊,每一個使用該機制的程序通過把一個共享的檔案對映到自己的程序地址空間來實現它。
    7. 訊號量(semaphore):主要作為程序間以及同一程序不同執行緒之間的同步手段。
    8. 套介面(Socket):更為一般的程序間通訊機制,可用於不同機器之間的程序間通訊。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支援套接字。

IO

繼承關係圖


* 待補充

Java虛擬機器

強烈建議閱讀周志明的《深入理解Java虛擬機器》

強軟弱虛四種引用

  • 強引用:我們平時使用的沒有經過特殊處理的例項都是強引用,特點:只要被引用,就不會被gc回收,使用不當就會導致記憶體溢位或洩露。
  • 軟引用:只有在記憶體不足時,gc才會回收這一部分引用。用於有用但不是必須的物件。軟引用可用來實現記憶體敏感的快取記憶體。用途:可以對一些消耗記憶體的物件比如:圖片資源、handler進行包裝。
  • 弱引用:按照深入Java虛擬機器作者的說法,在gc進行下一輪迴收時,就會回收該部分引用,不管用沒有用,不管記憶體是否夠用。用於非必須的物件。用途:TODO:求完善
  • 虛引用:按照深入Java虛擬機器作者的說法,無法通過虛引用獲取一個物件的例項。唯一的目的是在這個物件被回收的時候收到一個系統通知。用途:求完善

java記憶體分割槽

  1. 程式計數器,簡稱pc,執行緒獨享。
  2. 虛擬機器棧,執行緒獨享。
  3. 本地方法棧,執行緒獨享。
  4. 方法區(包括執行時常量池),執行緒共享。
  5. 堆,執行緒共享。
  6. 直接記憶體,這一塊是直接操作作業系統的記憶體空間,比如NIO,直接就是操作的這塊局域,減少虛擬機器和作業系統的互動,目的是為了提高效率。

java的記憶體分割槽在什麼情況下記憶體溢位

主要分為三塊溢位,
第一塊:虛擬機器棧和本地方法棧,當棧呼叫層次過多和和擴充套件時無法申請到足夠的空間,會出現StackOvewflow的異常和OutOfMemoryError:java.lang.StackOverflowErrorjava.lang.OutOfMemoryError
第二塊:方法區的溢位,該區域也稱為永久區。在jdk1.7中去除了永久區這一概念,因此不會再出現在該區域的溢位情況。PermGen space
第三塊:堆溢位:Java heap space,這是重要的回收區域。
* 這一塊局域分為:新生代(Eden+fromSurvivor+toSurvivor,8:1:1)和老年代,新生代大小:老年代大小:1:2。
* 新生代發生MinorGC,老年代發生FullGC/MajorGC。
* 當新生的物件(判定非大物件(需要連續記憶體空間))會直接放到新生代中,該區域會頻繁發生minorGc,回收不再使用的物件。發生minorGc的方式:將新生代區域內的Eden以及fromSurvivor的有效物件移動到toSurvivor中,全部清空新生代+fromSurvivor。toSurvivor不夠用,會使用一個類似銀行中借款擔保人,有一個擔保區(老年代),把資料存放到擔保區。
* 當新生的物件為大物件時,會直接放到老年區。還有一種情況,在新生代中每次發生gc的時候,對於沒有回收的物件會進行計數加一,當超過一定閥值15,也會移動到老年區。在老年區會發生fullGc。

java垃圾回收機制,垃圾收集演算法特點及工作在哪一代,分代收集策略,如何判斷一個物件該被回收了,java物件實現如何自救,java的垃圾收集器,記憶體分配與回收及分配擔保

  • 判斷物件是否該回收的演算法:
    • 引用計數法:主流虛擬機器不會使用,難以解決物件相互引用問題。
    • 可達性分析演算法:主流虛擬機器都在使用。
  • GC回收演算法:

    • 標記-清除演算法:一般不使用,效率不高;會產生大量記憶體碎片。
    • 複製演算法:新生代就是採用這種演算法
    • 標記-整理演算法(也稱標記-壓縮演算法):老年代採用這種演算法
    • 分代收集演算法:新生代+老年代。

    • 空間擔保:在新生代發生MinorGC之前會檢查老年代最大的可用連續空間是否大於新生代所有存活物件—>檢查是否允許擔保失敗—>不允許—->FullGC—–>允許—>根據歷史記錄評估是否夠用—-不夠用,FullGC—->夠用,擔保失敗,FullGC。

    • 求推薦一篇講這塊講的好的文章。

java的類載入機制,類載入的5個步驟,類載入器

  • Java類的載入採用雙親委派模型,具體闡述:最底層是ApplicationClassLoader,負責載入jre中的核心jar包;下層是ExtClassLoader,主要負責載入一些支援包;下層是使用者層的ClassLoader以及使用者自定義的一些ClassLoader,負責載入使用者層的Class檔案。當新載入一個Class檔案時,會從使用者層一直向上層尋找,如果最上層不存在,依次去下層尋找,如果在最下層找不到該Class檔案,就會丟擲NoClassFindException的異常。這樣設計的好處是:如果使用者定義一個和系統一致的類(比如 java.lang.Object),保證只加載系統的類,而不是去載入使用者的類,不然就全亂了。。
  • 延伸一點:後期設計有違背雙親委派模型的,叫什麼GSgi的組織,但是人家很好的解決了這一問題。
  • 五個步驟:
    • 載入:獲取二進位制流、將靜態儲存結構轉化為方法區的執行時資料結構、生成類物件。
    • 驗證:驗證檔案格式是否符合規範、進行語音分析,看是否滿足要求、位元組碼驗證、符號引用驗證。
    • 準備:正式分配記憶體設定初始值等。
    • 解析:將虛擬機器常量池的符號引用轉化為直接應用的過程、類解析、欄位解析等
    • 初始化:真正執行Java程式程式碼。

JVM結構、GC工作機制詳解