編譯機制
編譯主要是把 .Java文件轉換爲 .class 文件。其中轉換後的 .class 文件就包含了元數據,方法信息等一些信息。好比說元數據就包含了 Java 文件中聲明的常量,也就是咱們所說的常量池。java
泛型實現原理
Java泛型實現原理:類型擦除
Java的泛型是僞泛型。在編譯期間,全部的泛型信息都會被擦除掉。正確理解泛型概念的首要前提是理解類型擦出(type erasure)。
Java中的泛型基本上都是在編譯器這個層次來實現的。
在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會在編譯器在編譯的時候去掉。這個過程就稱爲類型擦除。
如在代碼中定義的List<object>和List<String>等類型,在編譯後都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來講是不可見的。Java編譯器會在編譯時儘量的發現可能出錯的地方,可是仍然沒法避免在運行時刻出現類型轉換異常的狀況。算法
編譯時期和運行時期類型檢查
Java中的許多對象(通常都是具備父子類關係的父類對象)在運行時都會出現兩種類型:編譯時類型和運行時類型,例如:Person person = new Student();
這行代碼將會生成一個person變量,該變量的編譯時類型是Person,運行時類型是Student。
Java的引用變量有兩個類型,一個是編譯時類型,一個是運行時類型,編譯時類型由聲明該變量時使用的類型決定,運行時類型由實際賦給該變量的對象決定。數據庫
多態實現原理
基於繼承實現的多態能夠總結以下:對於引用子類的父類類型,在處理該引用時,它適用於繼承該父類的全部子類,子類對象的不一樣,對方法的實現也就不一樣,執行相同動做產生的行爲也就不一樣。
繼承是經過重寫父類的同一方法的幾個不一樣子類來體現的,那麼就能夠是經過實現接口並覆蓋接口中同一方法的幾不一樣的類體現的。
在接口的多態中,指向接口的引用必須是指定這實現了該接口的一個類的實例程序,在運行時,根據對象引用的實際類型來執行對應的方法。
當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,可是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法,可是它仍然要根據繼承鏈中方法調用的優先級來確認方法,該優先級爲:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。bootstrap
A繼承B,加載順序api
若要加載類A,則先加載執行其父類B(Object)的靜態變量以及靜態語句塊(執行前後順序按排列的前後順序)。數組
而後再加載執行類A的靜態變量以及靜態語句塊。(而且一、2步驟只會執行1次)緩存
若需實例化類A,則先調用其父類B的構造函數,而且在調用其父類B的構造函數前,依次先調用父類B中的非靜態變量及非靜態語句塊.最後再調用父類B中的構造函數初始化。多線程
而後再依次調用類A中的非靜態變量及非靜態語句塊.最後調用A中的構造函數初始化。( 而且三、4步驟能夠重複執行)分佈式
而對於靜態方法和非靜態方法都是被動調用,即系統不會自動調用執行,因此用戶沒有調用時都不執行,主要區別在於靜態方法能夠直接用類名直接調用(實例化對象也能夠),而非靜態方法只能先實例化對象後才能調用。函數
serizeble有什麼用
序列化就是把一個對象保存到一個文件或數據庫字段中去,反序列化就是在適當的時候把這個文件再轉化成原來的對象使用。我想最主要的做用有:
在進程下次啓動時讀取上次保存的對象的信息
在不一樣的AppDomain或進程之間傳遞數據
在分佈式應用系統中傳遞數據
序列化
當一個父類實現序列化,子類自動實現序列化,不須要顯式實現Serializable接口;
當一個對象的實例變量引用其餘對象,序列化該對象時也把引用對象進行序列化;
static,transient後的變量不能被序列化;
抽象類和接口區別:
接口是抽象類的變體,接口中全部的方法都是抽象的。而抽象類是聲明方法的存在而不去實現它的類。
接口能夠多繼承,抽象類不行
接口定義方法,不能實現,而抽象類能夠實現部分方法。
接口中基本數據類型爲static 而抽類象不是的。
JVM虛擬機結構
JVM主要包括四個部分:
類加載器(ClassLoader):在JVM啓動時或者在類運行時將須要的class加載到JVM中。
執行引擎:負責執行class文件中包含的字節碼指令(執行引擎的工做機制,這裏也不細說了,這裏主要介紹JVM結構);
內存區(也叫運行時數據區):是在JVM運行的時候操做所分配的內存區。運行時內存區主要能夠劃分爲5個區域
方法區(Method Area):用於存儲類結構信息的地方,包括常量池、靜態變量、構造函數等(JDK7 永久代,JDK metaspace)。雖然JVM規範把方法區描述爲堆的一個邏輯部分,但它卻有個別名non-heap(非堆),因此你們不要搞混淆了。方法區還包含一個運行時常量池。這部分區域不是線程所私有,而是各個線程所共享的。
java堆(Heap):存儲java實例或者對象(對象和數組等實例)的地方。這塊是GC的主要區域(後面解釋)。從存儲的內容咱們能夠很容易知道,方法區和堆是被全部java線程共享的。
java棧(Stack):java棧老是和線程關聯在一塊兒,每當建立一個線程時,JVM就會爲這個線程建立一個對應的java棧。在這個java棧中又會包含多個棧幀,每運行一個方法就建立一個棧幀,用於存儲局部變量表、操做棧、方法返回值等。每個方法從調用直至執行完成的過程,就對應一個棧幀在java棧中入棧到出棧的過程。因此java棧是線程私有的。
程序計數器(PC Register):用於保存當前線程執行的內存地址。因爲JVM程序是多線程執行的(線程輪流切換),因此爲了保證線程切換回來後,還能恢復到原先狀態,就須要一個獨立的計數器,記錄以前中斷的地方,可見程序計數器也是線程私有的。
本地方法棧(Native Method Stack):和java棧的做用差很少,只不過是爲JVM使用到的native方法服務的。
4. 本地方法接口:主要是調用C或C++實現的本地方法及返回結果。
雙親委派模型
工做過程是:若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成, 每個層次的類加載都是如此 ,所以全部的加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父加載器反饋本身沒法加載這個加載請求的時候,子加載器纔會嘗試本身去加載。
使用這種機制,能夠避免重複加載,當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次(試想若是有人編寫了一個惡意的基礎類,好比String類,並裝載到JVM中將會引發多麼可怕的後果呢。可是,因爲有了全盤負責委託機制,String類 永遠是有根裝載器裝載,這樣就避免了事件的發生)。
ClassLoader主要對類的請求提供服務,當JVM須要某類時,它根據名稱向ClassLoader要求這個類,而後由ClassLoader返回這個類的class對象。
每當 JVM 啓動的時候,就會產生 三個 ClassLoader,它們分別是Bootstrap Loader, ExtClassLoader 和 AppClassLoader,ClassLoader就是用來動態加載class文件到內存當中用的。
Bootstrap Classloader啓動類加載器,主要負責java_home/lib下的核心api或者-Xbootstrap選項指定的jar包裝入工做。
Extension ClassLoader擴展類加載器,主要負責java_home/lib/ext下jar包。
App CLassLoader 系統類加載器,主要負責Java -classpath/所指的目錄下的類與jar包的裝入工做。
UserCustom ClassLoader用戶自定義類加載器,在程序運行期間,經過Java.lang.Classloader的子類動態加載class。
ExtClassLoader的父類加載器是null,只不過在默認的ClassLoader 的 loadClass 方法中,當parent爲null時,是交給BootStrapClassLoader來處理的,並且ExtClassLoader 沒有重寫默認的loadClass方法,因此,ExtClassLoader也會調用BootStrapLoader類加載器來加載,這就致使「BootStrapClassLoader具有了ExtClassLoader父類加載器的功能」。
查看classloader的源碼能夠發現三個重要的方法:
loadClass。classloader加載類的入口,此方法負責加載指定名字的類,ClassLoader的實現方法爲先從已經加載的類中尋找,如沒有則繼續從父ClassLoader中尋找,如仍然沒找到,則從BootstrapClassLoader中尋找,最後再調用findClass方法來尋找,如要改變類的加載順序,則可覆蓋此方法,如加載順序相同,則可經過覆蓋findClass來作特殊的處理,例如解密、固定路徑尋找等,當經過整個尋找類的過程仍然未獲取到Class對象時,則拋出ClassNotFoundException。如類須要resolve,則調用resolveClass進行連接。
findClass。它接受要加載的類做爲它的參數,在該方法中會找到class文件而且讀取文件中的內容到一個 byte 數組。此方法直接拋出ClassNotFoundException,所以須要經過覆蓋loadClass或此方法來以自定義的方式加載相應的類。
defineClass。此方法負責將二進制的字節碼轉換爲Class對象,這個方法對於自定義加載類而言很是重要,如二進制的字節碼的格式不符合JVM Class文件的格式,拋出ClassFormatError;如須要生成的類名和二進制字節碼中的不一樣,則拋出NoClassDefFoundError;如須要加載的class是受保護的、採用不一樣簽名的或類名是以java.開頭的,則拋出SecurityException;如需加載的class在此ClassLoader中已加載,則拋出LinkageError。
致使Gc的狀況:
tenured被寫滿
perm被寫滿
System.gc()的顯式調用。
上一次GC以後heap的各域分配策略動態變化。
JVM分別對新生代和舊生代採用不一樣的垃圾回收機制
將對象按其生命週期的不一樣劃分紅:年輕代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)
常見檢測出垃圾算法:
引用計數法
可達性分析算法
新生代的GC(Minor GC): 指發生在新生代的垃圾收集動做,由於 Java 對象大多都具有朝生夕滅的特性,因此 Minor GC 很是頻繁,通常回收速度也比較快。新生代一般存活時間較短,所以基於Copying算法來進行回收,所謂Copying算法就是掃描出存活的對象,並複製到一塊新的徹底未使用的空間中,對應於新生代,就是在Eden和FromSpace或ToSpace之間copy。
新生代採用空閒指針的方式來控制GC觸發,指針保持最後一個分配的對象在新生代區間的位置,當有新的對象要分配內存時,用於檢查空間是否足夠,不夠就觸發GC。當連續分配對象時,對象會逐漸從eden到survivor,最後到舊生代。
舊生代的GC(Major GC / Full GC):指發生在老年代的 GC。舊生代與新生代不一樣,對象存活的時間比較長,比較穩定,所以採用標記(Mark)算法來進行回收,所謂標記就是掃描出存活的對象,而後再進行回收未被標記的對象,回收後對用空出的空間要麼進行合併,要麼標記出來便於下次進行分配,總之就是要減小內存碎片帶來的效率損耗。 MajorGC 的速度通常會比 Minor GC 慢 10倍以上。Thinking in java給Java gc取了一個羅嗦的稱呼:「自適應、分代的、中止-複製、標記-掃描」式的垃圾回收器。
JVM調優
從如下幾個方面進行:
線程池:解決用戶響應時間長的問題
鏈接池
JVM啓動參數:調整各代的內存比例和垃圾回收算法,提升吞吐量
程序算法:改進程序邏輯算法提升性能
內存泄露:
歸納地說,這就是內存託管語言中的內存泄漏產生的主要緣由:保留下來卻永遠再也不使用的對象引用。
全局集合
緩存
典型的算法是:
檢查結果是否在緩存中,若是在,就返回結果。
若是結果不在緩存中,就進行計算。
將計算出來的結果添加到緩存中,以便之後對該操做的調用可使用。
該算法的問題(或者說是潛在的內存泄漏)出在最後一步。若是調用該操做時有至關多的不一樣輸入,就將有至關多的結果存儲在緩存中。很明顯這不是正確的方法。爲了預防這種具備潛在破壞性的設計,程序必須確保對於緩存所使用的內存容量有一個上限。
所以,更好的算法是:
檢查結果是否在緩存中,若是在,就返回結果。
若是結果不在緩存中,就進行計算。
若是緩存所佔的空間過大,就移除緩存最久的結果。
將計算出來的結果添加到緩存中,以便之後對該操做的調用可使用
ClassLoader
ClassLoader的特別之處在於它不只涉及「常規」的對象引用,還涉及元對象引用,好比:字段、方法和類。這意味着只要有對字段、方法、類或ClassLoader的對象的引用,ClassLoader就會駐留在JVM中。由於ClassLoader自己能夠關聯許多類及其靜態字段,因此就有許多內存被泄漏了。
volatile關鍵字怎麼實現
可見性的意思是當一個線程修改一個共享變量時,另一個線程能讀到這個修改的值。
若是你們有興趣查看代碼JIT生成後的彙編指令,會發現針對volatile的變量的寫操做,會有一個Lock指令,這是用來實現內存屏障的,保證若是一個處理器修改了變量值,會直接將值寫回到內存,其餘的處理器對應的緩存也會失效,須要從新從內存中讀取,這樣就保證全部的處理器讀到的值,都是最近的變量值。將當前處理器緩存行的數據會寫回到系統內存。這個寫回內存的操做會引發在其餘CPU裏緩存了該內存地址的數據無效。