1.概述
在Class文件格式與執(zhí)行引擎這部分,用戶的程序能直接影響的內(nèi)容并不多,以什么格式存儲(chǔ)、類型什么時(shí)候加載、怎樣連接、虛擬機(jī)如何執(zhí)行字節(jié)碼指令等都是由虛擬機(jī)直接控制。用戶程序控制的主要是字節(jié)碼生成與類加載器這兩部分功能。
2.案例分析
下面是關(guān)于類加載器和字節(jié)碼的案例各兩個(gè)。
2.1 Tomcat:正統(tǒng)的類加載器架構(gòu)
主流的Java Web服務(wù)器,都實(shí)現(xiàn)了自己定義的類加載器(一般不止一個(gè))。因?yàn)楣δ芙∪腤eb服務(wù)器需要解決如下的問題:
部署在同一個(gè)服務(wù)器上的兩個(gè)Web應(yīng)用程序使用的Java類庫(kù)可以實(shí)現(xiàn)相互隔離。兩個(gè)應(yīng)用程序可能會(huì)依賴同一個(gè)第三方類庫(kù)。
部署在同一個(gè)服務(wù)器上的兩個(gè)Web應(yīng)用程序所使用的Java類庫(kù)可以互相共享。例如10各Spring應(yīng)用程序部署在同一個(gè)服務(wù)器上,如果類庫(kù)不能共享,類庫(kù)使用時(shí)都被加載到服務(wù)器內(nèi)存,虛擬機(jī)的方法區(qū)很容易出現(xiàn)過度膨脹。
服務(wù)器需要盡可能的保證自身的安全不受部署的Web應(yīng)用程序影響。一般來說,基于安全考慮,服務(wù)器使用的類庫(kù)應(yīng)該與應(yīng)用程序的類庫(kù)互相獨(dú)立。
支持JSP應(yīng)用的Web服務(wù)器,大多數(shù)需要支持HotSwap功能。"主流"的Web服務(wù)器都會(huì)支持JSP生成類的熱替換。
現(xiàn)在,筆者以Tomcat服務(wù)器為例,看一看Tomcat如何規(guī)劃用戶類庫(kù)結(jié)構(gòu)和類加載器的。
Tomcat目錄結(jié)構(gòu)中,有3組目錄("/common/"、"/server/"、"/shared/*")可以存放Java類庫(kù),另外還可以加上Web應(yīng)用程序自身的目錄"/WEB-INF",一共4組。
/common目錄下:類庫(kù)可被Tomcat和所有的Web應(yīng)用程序共同使用。
/server目錄下:類庫(kù)可被Tomcat使用,對(duì)所有的Web應(yīng)用程序都不可見。
/shared目錄:類庫(kù)可被所有的Web應(yīng)用程序共同使用,對(duì)Tomcat不可見。
/WEB-INF:類庫(kù)僅僅可以被此Web應(yīng)用使用,對(duì)Tomcat和其他Web應(yīng)用程序不可見。
為此,Tomcat自定義類多個(gè)類加載器。關(guān)系如下圖所示:

2.2 OSGi:靈活的類加載器架構(gòu)
OSGi的每個(gè)模塊(Bundle)與普通的Java類庫(kù)區(qū)別不大。一般都以JAR格式進(jìn)行封裝,內(nèi)部存儲(chǔ)的是Java Package和Class。但一個(gè)Bundle可以聲明它所依賴但Java Package(通過Import-Package描述),也可以聲明它允許導(dǎo)出發(fā)布的Java package(通過Export-package描述)。
OSGi中,Bundle之間的依賴關(guān)系從傳統(tǒng)的上層模塊依賴底層模塊轉(zhuǎn)變?yōu)槠郊?jí)模塊之間的依賴,而且類庫(kù)的可見性由很準(zhǔn)確的控制,一個(gè)模塊只有被Export過的Package才能由外界訪問;它還很可能實(shí)現(xiàn)模塊級(jí)的熱插拔功能,當(dāng)程序升級(jí)或調(diào)試時(shí),可以只停用、重新安裝其中一部分。
OSGi之所以可以這樣,是因?yàn)樗`活的類加載器。OSGi的Bundle類加載器之間只有規(guī)則,沒有固定的委派關(guān)系。例如:某個(gè)Bundle聲明了一個(gè)它依賴的Package,如果其他的Bundle聲明發(fā)布了這個(gè)Package,那么所有對(duì)這個(gè)Package對(duì)類加載器動(dòng)作都會(huì)委派給發(fā)布它的Bundle類加載器去完成。
另外,一個(gè)Bundle類加載器為其他Bundle提供服務(wù)時(shí),會(huì)根據(jù)Export-Package列表嚴(yán)格控制訪問范圍。如果一個(gè)類存在于Bundle的類庫(kù)中但沒有被export,那么這個(gè)Bundle的類加載器能找到這個(gè)類,但不會(huì)提供給其他Bundle使用。
觀察下面圖片:

BundleA:聲明發(fā)布了packageA,依賴java.*的包。
BundleB:聲明依賴了packageA,packageC,同時(shí)依賴java.*的包。
BundleC:聲明發(fā)布了packageC,依賴packageA。
從圖中可以看出,OSGi里面,加載器之間不再是雙親委派模型的樹形結(jié)構(gòu),而是更復(fù)雜的、運(yùn)行時(shí)才能確定的網(wǎng)狀結(jié)構(gòu)。
2.3 字節(jié)碼生成技術(shù)與動(dòng)態(tài)代理的實(shí)現(xiàn)
使用字節(jié)碼生成的例子很多,如Web服務(wù)器的JSP編譯器,編譯時(shí)植入的AOP框架,動(dòng)態(tài)代理技術(shù)等。
動(dòng)態(tài)代理所謂的"動(dòng)態(tài)",是針對(duì)使用java代碼實(shí)際編寫了代理類的"靜態(tài)"代理而言,它的優(yōu)勢(shì)不在于省去了編寫代理類那一點(diǎn)工作量,而是實(shí)現(xiàn)了可以再原始類和接口還未知的時(shí)候,就確定代理類的行為。
下面看一個(gè)簡(jiǎn)單示例:
public class DynamicProxyTest {
interface IHello{
void sayHello();
}
static class Hello implements IHello{
@Override
public void sayHello() {
System.out.println("hello world!");
}
}
static class DynamicProxy implements InvocationHandler{
Object originalObj;
Object bind(Object originalObj){
this.originalObj = originalObj;
return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(),
originalObj.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("dancing????");
return method.invoke(originalObj,args);
}
}
public static void main(String[] args) {
IHello hello = (IHello) new DynamicProxy().bind(new Hello());
hello.sayHello();
}
}
結(jié)果:
dancing????
hello world!
上述代碼里,特殊之處就是Proxy.newProxyInstance()方法,跟蹤這個(gè)方法的源碼,可以看到程序進(jìn)行了驗(yàn)證、優(yōu)化、緩存、同步、生成字節(jié)碼、顯式類加載等操作,而最后它調(diào)用了sun.misc.ProxyGenerator.generateProxyClass()方法完成生成字節(jié)碼的動(dòng)作。這個(gè)方法可以再運(yùn)行時(shí)產(chǎn)生一個(gè)描述代理類的字節(jié)碼byte[]數(shù)組。
如果想看運(yùn)行時(shí)代理類寫了什么,可以再main()方法中加入下面這句:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
運(yùn)行程序,磁盤中會(huì)產(chǎn)生一個(gè)文件"$Proxy.class"的代理類Class文件,如下:
import DynamicProxyTest.IHello;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
final class $Proxy0 extends Proxy implements IHello {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void sayHello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("DynamicProxyTest$IHello").getMethod("sayHello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
大致的生成過程是根據(jù)Class文件的格式規(guī)范去拼裝字節(jié)碼。如果對(duì)字節(jié)碼拼裝過程感興趣,可以再OpenJDK的/jdk/src/share/classes/sun/misc目錄下找到sun.misc.ProxyGenerator的源碼。
2.4 Retrotranslator:跨越JDK版本
Java中,每次JDK大版本的發(fā)布,都伴隨著一場(chǎng)大規(guī)模的技術(shù)革新,有時(shí)候會(huì)因?yàn)橐Wo(hù)現(xiàn)有投資、維持程序結(jié)構(gòu)穩(wěn)定,需要使用以前的版本的JDK。于是一種"Java逆向移植"的工具應(yīng)運(yùn)而生,Retrotranslator是最典型的一個(gè)。
舉例來說,它可以將JDK1.5版本編譯出來的Class文件轉(zhuǎn)變?yōu)?.4版本上部署的版本,可以很好的支持自動(dòng)裝箱、泛型、動(dòng)態(tài)注解、枚舉、變長(zhǎng)參數(shù)、遍歷循環(huán)、靜態(tài)導(dǎo)入等,還可以支持1.5中新增的集合改進(jìn)、并發(fā)包以及對(duì)泛型、注解等反射操作。
想知道它如何做到,首先要弄清楚JDK升級(jí)中會(huì)提供哪些功能。JDK每次升級(jí)大致分為4類:
編譯器層面做的改進(jìn)。如自動(dòng)裝箱拆箱,實(shí)際上就是編譯器在程序中使用包裝對(duì)象的地方自動(dòng)插入了Integer.valueOf();變長(zhǎng)參數(shù)在編譯后自動(dòng)轉(zhuǎn)化成一個(gè)數(shù)組完成參數(shù)傳遞;
對(duì)Java API的代碼增強(qiáng)。引入新的包。
在字節(jié)碼中進(jìn)行支持的改動(dòng)。如JDK1.7里新的語(yǔ)法特性:動(dòng)態(tài)語(yǔ)言支持。
虛擬機(jī)內(nèi)部改進(jìn)。
上述4類,Retrotranslator只能模擬前2類,例如JDK1.5引入的java.util.concurrent包,要在舊的JDK中支持這部分功能,以獨(dú)立類庫(kù)的方法可以實(shí)現(xiàn)。Retrotranslator中附帶類"backport-util-concurrent.jar"的類庫(kù)。
至于JDK在編譯階段進(jìn)行處理的改進(jìn),Retrotranslator使用ASM框架直接對(duì)字節(jié)碼進(jìn)行處理。例如枚舉,Retrotranslator對(duì)枚舉所做的主要處理是把枚舉類的父類從"java.lang.Enum"替換為它運(yùn)行時(shí)類庫(kù)中包含的"net.sf.retrotranslator.runtime.java.lang.Enum",然后在類和字段的訪問標(biāo)志中抹去ACC_ENUM標(biāo)志位。