日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java热替换

發(fā)布時(shí)間:2024/4/15 java 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java热替换 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前面有提到過Tomcat的熱部署,所謂熱部署就是在應(yīng)用運(yùn)行時(shí)更新Java類文件以升級軟件功能,升級過程不需要關(guān)停和重啟應(yīng)用。要進(jìn)行熱部署需要做class熱替換。Class熱替換實(shí)現(xiàn)了將修改的class再次加載到JVM中,以動(dòng)態(tài)替換內(nèi)存中原有的class字節(jié)碼。

實(shí)現(xiàn)class的熱替換就與Java類加載過程相關(guān),關(guān)于Java類加載過程的文章或書籍早些年就已經(jīng)很多了,這里從” 深入探討 Java 類加載器(http://www.ibm.com/developerworks/cn/java/j-lo-classloader/)’’一文中摘錄了部分內(nèi)容說明Java類的加載過程。

?

類加載器基本概念

基本上所有的類加載器都是 java.lang.ClassLoader類的一個(gè)實(shí)例。java.lang.ClassLoader類的基本職責(zé)就是根據(jù)一個(gè)指定的類的名稱,找到或者生成其對應(yīng)的字節(jié)代碼,然后從這些字節(jié)代碼中定義出一個(gè) Java 類,即 java.lang.Class類的一個(gè)實(shí)例。除此之外,ClassLoader還負(fù)責(zé)加載 Java 應(yīng)用所需的資源,如圖像文件和配置文件等。

Java 中的類加載器大致可以分成兩類,一類是系統(tǒng)提供的,另外一類則是由 Java 應(yīng)用開發(fā)人員編寫的。系統(tǒng)提供的類加載器主要有下面三個(gè):

? ?? 引導(dǎo)類加載器(bootstrap class loader):它用來加載 Java 的核心庫,是用原生代碼來實(shí)現(xiàn)的,并不繼承自 java.lang.ClassLoader。

? ?? 擴(kuò)展類加載器(extensions class loader):它用來加載 Java 的擴(kuò)展庫。Java 虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。

? ?? 系統(tǒng)類加載器(system class loader):它根據(jù) Java 應(yīng)用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應(yīng)用的類都是由它來完成加載的??梢酝ㄟ^ ClassLoader.getSystemClassLoader()來獲取它。

除了系統(tǒng)提供的類加載器以外,開發(fā)人員可以通過繼承 java.lang.ClassLoader類的方式實(shí)現(xiàn)自己的類加載器,以滿足一些特殊的需求。

?

除了引導(dǎo)類加載器之外,所有的類加載器都有一個(gè)父類加載器。通過getParent()方法可以得到。對于系統(tǒng)提供的類加載器來說,系統(tǒng)類加載器的父類加載器是擴(kuò)展類加載器,而擴(kuò)展類加載器的父類加載器是引導(dǎo)類加載器;對于開發(fā)人員編寫的類加載器來說,其父類加載器是加載此類加載器 Java 類的類加載器。因?yàn)轭惣虞d器 Java 類如同其它的 Java 類一樣,也是要由類加載器來加載的。一般來說,開發(fā)人員編寫的類加載器的父類加載器是系統(tǒng)類加載器。類加載器通過這種方式組織起來,形成樹狀結(jié)構(gòu)。樹的根節(jié)點(diǎn)就是引導(dǎo)類加載器。

?

1、Bootstrap Loader(啟動(dòng)類加載器):加載System.getProperty("sun.boot.class.path")所指定的路徑或jar。

2、Extended Loader(標(biāo)準(zhǔn)擴(kuò)展類加載器ExtClassLoader):加載System.getProperty("java.ext.dirs")所指定的路徑或jar。在使用Java運(yùn)行程序時(shí),也可以指定其搜索路徑.

3、AppClass Loader(系統(tǒng)類加載器AppClassLoader):加載System.getProperty("java.class.path")所指定的路徑或jar。在使用Java運(yùn)行程序時(shí),也可以加上-cp來覆蓋原有的Classpath設(shè)置.????????

?

類加載器的代理模式

類加載器在嘗試自己去查找某個(gè)類的字節(jié)代碼并定義它時(shí),會(huì)先代理給其父類加載器,由父類加載器先去嘗試加載這個(gè)類,依次類推。代理模式是為了保證 Java 核心庫的類型安全。通過代理模式,對于 Java 核心庫的類的加載工作由引導(dǎo)類加載器來統(tǒng)一完成,保證了 Java 應(yīng)用所使用的都是同一個(gè)版本的 Java 核心庫的類,是互相兼容的。

