java逻辑第九章_深入理解jvm-(第九章)类加载及执行子系统的案例与实战
轉(zhuǎn)載自:http://blog.csdn.net/coslay/article/details/49564789
概述
在Class文件格式與執(zhí)行引擎這部分中,用戶的程序能直接影響的內(nèi)容并不太多,
Class文件以何種格式存儲(chǔ),類型何時(shí)加載、如何連接,以及虛擬機(jī)如何執(zhí)行字節(jié)碼指令等都是由虛擬機(jī)直接控制的行為,用戶程序無(wú)法對(duì)其進(jìn)行改變。能通過(guò)程序進(jìn)行操作的,主要是字節(jié)碼生成與類加載器這兩部分的功能,但僅僅在如何處理這兩點(diǎn)上,就已經(jīng)出現(xiàn)了許多值得欣賞和借鑒的思路,這些思路后來(lái)成為了許多常用功能和程序?qū)崿F(xiàn)的基礎(chǔ)。
案例分析
Tomcat:正統(tǒng)的類加載器架構(gòu)
主流的Java Web服務(wù)器
,如Tomcat、Jetty、WebLogic、WebSphere或其他筆者沒(méi)有列舉的服務(wù)器,都實(shí)現(xiàn)了自己定義的類加載器(一般都不止一個(gè))。因?yàn)橐粋€(gè)功能健全的Web服務(wù)器
,要解決如下幾個(gè)問(wèn)題:
部署在同一個(gè)服務(wù)器上的兩個(gè)Web應(yīng)用程序所使用的Java類庫(kù)可以實(shí)現(xiàn)相互隔離。這是最基本的需求,兩個(gè)不同的應(yīng)用程序可能會(huì)依賴同一個(gè)第三方類庫(kù)的不同版本,不能要求一個(gè)類庫(kù)在一個(gè)服務(wù)器中只有一份,服務(wù)器應(yīng)當(dāng)保證兩個(gè)應(yīng)用程序的類庫(kù)可以互相獨(dú)立使用。
部署在同一個(gè)服務(wù)器上的兩個(gè)Web應(yīng)用程序所使用的Java類庫(kù)可以互相共享。這個(gè)需求也很常見(jiàn),例如,用戶可能有10個(gè)使用Spring組織的應(yīng)用程序部署在同一臺(tái)服務(wù)器上,如果把10份Spring分別存放在各個(gè)應(yīng)用程序的隔離目錄中,將會(huì)是很大的資源浪費(fèi)——這主要倒不是浪費(fèi)磁盤空間的問(wèn)題,而是指類庫(kù)在使用時(shí)都要被加載到服務(wù)器內(nèi)存,如果類庫(kù)不能共享
,虛擬機(jī)的方法區(qū)就會(huì)很容易出現(xiàn)過(guò)度膨脹的風(fēng)險(xiǎn)。
服務(wù)器需要盡可能地保證自身的安全不受部署的Web應(yīng)用程序影響。目前,有許多主流的Java
Web服務(wù)器自身也是使用Java語(yǔ)言來(lái)實(shí)現(xiàn)的。因此
,服務(wù)器本身也有類庫(kù)依賴的問(wèn)題,一般來(lái)說(shuō),基于安全考慮,服務(wù)器所使用的類庫(kù)應(yīng)該與應(yīng)用程序的類庫(kù)互相獨(dú)立。
支持JSP應(yīng)用的Web服務(wù)器 ,大多數(shù)都需要支持HotSwap功能。我們知道,JSP文件最終要編譯成Java
Class才能由虛擬機(jī)執(zhí)行,但JSP文件由于其純文本存儲(chǔ)的特性,運(yùn)行時(shí)修改的概率遠(yuǎn)遠(yuǎn)大于第三方類庫(kù)或程序自身的Class文件。而且ASP、PHP和JSP這些網(wǎng)頁(yè)應(yīng)用也把修改后無(wú)須重啟作為一個(gè)很大的“優(yōu)勢(shì)”來(lái)看待,因此“主流”的Web服務(wù)器都會(huì)支持JSP生成類的熱替換,當(dāng)然也有“非主流”的
,如運(yùn)行在生產(chǎn)模式( Production Mode ) 下的WebLogic服務(wù)器默認(rèn)就不會(huì)處理JSP文件的變化。
由于存在上述問(wèn)題,在部署Web應(yīng)用時(shí)
,單獨(dú)的一個(gè)ClassPath就無(wú)法滿足需求了,所以各種Web服務(wù)器都“不約而同”地提供了好幾個(gè)ClassPath路徑供用戶存放第三方類庫(kù),這些路徑一般都以“l(fā)ib”或“classes”命名。被放置到不同路徑中的類庫(kù),具備不同的訪問(wèn)范圍和服務(wù)對(duì)象,通常,每一個(gè)目錄都會(huì)有一個(gè)相應(yīng)的自定義類加載器去加載放置在里面的Java類庫(kù)。
現(xiàn)在 ,筆者就以Tomcat服務(wù)器為例,看一看Tomcat具體是如何規(guī)劃用戶類庫(kù)結(jié)構(gòu)和類加載器的。
在Tomcat目錄結(jié)構(gòu)中,有3組目錄(“/common public class HotSwapClassLoader extends ClassLoader { public HotSwapClassLoader()
{ super(HotSwapClassLoader.class.getClassLoader()); }
public Class loadByte(byte[]
classByte) { return defineClass(null,
classByte, 0, classByte.length); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HotSwapClassLoader所做的事情僅僅是公開(kāi)父類(即
中的protected方法defineClass()
,我們將會(huì)使用這個(gè)方法把提交執(zhí)行的
或findClass() 方法 ,因此如果不算外部手工調(diào)用loadByte()
方法的話,這個(gè)類加載器的類查找范圍與它的父類加載器是完全一致的,在被虛擬機(jī)調(diào)用時(shí),它會(huì)按照雙親委派模型交給父類加載。構(gòu)造函數(shù)中指定為加載HotSwapClassLoader類的類加載器也為父類加載器,這一步是實(shí)現(xiàn)提交的執(zhí)行代碼可以訪問(wèn)服務(wù)端引用類庫(kù)的關(guān)鍵,下面我們來(lái)看看代碼清單9-3。
第二個(gè)類是實(shí)現(xiàn)將java.lang.System替換為我們自己定義的HackSystem類的過(guò)程,它直接修改符合Class文件格式的byte[]數(shù)組中的常量池部分,將常量池中指定內(nèi)容的
CONSTANT_UtfB_info常量替換為新的字符串,具體代碼如代碼清單9-4所示。
ClassModifier中涉及對(duì)byte[]數(shù)組操作的部分,主要是將byte[]與int和String互相轉(zhuǎn)換,以及把對(duì)byte[]數(shù)據(jù)的替換操作封裝在代碼清單9-5所示的ByteUtils中。
代碼清單9-4
ClassModifier的實(shí)現(xiàn)
public class ClassModifier {
private static final int CONSTANT_POOL_COUNT_INDEX = 8;
private static final int CONSTANT_Utf8_info = 1;
private static final int[] CONSTANT_ITEM_LENGTH = { -1, -1, -1, 5, 5, 9, 9, 3, 3, 5, 5, 5, 5 };
private static final int u1 = 1;
private static final int u2 = 2;
private byte[] classByte;
public ClassModifier(byte[] classByte) {
this.classByte = classByte;
}
public byte[] modifyUTF8Constant(String oldStr, String newStr) {
int cpc = getConstantPoolCount();
int offset = CONSTANT_POOL_COUNT_INDEX + u2;
for (int i = 0; i < cpc; i++) {
int tag = ByteUtils.bytes2Int(classByte, offset, u1);
if (tag == CONSTANT_Utf8_info) {
int len = ByteUtils.bytes2Int(classByte, offset + u1, u2);
offset += (u1 + u2);
String str = ByteUtils.bytes2String(classByte, offset, len);
if (str.equalsIgnoreCase(oldStr)) {
byte[] strBytes = ByteUtils.string2Bytes(newStr);
byte[] strLen = ByteUtils.int2Bytes(newStr.length(), u2);
classByte = ByteUtils.bytesReplace(classByte, offset - u2, u2, strLen);
classByte = ByteUtils.bytesReplace(classByte, offset, len, strBytes);
return classByte;
} else {
offset += len;
}
} else {
offset += CONSTANT_ITEM_LENGTH[tag];
}
}
return classByte;
}
public int getConstantPoolCount() {
return ByteUtils.bytes2Int(classByte, CONSTANT_POOL_COUNT_INDEX, u2);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
代碼清單9-5
ByteUtils的實(shí)現(xiàn)
public class ByteUtils {
public static int bytes2Int(byte[] b, int start, int len) {
int sum = 0;
int end = start + len;
for (int i = start; i < end; i++) {
int n = ((int) b[i]) & 0xff;
n <<= (--len) * 8;
sum = n + sum;
}
return sum;
}
public static byte[] int2Bytes(int value, int len) {
byte[] b = new byte[len];
for (int i = 0; i < len; i++) {
b[len - i - 1] = (byte) ((value >> 8 * i) & 0xff);
}
return b;
}
public static String bytes2String(byte[] b, int start, int len) {
return new String(b, start, len);
}
public static byte[] string2Bytes(String str) {
return str.getBytes();
}
public static byte[] bytesReplace(byte[] originalBytes, int offset, int len, byte[] replaceBytes) {
byte[] newBytes = new byte[originalBytes.length + (replaceBytes.length - len)];
System.arraycopy(originalBytes, 0, newBytes, 0, offset);
System.arraycopy(replaceBytes, 0, newBytes, offset, replaceBytes.length);
System.arraycopy(originalBytes, offset + len, newBytes, offset + replaceBytes.length, originalBytes.length - offset - len);
return newBytes;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
經(jīng)過(guò)ClassModifier處理后的byte[]數(shù)組才會(huì)傳給HotSwapClassLoader.loadByte()方法進(jìn)行類加載,byte[]數(shù)組在這里替換符號(hào)引用之后,與客戶端直接在
,又避免了服務(wù)端修改標(biāo)準(zhǔn)輸出后影響到其他程序的 輸出。下面我們來(lái)看看代碼清單9-4和代碼清單9-5。
public class HackSystem {
public final static InputStream in = System.in;
private static ByteArrayOutputStream buffer = new ByteArrayOutputStream();
public final static PrintStream out = new PrintStream(buffer);
public final static PrintStream err = out;
public static String getBufferString() {
return buffer.toString();
}
public static void clearBuffer() {
buffer.reset();
}
public static void setSecurityManager(final SecurityManager s) {
System.setSecurityManager(s);
}
public static SecurityManager getSecurityManager() {
return System.getSecurityManager();
}
public static long currentTimeMillis() {
return System.currentTimeMillis();
}
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) {
System.arraycopy(src, srcPos, dest, destPos, length);
}
public static int identityHashCode(Object x) {
return System.identityHashCode(x);
}
// 下面所有的方法都與java.lang.System的名稱一樣
// 實(shí)現(xiàn)都是字節(jié)轉(zhuǎn)調(diào)System的對(duì)應(yīng)方法
// 因版面原因,省略了其他方法
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
至此,
4個(gè)支持類已經(jīng)講解完畢,我們來(lái)看看最后一個(gè)類JavaClassExecuter ,
它是提供給外部調(diào)用的入口,調(diào)用前面幾個(gè)支持類組裝邏輯,完成類加載工作。方法,如果期間出現(xiàn)任何異常,將異常信息打印到HackSystemout中,最后把緩沖區(qū)中的信息、作為方法的結(jié)果返回。JavaClassExecuter的實(shí)現(xiàn)代碼如代運(yùn)清單9-
7所示。
代碼清單9-7
JavaClassExecuter的實(shí)現(xiàn)
public class JavaClassExecuter {
public static String execute(byte[] classByte) {
HackSystem.clearBuffer();
ClassModifier cm = new ClassModifier(classByte);
byte[] modiBytes = cm.modifyUTF8Constant("java/lang/System", "org/fenixsoft/classloading/execute/HackSystem");
HotSwapClassLoader loader = new HotSwapClassLoader();
Class clazz = loader.loadByte(modiBytes);
try {
Method method = clazz.getMethod("main", new Class[] { String[].class });
method.invoke(null, new String[] { null });
} catch (Throwable e) {
e.printStackTrace(HackSystem.out);
}
return HackSystem.getBufferString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
驗(yàn)證
遠(yuǎn)程執(zhí)行功能的編碼到此就完成了,接下來(lái)就要檢驗(yàn)一下我們的勞動(dòng)成果了。如果只是測(cè)試的話,那么可以任意寫(xiě)一個(gè)Java類
,內(nèi)容無(wú)所謂,只要向System.out輸出信息即可,取名為TestClass,
同時(shí)放到服務(wù)器C盤的根目錄中。然后,建立一個(gè)JSP文件并加入如代碼清單9-
8所示的內(nèi)容,就可以在瀏覽器中看到這個(gè)類的運(yùn)行結(jié)果了。
總結(jié)
以上是生活随笔為你收集整理的java逻辑第九章_深入理解jvm-(第九章)类加载及执行子系统的案例与实战的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 电动车断电下坡滑行感觉有阻力并伴有声响是
- 下一篇: 在java中如何实现声音,我如何在Jav