java虛擬機jvm

文章主要來源:http://www.cnblogs.com/java-my-life/archive/2012/08/01/2615221.html

JVM提供了一個相對安全的內存管理和訪問機制,避免了絕大部分的內存泄漏和指針越界問題。

JDK:java程序設計語言、java虛擬機、java API類庫。
JRE包括JVM和JAVA核心類庫和支持文件。與JDK相比,它不包含開發工具——編譯器、調試器和其它工具。

java語言編譯程序只需生成在java虛擬機上運行的目標代碼(字節碼),java虛擬機在執行字節碼時,將字節碼解釋成具體平臺上的機器指令執行。


 java程序的運行必須經過編寫、編譯、運行三個步驟。

1、編寫是指在Java開發環境中進行程序代碼的輸入,最終形成後綴名爲.java的源文件。
2、編譯是指使用Java編譯器對源文件進行錯誤排查的過程,編譯後將生成後綴名爲.class的字節碼文件,這不像C語言那樣最終生成可執行文件。

3、運行是指使用Java解釋器將字節碼文件翻譯成機器代碼,執行並顯示結果。


字節碼文件是一種和任何具體機器環境及操作系統環境無關的中間代碼,它是一種二進制文件,是Java源文件由Java編譯器編譯後生成的目標代碼文件。 必須由專用的Java解釋器來解釋執行,因此Java是一種在編譯基礎上進行解釋運行的語言。


Java解釋器負責將字節碼文件翻譯成具體硬件環境和操作系統平臺下的機器代碼,以便執行。因此Java程序不能直接運行在現有的操作系統平臺上,它必須運行在被稱爲Java虛擬機的軟件平臺之上。Java解釋器就是Java虛擬機的一部分。

 

java虛擬機運行時數據區:方法區、堆、虛擬機棧、本地方法棧、程序計數器。


所有線程共享的運行時數據區:方法區和堆。當每一個新線程被創建時,它都將得到它自己的PC寄存器(程序計數器)以及一個Java棧,如果線程正在執行的是一個Java方法(非本地方法),那麼PC寄存器的值將總是指向下一條將被執行的指令,而它的Java棧則總是存儲該線程中Java方法調用的狀態——包括它的局部變量,被調用時傳進來的參數、返回值,以及運算的中間結果等等。而本地方法調用的狀態,則是以某種依賴於具體實現的方法存儲在本地方法棧中,也可能是在寄存器或者其他某些與特定實現相關的內存區中。


 類裝載器子系統
類裝載器子系統除了要定位和導入二進制class文件外,還必須負責驗證被導入類的正確性,爲類變量分配並初始化內存,以及幫助解析符號引用。這些動作必須嚴格按以下順序進行:
  (1)裝載——查找並裝載類的二進制數據。
  (2)鏈接——驗證、準備、以及解析(可選)。
    ● 驗證  確保被導入類型的正確性。
    ● 準備  爲類的靜態變量分配內存,並將其初始化爲默認值。
    ● 解析  把類型中的符號引用轉換爲直接引用。(符號引用是一個字符串,給出了被引用的內容的名字並且可能會包含一些其他關於這個被引用項的信息——這些信息必須足以唯一的識別一個類、字段、方法。對於類的符號引用必須給出類的全名;對於類的字段,必須給出類名、字段名以及字段描述符;對於類的方法的引用必須給出類名、方法名以及方法的描述符。直接引用指向方法區的本地指針)

(3)初始化——把類變量初始化爲正確初始值。


類初始化的時機:

1、創建類的實例,new一個對象;

2、調用類或接口的靜態變量,或者對該靜態變量賦值;

3、調用類的靜態方法;

4、反射;

5、初始化一個類的子類(會首先初始化子類的父類);

6、JVM啓動時標明的啓動類,即文件名和類名相同的那個類。

 

類的初始化步驟:

1、如果這個類還沒有被加載和鏈接,那就先進行加載和鏈接;

2、假如這個類存在直接父類,並且這個類還沒有被初始化(注意,在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用於接口);

3、假如類中存在初始化語句(比如static變量和static塊),那就依次執行這些初始化語句。