不同的類加載器為相同名稱的類創(chuàng)建了額外的名稱空間。相同名稱的類可以并存在 Java 虛擬機(jī)中,只需要用不同的類加載器來加載它們即可。不同類加載器加載的類之間是不兼容的,這就相當(dāng)于在 Java 虛擬機(jī)內(nèi)部創(chuàng)建了一個(gè)個(gè)相互隔離的 Java 類空間。

?

加載類的過程

真正完成類的加載工作是通過調(diào)用 defineClass來實(shí)現(xiàn)的;而啟動(dòng)類的加載過程是通過調(diào)用 loadClass來實(shí)現(xiàn)的。前者稱為一個(gè)類的定義加載器(defining loader),后者稱為初始加載器(initiating loader)。在 Java 虛擬機(jī)判斷兩個(gè)類是否相同的時(shí)候,使用的是類的定義加載器。也就是說,哪個(gè)類加載器啟動(dòng)類的加載過程并不重要,重要的是最終定義這個(gè)類的加載器。兩種類加載器的關(guān)聯(lián)之處在于:一個(gè)類的定義加載器是它引用的其它類的初始加載器。如類 com.example.Outer引用了類 com.example.Inner,則由類 com.example.Outer的定義加載器負(fù)責(zé)啟動(dòng)類 com.example.Inner的加載過程。

方法 loadClass()拋出的是 java.lang.ClassNotFoundException異常;方法 defineClass()拋出的是 java.lang.NoClassDefFoundError異常。

類加載器在成功加載某個(gè)類之后,會(huì)把得到的 java.lang.Class類的實(shí)例緩存起來。下次再請求加載該類的時(shí)候,類加載器會(huì)直接使用緩存的類的實(shí)例,而不會(huì)嘗試再次加載。也就是說,對于一個(gè)類加載器實(shí)例來說,相同全名的類只加載一次,即 loadClass方法不會(huì)被重復(fù)調(diào)用。

?

類加載過程:

1、尋找jre目錄,尋找jvm.dll,并初始化JVM;

2、產(chǎn)生一個(gè)Bootstrap Loader(啟動(dòng)類加載器);

3、Bootstrap Loader自動(dòng)加載Extended Loader(標(biāo)準(zhǔn)擴(kuò)展類加載器),并將其父Loader設(shè)為Bootstrap Loader。

4、Bootstrap Loader自動(dòng)加載AppClass Loader(系統(tǒng)類加載器),并將其父Loader設(shè)為Extended Loader。

5、最后由AppClass Loader加載應(yīng)用程序類。

?

我們再來回顧一下類的加載過程:前面提到,某一特定類加載器在加載類時(shí),首先將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;如果父類加載器無法完成加載,則由該特定類加載器完成加載。這個(gè)機(jī)制就是所謂的雙親委派機(jī)制。

?

我們再來看一下Java 虛擬機(jī)是如何判定兩個(gè) Java 類是相同的。Java 虛擬機(jī)不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。只有兩者都相同的情況,才認(rèn)為兩個(gè)類是相同的。即便是同樣的字節(jié)代碼,被不同的類加載器加載之后所得到的類,也是不同的。具體的示例可以參見”深入探討 Java 類加載器”一文。

?

了解了基本概念,我們知道要實(shí)現(xiàn)Java類的熱替換,首先就需要繼承java.lang.ClassLoader類實(shí)現(xiàn)自己的類加載器。

定義一個(gè)類加載器,如下:

