1,類加載
每個編寫的”.java”拓展名類文件都存儲着需要執行的程序邏輯,這些”.java”文件經過Java編譯器編譯成拓展名為”.class”的文件,”.class”文件中保存着Java代碼經轉換后的虛擬機指令,
當需要使用某個類時,虛擬機將會加載它的”.class”文件,並創建對應的class對象,將class文件加載到虛擬機的內存,這個過程稱為類加載,這里我們需要了解一下類加載的過程,如下:
創建的對象存儲在java 堆內存,對象的引用存儲在java 虛擬機棧 class 對象中包含的方法,屬性存儲在方法區(JDK1.8 之前叫叫做永久區)
Jvm執行class文件
Loading:
step1: 類加載器將.class文件的二進制數據從外部存儲器(如光盤,硬盤)調入內存中,在方法區生成方法區中的運行數據,生成java.lang.Class 對象(存儲在java 堆)
step2: CPU再從內存中讀取指令和數據進行運算,並將運算結果存入內存中(方法的返回結果也是存在方法區中) 。直接給CPU處理,而由於CPU的處理速度遠遠大於調入數據的速度,容易造成數據的脫節,所以需要內存起緩沖作用
每個類都對應有一個Class類型的對象,Class類的構造方法是私有的,只有JVM能夠創建。因此Class對象是反射的入口,使用該對象就可以獲得目標類所關聯的.class文件中具體的數據結構。
Linking:
驗證:確保加載的類信息符合JVM規范,沒有安全方面的問題
准備:正式為類變量(static變量)分配內存並設置類變量初始值的階段,這些內存都將在方法區中進行分配
解析:虛擬機常量池的符號引用替換為字節引用過程
initilization:初始化
當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化
初始化過程:
類構造器<clinit>()方法是由編譯器自動收藏類中的所有類變量的賦值動作和靜態語句塊(static塊)中的語句合並產生,代碼從上往下執行。
也就是說這個方法,在類初始化階段,會將類中所有的靜態變量收集到一塊並且分配空間,順序是從上往下執行,若一個靜態變量多次賦值,靜態函數里面若沒有類型也是可以的,會默認初始化:
static { agent = 0; } private static int agent = 9;
2,類加載器常用方法
loadClass(String)
該方法加載指定名稱(包括包名)的二進制類型,該方法在JDK1.2之后不再建議用戶重寫但用戶可以直接調用該方法,loadClass()方法是ClassLoader類自己實現的,該方法中的邏輯就是雙親委派模式的實現,其源碼如下,loadClass(String name, boolean resolve)是一個重載方法,resolve參數代表是否生成class對象的同時進行解析相關操作。
正如loadClass方法所展示的,當類加載請求到來時,先從緩存中查找該類對象,如果存在直接返回,如果不存在則交給該類加載去的父加載器去加載,倘若沒有父加載則交給頂級啟動類加載器去加載,最后倘若仍沒有找到,則使用findClass()方法去加載(關於findClass()稍后會進一步介紹)。從loadClass實現也可以知道如果不想重新定義加載類的規則,也沒有復雜的邏輯,只想在運行時加載自己指定的類,那么我們可以直接使用this.getClass().getClassLoder.loadClass("className"),這樣就可以直接調用ClassLoader的loadClass方法獲取到class對象。
findClass(String)
在JDK1.2之前,在自定義類加載時,總會去繼承ClassLoader類並重寫loadClass方法,從而實現自定義的類加載類,但是在JDK1.2之后已不再建議用戶去覆蓋loadClass()方法,而是建議把自定義的類加載邏輯寫在findClass()方法中,從前面的分析可知,findClass()方法是在loadClass()方法中被調用的,當loadClass()方法中父加載器加載失敗后,則會調用自己的findClass()方法來完成類加載,這樣就可以保證自定義的類加載器也符合雙親委托模式。需要注意的是ClassLoader類中並沒有實現findClass()方法的具體代碼邏輯,取而代之的是拋出ClassNotFoundException異常,同時應該知道的是findClass方法通常是和defineClass方法一起使用的(稍后會分析)
defineClass(byte[] b, int off, int len)
defineClass()方法是用來將byte字節流解析成JVM能夠識別的Class對象(ClassLoader中已實現該方法邏輯),通過這個方法不僅能夠通過class文件實例化class對象,也可以通過其他方式實例化class對象,如通過網絡接收一個類的字節碼,然后轉換為byte字節流創建對應的Class對象,defineClass()方法通常與findClass()方法一起使用,一般情況下,在自定義類加載器時,會直接覆蓋ClassLoader的findClass()方法並編寫加載規則,取得要加載類的字節碼后轉換成流,然后調用defineClass()方法生成類的Class對象
resolveClass(Class≺?≻ c)
使用該方法可以使用類的Class對象創建完成也同時被解析。前面我們說鏈接階段主要是對字節碼進行驗證,為類變量分配內存並設置初始值同時將字節碼文件中的符號引用轉換為直接引用。
3,熱部署
對於Java應用程序來說,熱部署就是在運行時更新Java類文件。
通俗的說,項目在運行過程中,已經部署到服務器的項目,發現邏輯有問題,需要修改代碼,但是又不想在修改完畢之后重新部署,這時候就需要熱部署的方法。
熱部署就是直接修改.class 文件,但是修改之后,並不會生效,因為之前版本的.class 文件已經在項目啟動的時候被類加載器讀取到內存中,而且這個過程只會發生一次(除非重新部署),所以我們要實現熱部署需要做的就是:
1,銷毀之前的ClassLoader加載的對象,讓System.gc(),提醒垃圾回收
2,更新字節碼文件
3,創建新的自定義ClassLoader 讀取字節碼文件
Java熱部署與熱加載的聯系
1.不重啟服務器編譯/部署項目
2.基於Java的類加載器實現
Java熱部署與熱加載的區別
部署方式
熱部署在服務器運行時重新部署項目
熱加載在運行時重新加載class
實現原理
熱部署直接重新加載整個應用
熱加載在運行時重新加載class
使用場景
熱部署更多的是在生產環境使用
熱加載則更多的實在開發環境使用
4,例子:
創建一個對象HelloWorld 類,里面打印Hello World 1,用記事本創建和HelloWorld類一樣的java 文件,打印的是Hello World 2,Hello World2 的類用javac 編譯,生成.class文件,在項目運行中先加載之前的class 文件,再用自定義的classLoader自動的去更新字節碼文件,查看輸出是否有變化。
package com.hella.hotswap; public class HelloWorld { public void search(){ System.out.println("Hello World 1"); } }
創建對象:
package com.hella.hotswap; public class HelloWorld { public void search(){ System.out.println("Hello World 2"); } }
//自定義ClassLoader
public class ClassLoaderDemo extends ClassLoader { // name 是一個全路徑 如 com.baidu.dev.HelloWorld @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { // 文件名稱 String fileName = "/"+ name.replace(".", "/")+".class"; // 獲取文件輸入流 InputStream is = this.getClass().getResourceAsStream(fileName); //加 / 代表從classes 的根目錄下開始找 // 讀取字節 byte[] b = new byte[is.available()]; is.read(b); // 將byte字節流解析成jvm能夠識別的Class對象 return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(); } } }
測試:
import java.io.File; import java.lang.reflect.Method; public class App { public static void main(String[] args) throws Exception { loadObject(); System.gc(); Thread.sleep(1000);// 等待資源回收 // 需要被熱部署的class文件 File file1 = new File("C:\\Users\\caich5\\Desktop\\test\\HelloWorld.class"); // 之前編譯好的class文件 File file2 = new File( "C:\\Users\\caich5\\workspace\\JvmWeb\\target\\classes\\com\\hella\\hotswap\\HelloWorld.class"); boolean isDelete = file2.delete();// 刪除舊版本的class文件 if (!isDelete) { System.out.println("熱部署失敗."); return; } file1.renameTo(file2); System.out.println("update success!"); loadObject(); } private static void loadObject() throws Exception { ClassLoaderDemo classLoaderDemo = new ClassLoaderDemo(); Class<?> clazz = classLoaderDemo.findClass("com.hella.hotswap.HelloWorld"); Object object = clazz.newInstance(); //通過反射機制獲取方法 Method method = clazz.getMethod("search"); //反射機制對象執行方法 method.invoke(object); } }
打印:
Hello World 1
update success!
Hello World 2