Java虛擬機是一個能夠執行Java字節碼的虛擬機進程。Java源文件被編譯成能被Java虛擬機執行的字節碼文件。 Java被設計成容許應用程序能夠運行在任意的平臺,而不須要程序員爲每個平臺單獨重寫或者是從新編譯。Java虛擬機讓這個變爲可能,由於它知道底層硬件平臺的指令長度和其餘特性。java
方法區和對是全部線程共享的內存區域;而java棧、本地方法棧和程序員計數器是運行是線程私有的內存區域。android
一般咱們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用JVM中的棧空間;而經過new關鍵字和構造器建立的對象則放在堆空間,堆是垃圾收集器管理的主要區域,因爲如今的垃圾收集器都採用分代收集算法,因此堆空間還能夠細分爲新生代和老生代,再具體一點能夠分爲Eden、Survivor(又可分爲From Survivor和To Survivor)、Tenured;方法區和堆都是各個線程共享的內存區域,用於存儲已經被JVM加載的類信息、常量、靜態變量、JIT編譯器編譯後的代碼等數據;程序中的字面量(literal)如直接書寫的100、」hello」和常量都是放在常量池中,常量池是方法區的一部分,。棧空間操做起來最快可是棧很小,一般大量的對象都是放在堆空間,棧和堆的大小均可以經過JVM的啓動參數來進行調整,棧空間用光了會引起StackOverflowError,而堆和常量池空間不足則會引起OutOfMemoryError。git
String str = new String("hello");
上面的語句中變量str放在棧上,用new建立出來的字符串對象放在堆上,而」hello」這個字面量是放在方法區的。程序員
補充1:較新版本的Java(從Java 6的某個更新開始)中,因爲JIT編譯器的發展和」逃逸分析」技術的逐漸成熟,棧上分配、標量替換等優化技術使得對象必定分配在堆上這件事情已經變得不那麼絕對了。github
補充2:運行時常量池至關於Class文件常量池具備動態性,Java語言並不要求常量必定只有編譯期間才能產生,運行期間也能夠將新的常量放入池中,String類的intern()方法就是這樣的。 看看下面代碼的執行結果是什麼而且比較一下Java 7之前和之後的運行結果是否一致。面試
String s1 = new StringBuilder("go") .append("od").toString(); System.out.println(s1.intern() == s1); String s2 = new StringBuilder("ja") .append("va").toString(); System.out.println(s2.intern() == s2);
類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,而後在堆區建立一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位於堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,而且向Java程序員提供了訪問方法區內的數據結構的接口。算法
答:JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java中的類加載器是一個重要的Java運行時系統組件,它負責在運行時查找和裝入類文件中的類。 因爲Java的跨平臺性,通過編譯的Java源程序並非一個可執行程序,而是一個或多個類文件。當Java程序須要使用某個類時,JVM會確保這個類已經被加載、鏈接(驗證、準備和解析)和初始化。類的加載是指把類的.class文件中的數據讀入到內存中,一般是建立一個字節數組讀入.class文件,而後產生與所加載類對應的Class對象。加載完成後,Class對象還不完整,因此此時的類還不可用。當類被加載後就進入鏈接階段,這一階段包括驗證、準備(爲靜態變量分配內存並設置默認的初始值)和解析(將符號引用替換爲直接引用)三個步驟。最後JVM對類進行初始化,包括:1)若是類存在直接的父類而且這個類尚未被初始化,那麼就先初始化父類;2)若是類中存在初始化語句,就依次執行這些初始化語句。 類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)。從Java 2(JDK 1.2)開始,類加載過程採起了父親委託機制(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根加載器,其餘的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM不會向Java程序提供對Bootstrap的引用。下面是關於幾個類加載器的說明:編程
JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java中的類加載器是一個重要的Java運行時系統組件,它負責在運行時查找和裝入類文件中的類。數組
因爲Java的跨平臺性,通過編譯的Java源程序並非一個可執行程序,而是一個或多個類文件。當Java程序須要使用某個類時,JVM會確保這個類已經被加載、鏈接(驗證、準備和解析)和初始化。類的加載是指把類的.class文件中的數據讀入到內存中,一般是建立一個字節數組讀入.class文件,而後產生與所加載類對應的Class對象。加載完成後,Class對象還不完整,因此此時的類還不可用。當類被加載後就進入鏈接階段,這一階段包括驗證、準備(爲靜態變量分配內存並設置默認的初始值)和解析(將符號引用替換爲直接引用)三個步驟。最後JVM對類進行初始化,包括:瀏覽器
類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)。
從Java 2(JDK 1.2)開始,類加載過程採起了父親委託機制(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根加載器,其餘的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM不會向Java程序提供對Bootstrap的引用。下面是關於幾個類加載器的說明:
1.JVM遇到一條新建對象的指令時首先去檢查這個指令的參數是否能在常量池中定義到一個類的符號引用。而後加載這個類(類加載過程在後邊講)
2.爲對象分配內存。一種辦法「指針碰撞」、一種辦法「空閒列表」,最終經常使用的辦法「本地線程緩衝分配(TLAB)」
3.將除對象頭外的對象內存空間初始化爲0
4.對對象頭進行必要設置
類的生命週期包括這幾個部分,加載、鏈接、初始化、使用和卸載,其中前三部是類的加載的過程,以下圖;
Java對象由三個部分組成:對象頭、實例數據、對齊填充。
對象頭由兩部分組成,第一部分存儲對象自身的運行時數據:哈希碼、GC分代年齡、鎖標識狀態、線程持有的鎖、偏向線程ID(通常佔32/64 bit)。第二部分是指針類型,指向對象的類元數據類型(即對象表明哪一個類)。若是是數組對象,則對象頭中還有一部分用來記錄數組長度。
實例數據用來存儲對象真正的有效信息(包括父類繼承下來的和本身定義的)
對齊填充:JVM要求對象起始地址必須是8字節的整數倍(8字節對齊)
句柄池、直接指針。
判斷對象是否存活通常有兩種方式:
垃圾回收不會發生在永久代,若是永久代滿了或者是超過了臨界值,會觸發徹底垃圾回收(Full GC)。若是你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是爲何正確的永久代大小對避免Full GC是很是重要的緣由。請參考下Java8:從永久代到元數據區 (注:Java8中已經移除了永久代,新加了一個叫作元數據區的native內存區)
###GC是什麼?爲何要有GC? 答:GC是垃圾收集的意思,內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會致使程序或系統的不穩定甚至崩潰,Java提供的GC功能能夠自動監測對象是否超過做用域從而達到自動回收內存的目的,Java語言沒有提供釋放已分配內存的顯示操做方法。Java程序員不用擔憂內存管理,由於垃圾收集器會自動進行管理。要請求垃圾收集,能夠調用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM能夠屏蔽掉顯示的垃圾回收調用。 垃圾回收能夠有效的防止內存泄露,有效的使用可使用的內存。垃圾回收器一般是做爲一個單獨的低優先級的線程運行,不可預知的狀況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或全部對象進行垃圾回收。在Java誕生初期,垃圾回收是Java最大的亮點之一,由於服務器端的編程須要有效的防止內存泄露問題,然而時過境遷,現在Java的垃圾回收機制已經成爲被詬病的東西。移動智能終端用戶一般以爲iOS的系統比Android系統有更好的用戶體驗,其中一個深層次的緣由就在於android系統中垃圾回收的不可預知性。
補充:垃圾回收機制有不少種,包括:分代複製垃圾回收、標記垃圾回收、增量垃圾回收等方式。標準的Java進程既有棧又有堆。棧保存了原始型局部變量,堆保存了要建立的對象。Java平臺對堆內存回收和再利用的基本算法被稱爲標記和清除,可是Java對其進行了改進,採用「分代式垃圾收集」。這種方法會跟Java對象的生命週期將堆內存劃分爲不一樣的區域,在垃圾收集過程當中,可能會將對象移動到不一樣區域:
-Xms / -Xmx — 堆的初始大小 / 堆的最大大小 -Xmn — 堆中年輕代的大小 -XX:-DisableExplicitGC — 讓System.gc()不產生任何做用 -XX:+PrintGCDetails — 打印GC的細節 -XX:+PrintGCDateStamps — 打印GC操做的時間戳 -XX:NewSize / XX:MaxNewSize — 設置新生代大小/新生代最大大小 -XX:NewRatio — 能夠設置老生代和新生代的比例 -XX:PrintTenuringDistribution — 設置每次新生代GC後輸出倖存者樂園中對象年齡的分佈 -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設置老年代閥值的初始值和最大值 -XX:TargetSurvivorRatio:設置倖存區的目標使用率
1.該對象沒有與GC Roots相連
2.該對象沒有重寫finalize()方法或finalize()已經被執行過則直接回收(第一次標記)、不然將對象加入到F-Queue隊列中(優先級很低的隊列)在這裏finalize()方法被執行,以後進行第二次標記,若是對象仍然應該被GC則GC,不然移除隊列。 (在finalize方法中,對象極可能和其餘 GC Roots中的某一個對象創建了關聯,finalize方法只會被調用一次,且不推薦使用finalize方法)
方法區回收價值很低,主要回收廢棄的常量和無用的類。
如何判斷無用的類:
1.該類全部實例都被回收(Java堆中沒有該類的對象)
2.加載該類的ClassLoader已經被回收
3.該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方利用反射訪問該類
GC最基礎的算法有三種: 標記 -清除算法、複製算法、標記-壓縮算法,咱們經常使用的垃圾回收器通常都採用分代收集算法。
摘錄GC日誌一部分(前部分爲年輕代gc回收;後部分爲full gc回收):
2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs] 2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]
經過上面日誌分析得出,PSYoungGen、ParOldGen、PSPermGen屬於Parallel收集器。其中PSYoungGen表示gc回收先後年輕代的內存變化;ParOldGen表示gc回收先後老年代的內存變化;PSPermGen表示gc回收先後永久區的內存變化。young gc 主要是針對年輕代進行內存回收比較頻繁,耗時短;full gc 會對整個堆內存進行回城,耗時長,所以通常儘可能減小full gc的次數
Sun JDK監控和故障處理命令有jps jstat jmap jhat jstack jinfo
經常使用調優工具分爲兩類,jdk自帶監控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。
新生代內存不夠用時候發生MGC也叫YGC,JVM內存不夠的時候發生FGC
-Xmx:堆內存最大限制。
-XX:NewSize:新生代大小
-XX:NewRatio 新生代和老生代佔比
-XX:SurvivorRatio:伊甸園空間和倖存者空間的佔比
原文:Java架構筆記
免費Java高級資料須要本身領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G。
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q