public class MyClassLoader extends URLClassLoader {

??? public static Map<String, Long> cacheLastModifyTimeMap = new HashMap<String, Long>();

??? public static URL url = null;

?

??? public MyClassLoader(URL url) {

??????? super(new URL[]{url});

??????? this.url = url;

??? }

?

??? public Class<?> loadClass(String name) throws ClassNotFoundException {

??????? return loadClass(name, false);

??? }

?

??? @Override

??? public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

??????? Class clazz = null;

??????? //首先檢查請求的類型是否已經(jīng)被這個(gè)類裝載器裝載到命名空間中了,如果已經(jīng)裝載,直接返回;否則繼續(xù)。

??????? clazz = findLoadedClass(name); //查找名稱為 name的已經(jīng)被加載過的類

??????? if (clazz != null) {

??????????? if (resolve) {

??????????????? resolveClass(clazz); //鏈接指定的 Java 類

??????????? }

??????????? //如果class類被修改過,則重新加載

??????????? if (isModify(name)) {

??????????????? MyClassLoader hcl = new MyClassLoader(url);

??????????????? clazz = customLoad(name, hcl);

??????????? }

??????????? return (clazz);

??????? }

?

??????? //如果類的包名為"java."開始,則有系統(tǒng)默認(rèn)加載器加載

??????? if (!name.startsWith("org.jevo.")) {

??????????? try {

??????????????? //得到系統(tǒng)默認(rèn)的加載cl

??????????????? ClassLoader system = ClassLoader.getSystemClassLoader();

??????????????? clazz = system.loadClass(name);? //加載名稱為 name的類

??????????????? if (clazz != null) {

??????????????????? if (resolve)

??????????????????????? resolveClass(clazz);

??????????????????? return (clazz);

??????? ????????}

??????????? } catch (ClassNotFoundException e) {

??????????????? // Ignore

??????????? }

??????? }

?

??????? return customLoad(name, this);

??? }

?

??? public Class customLoad(String name, ClassLoader cl) throws ClassNotFoundException {

??????? return customLoad(name, false, cl);

??? }

?

??? public Class customLoad(String name, boolean resolve, ClassLoader cl)

??????????? throws ClassNotFoundException {

??????? // //調(diào)用本類加載器的findClass(…)方法,試圖獲取對應(yīng)的字節(jié)碼,如果獲取的到,則調(diào)用defineClass(…)導(dǎo)入類型到方法區(qū);否則拋出異常

??????? Class clazz = ((MyClassLoader) cl).findClass(name); //查找名稱為 name的類

??????? if (resolve)

??????????? ((MyClassLoader) cl).resolveClass(clazz);

??????? //緩存加載class文件的最后修改時(shí)間

??????? long lastModifyTime = getClassLastModifyTime(name);

??????? cacheLastModifyTimeMap.put(name, lastModifyTime);

??????? return clazz;

??? }

?

??? private boolean isModify(String name) {

??????? long lastmodify = getClassLastModifyTime(name);

??????? long previousModifyTime = cacheLastModifyTimeMap.get(name);

??????? if (lastmodify > previousModifyTime) {

??????????? return true;

??????? }

??????? return false;

??? }

?

??? private long getClassLastModifyTime(String name) {

??????? String path = getClassCompletePath(name);

??????? File file = new File(path);

??????? if (!file.exists()) {

?? ?????????throw new RuntimeException(new FileNotFoundException(name));

??????? }

??????? return file.lastModified();

??? }

?

??? private String getClassCompletePath(String name) {

??????? String simpleName = name.replaceAll("\\.", "/");

??????? return url.getPath() + simpleName + ".class";

??? }

}

?

測試代碼如下:

public class DyService {

??? public String doBusiness() {

//??????? return "do something here..";

??????? return "do otherthings here..";

??? }

}

?

public class Main {

??? static ClassLoader cl;

??? static Object server;

??? static Class hotClazz = null;

?

??? public static void loadNewVersionOfServer() throws Exception {

??????? synchronized (Main.class) {

??????????? if (cl == null)

??????????????? cl = new MyClassLoader(new URL("file://D:/Project/test/out/"));

??????? }

??????? hotClazz = cl.loadClass("org.jevo.hotswap.sample.DyService");

??????? server = hotClazz.newInstance();

??? }

?

??? public static void test() throws Exception {

??????? BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

??????? loadNewVersionOfServer();

??????? while (true) {

??????????? System.out.println("Enter DOBUS, RELOAD, or QUIT: ");

??????????? String cmdRead = br.readLine();

??????????? String cmd = cmdRead.toUpperCase();

??????????? if (cmd.equals("QUIT")) {

??????????????? return;

??????????? } else if (cmd.equals("DOBUS")) {

??????????????? Method m = hotClazz.getMethod("doBusiness");

??????????????? System.out.println(m.invoke(server, null)); //這里使用反射機(jī)制來執(zhí)行事務(wù)。

??????????? } else if (cmd.equals("RELOAD")) {

??????????????? loadNewVersionOfServer();

??????????? }

??????? }

??? }

?

??? public static void main(String[] args) {

??????? try {

??????????? test();

??????? } catch (Exception e) {

??????????? e.printStackTrace();

??????? }

??? }

}

?