類的加載是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個這個類的java.lang.Class對象,用來封裝類在方法區類的對象。類的加載的最終產品是位於堆區中的Class對象。

Class對象封裝了類在方法區內的數據結構,並且向Java程序員提供了訪問方法區內的數據結構的接口。



方法區

在java虛擬機中,被裝載類型的信息存儲在方法區。當虛擬機裝載某個類型時,它使用類裝載器定位相應的class文件,然後讀入這個class文件——1個線性二進制數據流,然後它傳輸到虛擬機中,緊接着虛擬機提取其中的類型信息,並將這些信息存儲到方法區。該類型中的類(靜態)變量同樣也是存儲在方法區中。


對於每個裝載的類型,虛擬機都會在方法區中存儲以下類型信息:
  ● 這個類型的全限定名
  ● 這個類型的直接超類的全限定名(除非這個類型是java.lang.Object,它沒有超類)
  ● 這個類型是類類型還是接口類型
  ● 這個類型的訪問修飾符(public、abstract或final的某個子集)

  ● 任何直接超接口的全限定名的有序列表


除了上面列出的基本類型信息外,虛擬機還得爲每個被裝載的類型存儲以下信息:
  ● 該類型的常量池
  ● 字段信息
  ● 方法信息
  ● 除了常量以外的所有類(靜態)變量
  ● 一個到類ClassLoader的引用

  ● 一個到Class類的引用


常量池:

虛擬機必須爲每個被裝載的類型維護一個常量池。常量池就是該類型所用常量的一個有序集合,包括直接常量和對其他類型、字段和方法的符號引用。池中的數據項就像數組一樣是通過索引訪問的。因爲常量池存儲了相應類型所用到的所有類型、字段和方法的符號引用,所以它在Java程序的動態連接中起着核心的作用。

常量池理解與總結:http://www.jianshu.com/p/c7f47de2ee80#


java是一種動態連接的語言,常量池的作用非常重要。常量池存放量大類常量:字面量和符號引用。字面量相當於java語言層面常量的概念,如文本字符串,聲明爲final的常量值等;符號引用:編譯時,如果發現對其他類方法的調用或者對其他類字段的引用的話,記錄進Class文件中的,只能是一個文本形式的符號引用,在連接過程中,虛擬機根據這個文本信息去查找對應的方法或字段。


字段類型:
對於類型中聲明的每一個字段。方法區中必須保存下面的信息。除此之外,這些字段在類或者接口中的聲明順序也必須保存。
  ○ 字段名
  ○ 字段的類型

○ 字段的修飾符(public、private、protected、static、final、volatile、transient的某個子集)


方法信息:
對於類型中聲明的每一個方法,方法區中必須保存下面的信息。和字段一樣,這些方法在類或者接口中的聲明順序也必須保存。
  ○ 方法名
  ○ 方法的返回類型(或void)
  ○ 方法參數的數量和類型(按聲明順序)
  ○ 方法的修飾符(public、private、protected、static、final、synchronized、native、abstract的某個子集)
  除了上面清單中列出的條目之外,如果某個方法不是抽象的和本地的,它還必須保存下列信息:
  ○ 方法的字節碼(bytecodes)
  ○ 操作數棧和該方法的棧幀中的局部變量區的大小

  ○ 異常表


類(靜態)變量:
它們總是作爲類型信息的一部分而存儲在方法區。除了在類中聲明的編譯時常量外,虛擬機在使用某個類之前,必須在方法區中爲這些類變量分配空間。

編譯時常量(就是那些用final聲明以及用編譯時已知的值初始化的類變量)則和一般的類變量處理方式不同,每個使用編譯時常量的類型都會複製它的所有常量到自己的常量池中,或嵌入到它的字節碼流中。作爲常量池或字節碼流的一部分,編譯時常量保存在方法區中——就和一般的類變量一樣。但是當一般的類變量作爲聲明它們的類型的一部分數據面保存的時候,編譯時常量作爲使用它們的類型的一部分而保存。


指向ClassLoader類的引用:

每個類型被加載的時候,需要類加載器。所以虛擬機必須在類型信息中存儲對該加載器的引用。虛擬機在動態連接期間使用這個信息。正確地執行動態連接以及維護多個命名空間。


指向Class類的引用:

  對於每一個被裝載的類型(不管是類還是接口),虛擬機都會相應地爲它創建一個java.lang.Class類的實例,而且虛擬機還必須以某種方式把這個實例和存儲在方法區中的類型數據關聯起來。


運行時常量池
運行時常量池:方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,存放編譯期生成的各種字面量和符號引用。     java虛擬機對Class文件每一部分(包括常量池)的格式都有嚴格規定,但對於運行時常量池,java虛擬機規範沒有做任何細節的要求。具有動態性,運行期間可能將新的常量放入池中。


每個java程序獨佔一個java虛擬機實例。每個java程序都有自己的堆空間——它們不會彼此干擾。但一個程序中的所有線程都共享這個堆。
數組的內部表示:所有具有相同維度和類型的數組都是同一個類的實例,而不管數組的長度(多維數組每一維的長度)是多少。數組類的名稱由兩部分組成:每一維用一個方括號“[”表示,用字符或字符串表示元素類型。
 
程序計數器
對於一個運行中的Java程序而言,其中的每一個線程都有它自己的PC(程序計數器)寄存器,它是在該線程啓動時創建的,PC寄存器的大小是一個字長,因此它既能夠持有一個本地指針,也能夠持有一個returnAddress。當線程執行某個Java方法時,PC寄存器的內容總是下一條將被執行指令的“地址”,這裏的“地址”可以是一個本地指針,也可以是在方法字節碼中相對於該方法起始指令的偏移量。如果該線程正在執行一個本地方法,那麼此時PC寄存器的值是“undefined”。
 


 棧

每當啓動一個新線程時,Java虛擬機都會爲它分配一個Java棧。Java棧以幀爲單位保存線程的運行狀態。虛擬機只會直接對Java棧執行兩種操作:以幀爲單位的壓棧和出棧。
在線程執行一個方法時,它會跟蹤當前類和當前常量池。此外,當虛擬機遇到棧內操作指令時,它對當前幀內數據執行操作。
每當線程調用一個java方法時,虛擬機在該線程的java棧中壓入一個新幀,該幀成爲當前幀。在執行這個方法時,使用這個幀來存儲參數、局部變量、中間運算結果等數據。
java方法以return返回(正常返回)或者通過拋出異常而異常終止。
 


 本地方法棧

  

當某個線程調用一個本地方法時,它就進入了一個全新的並且不再受虛擬機限制的世界。本地方法可以通過本地方法接口來訪問虛擬機的運行時數據區,但不止如此,它還可以做任何它想做的事情。


當它調用的是本地方法時,虛擬機會保持Java棧不變,不再在線程的Java棧中壓入新的幀,虛擬機只是簡單地動態連接並直接調用指定的本地方法。

java虛擬機的主要任務是裝載class文件並且執行其中的字節碼。類加載器從程序和API(只有程序執行時需要的那些類纔會被加載)中加載class文件;字節碼由執行引擎來執行。


java方法(由java語言寫的,編譯成字節碼,存儲在Class文件中)和本地方法(由其他語言如C,C++,編譯語言編寫,編譯成和處理器相關的機器代碼,本地方法時平臺相關的),本地方法保存在動態連接庫中。本地方法是聯繫java程序和底層主機操作系統的連接方法,通過本地方法,java程序可以直接訪問底層操作系統的資源。


java API類庫是用java寫的,但它有很多功能是直接調用操作系統的API來完成的,java虛擬機是C++或者C寫的。
java和C++區別:內存動態分配和垃圾收集技術。
java程序從源文件創建到程序運行:1、源文件由編譯器編譯成字節碼;2、字節碼由java虛擬機解釋運行。
彙編語言:直接面向處理器的程序設計語言。彙編語言所操作的對象不是具體的數據而是寄存器或者存儲器。彙編語言指令是機器指令的一種符號表示。
發佈了30 篇原創文章 · 獲贊 8 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章