Java虛擬機工做原理 |
首先我想從宏觀上介紹一下Java虛擬機的工做原理。從最初的咱們編寫的Java源文件(.java文件)是如何一步步執行的,以下圖所示,首先Java源文件通過前端編譯器(javac或ECJ)將.java文件編譯爲Java字節碼文件,而後JRE加載Java字節碼文件,載入系統分配給JVM的內存區,而後執行引擎解釋或編譯類文件,再由即時編譯器將字節碼轉化爲機器碼。主要介紹下圖中的類加載器和運行時數據區兩個部分。前端
類加載指將類的字節碼文件(.class)中的二進制數據讀入內存,將其放在運行時數據區的方法區內,而後在堆上建立java.lang.Class對象,封裝類在方法區內的數據結構。類加載的最終產品是位於堆中的類對象,類對象封裝了類在方法區內的數據結構,而且向JAVA程序提供了訪問方法區內數據結構的接口。以下是類加載器的層次關係圖。java
注意如上的類加載器並非經過繼承的方式實現的,而是經過組合的方式實現的。而JAVA虛擬機的加載模式是一種委派模式,如上圖中的1-7步所示。下層的加載器可以看到上層加載器中的類,反之則不行。類加載器能夠加載類可是不能卸載類。說了一大堆,仍是感受須要拿點代碼說事。算法
首先咱們先定義本身的類加載器MyClassLoader,繼承自ClassLoader,並覆蓋了父類的findClass(String name)方法,以下:數據庫
public class MyClassLoader extends ClassLoader{ private String loaderName; //類加載器名稱 private String path = ""; //加載類的路徑 private final String fileType = ".class"; public MyClassLoader(String name){ super(); //應用類加載器爲該類的父類 this.loaderName = name; } public MyClassLoader(ClassLoader parent,String name){ super(parent); this.loaderName = name; } public String getPath(){return this.path;} public void setPath(String path){this.path = path;} @Override public String toString(){return this.loaderName;} @Override public Class<?> findClass(String name) throws ClassNotFoundException{ byte[] data = loaderClassData(name); return this.defineClass(name, data, 0, data.length); } //讀取.class文件 private byte[] loaderClassData(String name){ InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { is = new FileInputStream(new File(path + name + fileType)); int c = 0; while(-1 != (c = is.read())){ baos.write(c); } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally{ try { if(is != null) is.close(); if(baos != null) baos.close(); } catch (IOException e) { e.printStackTrace(); } } return data; } }
咱們如何利用咱們定義的類加載器加載指定的字節碼文件(.class)呢?如經過MyClassLoader加載C:\\Users\\Administrator\\下的Test.class字節碼文件,代碼以下所示:網絡
public class Client { public static void main(String[] args) { // TODO Auto-generated method stub //MyClassLoader的父類加載器爲系統默認的加載器AppClassLoader MyClassLoader myCLoader = new MyClassLoader("MyClassLoader"); //指定MyClassLoader的父類加載器爲ExtClassLoader //MyClassLoader myCLoader = new MyClassLoader(ClassLoader.getSystemClassLoader().getParent(),"MyClassLoader"); myCLoader.setPath("C:\\Users\\Administrator\\"); Class<?> clazz; try { clazz = myCLoader.loadClass("Test"); Field[] filed = clazz.getFields(); //獲取加載類的屬性字段 Method[] methods = clazz.getMethods(); //獲取加載類的方法字段 System.out.println("該類的類加載器爲:" + clazz.getClassLoader()); System.out.println("該類的類加載器的父類爲:" + clazz.getClassLoader().getParent()); System.out.println("該類的名稱爲:" + clazz.getName()); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
字節碼的加載第一步,其後分別是認證、準備、解析、初始化,那麼這些步驟又具體作了哪些工做,以及他們會對運行時數據區纏身什麼影響呢?以下圖所示:數據結構
以下咱們將介紹運行時數據區,主要分爲方法區、Java堆、虛擬機棧、本地方法棧、程序計數器。其中方法區和Java堆同樣,是各個線程共享的內存區域,而虛擬機棧、本地方法棧、程序計數器是線程私有的內存區。ide
寫了這麼多,感受仍是少一個例子。經過最簡單的一段代碼解釋一下,程序在運行時數據區個部分的變化狀況。優化
public class Test{ public static void main(String[] args){ String name = "best.lei"; sayHello(name); } public static void sayHello(String name){ System.out.println("Hello " + name); } }
經過編譯器將Test.java文件編譯爲Test.class,利用javap -verbose Test.class對編譯後的字節碼進行分析,以下圖所示:this
咱們在看看運行時數據區的變化:spa