在上面的代碼中,使用了反射機(jī)制來執(zhí)行事務(wù)。這里不能將hotClazz.newInstance()得到的實(shí)例強(qiáng)制轉(zhuǎn)換成DyService對象實(shí)例來使用doBussiness方法,否則會(huì)拋出java.lang.ClassCastException異常。拋出ClassCastException異常的原因就在于前面提到的“Java 虛擬機(jī)如何判定兩個(gè) Java 類是相同的”,這里例子中的hotClazz 是由MyClassLoader加載的,而server變量類型聲明和類是由loadNewVersionOfServer方法所屬的類的加載器加載的,因此屬于不同的兩個(gè)類型,轉(zhuǎn)換時(shí)并不兼容,所以會(huì)拋出ClassCastException異常。

同樣,在上面例子中,當(dāng)class類被修改過后重新加載時(shí),我們是通過重新new一個(gè)MyClassLoader來加載被修改類的,MyClassLoader hcl = new MyClassLoader(url);

在前面我們提到:對于一個(gè)類加載器實(shí)例來說,相同全名的類只加載一次。所以同一個(gè)ClassLoader實(shí)例只能加載Class一次,一個(gè)class被一個(gè)ClassLoader實(shí)例加載過的話,就不能再被這個(gè)ClassLoader實(shí)例再次加載,即不再重復(fù)調(diào)defineClass()方法關(guān)聯(lián)字節(jié)碼,重復(fù)裝載將拋出重復(fù)類定義異常。同樣,系統(tǒng)默認(rèn)的ClassLoader加載器內(nèi)部會(huì)緩存加載過的class,重新加載的話,就直接取緩存。因此這里只能重新創(chuàng)建一個(gè)ClassLoader,然后再去加載已經(jīng)被加載過的class文件。

?

AppClass Loader(系統(tǒng)類加載器AppClassLoader):加載System.getProperty("java.class.path")所指定的路徑或jar

?

前面我們通過模擬loadClass方法的過程來加載Java類,我們也提到” 真正完成類的加載工作是通過調(diào)用 defineClass來實(shí)現(xiàn)的;而啟動(dòng)類的加載過程是通過調(diào)用 loadClass來實(shí)現(xiàn)的?!蓖瑫r(shí)我們也知道系統(tǒng)類加載器一般加載ClassPath所指定的路徑下的class,所以我們可以使用類定義加載器來加載ClassPath指定路徑下的class來實(shí)現(xiàn)熱替換,代碼如下:

??????? String classPath = System.getProperty("java.class.path");

??????? List classRepository = new ArrayList();

?

??????? if ((classPath != null) && !(classPath.equals(""))) {

??????????? StringTokenizer tokenizer = new StringTokenizer(classPath,

??????? ????????????File.pathSeparator);

??????????? while (tokenizer.hasMoreTokens()) {

??????????????? classRepository.add(tokenizer.nextToken());

??????????? }

??????? }

??????? Iterator dirs = classRepository.iterator();

??????? byte[] classBytes = null;

?

??????? while (dirs.hasNext()) {

??????????? String dir = (String) dirs.next();

??????????? //replace '.' in the class name with File.separatorChar & append .class to the name

??????????? String classFileName = className.replace('.', File.separatorChar);

??????????? classFileName += ".class";

??????????? try {

??????????????? File file = new File(dir + File.separatorChar + classFileName);

??????????????? if (file.exists()) {

??????????????????? InputStream is = new FileInputStream(file);

?

????????????????? ??classBytes = new byte[is.available()];

??????????????????? is.read(classBytes);

??????????????????? break;

??????????????? }

??????????? }

??????????? catch (IOException ex) {

??????????????? System.out.println("IOException raised while reading class file data");

??????????????? ex.printStackTrace();

??????????????? return null;

??????????? }

??????? }

??????? return this.defineClass(className, classBytes, 0, classBytes.length);

?

我們知道,JVM中class和Meta信息存放在PermGen space區(qū)域。在上面的代碼中,如果加載的class文件很多,那么可能導(dǎo)致PermGen space區(qū)域空間溢出,即java.lang.OutOfMemoryErrorPermGen space.?異常。

