JAVA複習資料-JVM
1,JVM(Java Virtual Machine,Java虛擬機器)
Java程式的跨平臺特性主要是指位元組碼檔案可以在任何具有Java虛擬機器的計算機或者電子裝置上執行,Java虛擬機器中的Java直譯器負責將位元組碼檔案解釋成為特定的機器碼進行執行。因此在執行時,Java源程式需要通過編譯器編譯成為.class檔案。眾所周知java.exe是java class檔案的執行程式,但實際上java.exe程式只是一個執行的外殼,它會裝載jvm.dll(windows下,下皆以windows平臺為例,linux下和solaris下其實類似,為:libjvm.so),這個動態連線庫才是java虛擬機器的實際操作處理所在。
JVM是JRE的一部分。它是一個虛構出來的計算機,是通過在實際的計算機上模擬模擬各種計算機功能來實現的。JVM有自己完善的硬體架構,如處理器、堆疊、暫存器等,還具有相應的指令系統。Java語言最重要的特點就是跨平臺執行。使用JVM就是為了支援與作業系統無關,實現跨平臺。所以,JAVA虛擬機器JVM是屬於JRE的,而現在我們安裝JDK時也附帶安裝了JRE(當然也可以單獨安裝JRE)。
JVM記憶體區域劃分
粗略分來,JVM的內部體系結構分為三部分,分別是:類裝載器(ClassLoader)子系統,執行時資料區,和執行引擎。
類裝載器
每一個Java虛擬機器都由一個類載入器子系統(class loader subsystem),負責載入程式中的型別(類和介面),並賦予唯一的名字。每一個Java虛擬機器都有一個執行引擎(execution engine)負責執行被載入類中包含的指令。JVM的兩種類裝載器包括:啟動類裝載器和使用者自定義類裝載器,啟動類裝載器是JVM實現的一部分,使用者自定義類裝載器則是Java程式的一部分,必須是ClassLoader類的子類。
執行引擎:它或者在執行位元組碼,或者執行本地方法
主要的執行技術有:解釋,即時編譯,自適應優化、晶片級直接執行其中解釋屬於第一代JVM,即時編譯JIT屬於第二代JVM,自適應優化(目前Sun的HotspotJVM採用這種技術)則吸取第一代JVM和第二代JVM的經驗,採用兩者結合的方式 。
自適應優化:開始對所有的程式碼都採取解釋執行的方式,並監視程式碼執行情況,然後對那些經常呼叫的方法啟動一個後臺執行緒,將其編譯為原生代碼,並進行仔細優化。若方法不再頻繁使用,則取消編譯過的程式碼,仍對其進行解釋執行。
A、解釋程式 所謂解釋程式是高階語言翻譯程式的一種,它將源語言(如BASIC)書寫的源程式作為輸入,解釋一句後就提交計算機執行一句,並不形成目標程式。就像外語翻譯中的“口譯”一樣,說一句翻一句,不產生全文的翻譯文字。這種工作方式非常適合於人通過終端裝置與計算機會話,如在終端上打一條命令或語句,解釋程式就立即將此語句解釋成一條或幾條指令並提交硬體立即執行且將執行結果反映到終端,從終端把命令打入後,就能立即得到計算結果。這的確是很方便的,很適合於一些小型機的計算問題。但解釋程式執行速度很慢,例如源程式中出現迴圈,則解釋程式也重複地解釋並提交執行這一組語句,這就造成很大浪費。 B、編譯程式 這是一類很重要的語言處理程式,它把高階語言(如FORTRAN、COBOL、Pascal、C等)源程式作為輸入,進行翻譯轉換,產生出機器語言的目標程式,然後再讓計算機去執行這個目標程式,得到計算結果。 編譯程式工作時,先分析,後綜合,從而得到目標程式。所謂分析,是指詞法分析和語法分析;所謂綜合是指程式碼優化,儲存分配和程式碼生成。為了完成這些分析綜合任務,編譯程式採用對源程式進行多次掃描的辦法,每次掃描集中完成一項或幾項任務,也有一項任務分散到幾次掃描去完成的。下面舉一個四遍掃描的例子:第一遍掃描做詞法分析;第二遍掃描做語法分析;第三遍掃描做程式碼優化和儲存分配;第四遍掃描做程式碼生成。 值得一提的是,大多數的編譯程式直接產生機器語言的目的碼,形成可執行的目標檔案,但也有的編譯程式則先產生組合語言一級的符號程式碼檔案,然後再調用匯程式設計序進行翻譯加工處理,最後產生可執行的機器語言目標檔案。 在實際應用中,對於需要經常使用的有大量計算的大型題目,採用招待速度較快的編譯型的高階語言較好,雖然編譯過程本身較為複雜,但一旦形成目標檔案,以後可多次使用。相反,對於小型題目或計算簡單不太費機時的題目,則多選用解釋型的會話式高階語言,如BASIC,這樣可以大大縮短程式設計及除錯的時
- 方法區和堆由所有執行緒共享
堆:存放所有程式在執行時建立的物件
它是JVM用來儲存物件例項以及陣列值的區域,可以認為Java中所有通過new建立的物件的記憶體都在此分配,Heap中的物件的記憶體需要等待GC進行回收。
堆是JVM中所有執行緒共享的,因此在其上進行物件記憶體的分配均需要進行加鎖,這也導致了new物件的開銷是比較大的
Sun Hotspot JVM為了提升物件記憶體分配的效率,對於所建立的執行緒都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據執行的情況計算而得,在TLAB上分配物件時不需要加鎖,因此JVM在給執行緒的物件分配記憶體時會盡量的在TLAB上分配,在這種情況下JVM中分配物件記憶體的效能和C基本是一樣高效的,但如果物件過大的話則仍然是直接使用堆空間分配
TLAB僅作用於新生代的Eden Space,因此在編寫Java程式時,通常多個小的物件比大的物件分配起來更加高效。
方法區:當JVM的類裝載器載入.class檔案,並進行解析,把解析的型別資訊放入方法區。
在Sun JDK中這塊區域對應的為PermanetGeneration,又稱為持久代。
方法區域存放了所載入的類的資訊(名稱、修飾符等)、類中的靜態變數、類中定義為final型別的常量、類中的Field資訊、類中的方法資訊,當開發人員在程式中通過Class物件中的getName、isInterface等方法來獲取資訊時,這些資料都來源於方法區域,同時方法區域也是全域性共享的,在一定的條件下它也會被GC,當方法區域需要使用的記憶體超過其允許的大小時,會丟擲OutOfMemory的錯誤資訊。
Java棧和PC暫存器由執行緒獨享
JVM棧是執行緒私有的,每個執行緒建立的同時都會建立JVM棧,JVM棧中存放的為當前執行緒中區域性基本型別的變數(java中定義的八種基本型別:boolean、char、byte、short、int、long、float、double)、部分的返回結果以及Stack Frame(表示當前執行緒的呼叫堆疊中的一個函式呼叫的資訊,常用來追蹤錯誤),非基本型別的物件在JVM棧上僅存放一個指向堆上的地址.
虛擬機器只會直接對Javastack執行兩種操作:以幀為單位的壓棧或出棧.每個幀代表一個方法,Java方法有兩種返回方式,return和丟擲異常,兩種方式都會導致該方法對應的幀出棧和釋放記憶體。
幀的組成:區域性變數區(包括方法引數和區域性變數,對於instance方法,還要首先儲存this型別,其中方法引數按照宣告順序嚴格放置,區域性變數可以任意放置),運算元棧,幀資料區(用來幫助支援常量池的解析,正常方法返回和異常處理)。
ProgramCounter(程式計數器)
每一個執行緒都有它自己的PC暫存器,也是該執行緒啟動時建立的。PC暫存器的內容總是指向下一條將被執行指令的地址,這裡的地址可以是一個本地指標,也可以是在方法區中相對應於該方法起始指令的偏移量。
本地方法棧:儲存本地方法呼叫的狀態
儲存native方法進入區域的地址
依賴於本地方法的實現,如某個JVM實現的本地方法藉口使用C連線模型,則本地方法棧就是C棧,可以說某執行緒在呼叫本地方法時,就進入了一個不受JVM限制的領域,也就是JVM可以利用本地方法來動態擴充套件本身
JVM垃圾回收
Sun的JVMGenerationalCollecting(垃圾回收)原理是這樣的:把物件分為年青代(Young)、年老代(Tenured)、持久代(Perm),對不同生命週期的物件使用不同的演算法。(基於對物件生命週期分析)通常我們說的JVM記憶體回收總是在指堆記憶體回收,確實只有堆中的內容是動態申請分配的,所以以上物件的年輕代和年老代都是指的JVM的Heap空間,而持久代則是之前提到的MethodArea,不屬於Heap。
GC的基本原理:將記憶體中不再被使用的物件進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對物件的生命週期特徵進行分析後,按照新生代、舊生代的方式來對物件進行收集,以儘可能的縮短GC對應用造成的暫停
(1)對新生代的物件的收集稱為minor GC;
(2)對舊生代的物件的收集稱為Full GC;
(3)程式中主動呼叫System.gc()強制執行的GC為Full GC。
不同的物件引用型別, GC會採用不同的方法進行回收,JVM物件的引用分為了四種類型:
(1)強引用:預設情況下,物件採用的均為強引用(這個物件的例項沒有其他物件引用,GC時才會被回收)
(2)軟引用:軟引用是Java中提供的一種比較適合於快取場景的應用(只有在記憶體不夠用的情況下才會被GC)
(3)弱引用:在GC時一定會被GC回收
(4)虛引用:由於虛引用只是用來得知物件是否被GC
- Young(年輕代)
年輕代分三個區。一個Eden區,兩個Survivor區。大部分物件在Eden區中生成。當Eden區滿時,還存活的物件將被複制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活物件將被複制到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的物件,將被複制年老區(Tenured。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來物件,和從前一個Survivor複製過來的物件,而複製到年老區的只有從第一個Survivor去過來的物件。而且,Survivor區總有一個是空的。
- Tenured(年老代)
年老代存放從年輕代存活的物件。一般來說年老代存放的都是生命期較長的物件。
- Perm(持久代)
用於存放靜態檔案,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者呼叫一些class,例如Hibernate等,在這種時候需要設定一個比較大的持久代空間來存放這些執行過程中新增的類。持久代大小通過-XX:MaxPermSize=進行設定。