‘Java虛擬機(jī)類型卸載和類型更新解析(http://www.blogjava.net/zhuxing/archive/2008/07/24/217285.html)’中作了一些說明,摘錄部分內(nèi)容如下:

首先看一下,關(guān)于java虛擬機(jī)規(guī)范中時(shí)如何闡述類型卸載(unloading)的:
????A class or interface may be unloaded if and only if its class loader is unreachable. The bootstrap class loader is always reachable; as a result, system classes may never be unloaded.
Java虛擬機(jī)規(guī)范中關(guān)于類型卸載的內(nèi)容就這么簡單兩句話,大致意思就是:只有當(dāng)加載該類型的類加載器實(shí)例(非類加載器類型)為unreachable狀態(tài)時(shí),當(dāng)前被加載的類型才被卸載.啟動(dòng)類加載器實(shí)例永遠(yuǎn)為reachable狀態(tài),由啟動(dòng)類加載器加載的類型可能永遠(yuǎn)不會(huì)被卸載.
我們再看一下Java語言規(guī)范提供的關(guān)于類型卸載的更詳細(xì)的信息(部分摘錄):
????//摘自JLS 12.7 Unloading of Classes and Interfaces
????1、An implementation of the Java programming language may unload classes.
????2、Class unloading is an optimization that helps reduce memory use. Obviously,the semantics of a program should not depend? on whether and how a system chooses to implement an optimization such as class unloading.
????3、Consequently,whether a class or interface has been unloaded or not should be transparent to a program
通過以上我們可以得出結(jié)論: 類型卸載(unloading)僅僅是作為一種減少內(nèi)存使用的性能優(yōu)化措施存在的,具體和虛擬機(jī)實(shí)現(xiàn)有關(guān),對開發(fā)者來說是透明的.
縱觀java語言規(guī)范及其相關(guān)的API規(guī)范,找不到顯示類型卸載(unloading)的接口, 換句話說:?
????1、一個(gè)已經(jīng)加載的類型被卸載的幾率很小至少被卸載的時(shí)間是不確定的
????2、一個(gè)被特定類加載器實(shí)例加載的類型運(yùn)行時(shí)可以認(rèn)為是無法被更新的

有 關(guān)unreachable狀態(tài)的解釋:
????1、A reachable object is any object that can be accessed in any potential continuing computation from any live thread.
????2、finalizer-reachable: A finalizer-reachable object can be reached from some finalizable object through some chain of references, but not from any live thread. An unreachable object cannot be reached by either means.

某種程度上講,在一個(gè)稍微復(fù)雜的java應(yīng)用中,我們很難準(zhǔn)確判斷出一個(gè)實(shí)例是否處于unreachable狀態(tài).

關(guān)于類型卸載大致概括為:

??? 1、有啟動(dòng)類加載器加載的類型在整個(gè)運(yùn)行期間是不可能被卸載的(jvm和jls規(guī)范).

??? 2、被系統(tǒng)類加載器和標(biāo)準(zhǔn)擴(kuò)展類加載器加載的類型在運(yùn)行期間不太可能被卸載,因?yàn)橄到y(tǒng)類加載器實(shí)例或者標(biāo)準(zhǔn)擴(kuò)展類的實(shí)例基本上在整個(gè)運(yùn)行期間總能直接或者間接的訪問的到,其達(dá)到unreachable的可能性極小.

??? 3、被開發(fā)者自定義的類加載器實(shí)例加載的類型只有在很簡單的上下文環(huán)境中才能被卸載,而且一般還要借助于強(qiáng)制調(diào)用虛擬機(jī)的垃圾收集功能才可以做到.

綜合以上三點(diǎn),我們可以默認(rèn)前面的結(jié)論 一個(gè)已經(jīng)加載的類型被卸載的幾率很小至少被卸載的時(shí)間是不確定的..

關(guān)于java.lang.OutOfMemoryErrorPermGen space異常,網(wǎng)上曾有過相關(guān)的討論,可通過搜索找到更多的內(nèi)容。

通過上面的說明,我們在自定義ClassLoader中實(shí)現(xiàn)加載過程時(shí),就可以將先將原加載器設(shè)置為null,相關(guān)的加載信息也都設(shè)置為null后再調(diào)用System.gc();作一次GC.

?

關(guān)于類加載的說明,有幾篇可作參考:

Java類加載原理解析 http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html

入探討 Java 類加載器 http://www.ibm.com/developerworks/cn/java/j-lo-classloader/

深入研究Java類加載機(jī)制? http://lavasoft.blog.51cto.com/62575/184547/

http://nonopo.iteye.com/blog/208007

http://nonopo.iteye.com/blog/208012

?

轉(zhuǎn)載于:https://www.cnblogs.com/jevo/archive/2013/04/22/3019573.html

總結(jié)

以上是生活随笔為你收集整理的Java热替换的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。