java逻辑第九章_深入理解jvm-(第九章)类加载及执行子系统的案例与实战
轉載自:http://blog.csdn.net/coslay/article/details/49564789
概述
在Class文件格式與執行引擎這部分中,用戶的程序能直接影響的內容并不太多,
Class文件以何種格式存儲,類型何時加載、如何連接,以及虛擬機如何執行字節碼指令等都是由虛擬機直接控制的行為,用戶程序無法對其進行改變。能通過程序進行操作的,主要是字節碼生成與類加載器這兩部分的功能,但僅僅在如何處理這兩點上,就已經出現了許多值得欣賞和借鑒的思路,這些思路后來成為了許多常用功能和程序實現的基礎。
案例分析
Tomcat:正統的類加載器架構
主流的Java Web服務器
,如Tomcat、Jetty、WebLogic、WebSphere或其他筆者沒有列舉的服務器,都實現了自己定義的類加載器(一般都不止一個)。因為一個功能健全的Web服務器
,要解決如下幾個問題:
部署在同一個服務器上的兩個Web應用程序所使用的Java類庫可以實現相互隔離。這是最基本的需求,兩個不同的應用程序可能會依賴同一個第三方類庫的不同版本,不能要求一個類庫在一個服務器中只有一份,服務器應當保證兩個應用程序的類庫可以互相獨立使用。
部署在同一個服務器上的兩個Web應用程序所使用的Java類庫可以互相共享。這個需求也很常見,例如,用戶可能有10個使用Spring組織的應用程序部署在同一臺服務器上,如果把10份Spring分別存放在各個應用程序的隔離目錄中,將會是很大的資源浪費——這主要倒不是浪費磁盤空間的問題,而是指類庫在使用時都要被加載到服務器內存,如果類庫不能共享
,虛擬機的方法區就會很容易出現過度膨脹的風險。
服務器需要盡可能地保證自身的安全不受部署的Web應用程序影響。目前,有許多主流的Java
Web服務器自身也是使用Java語言來實現的。因此
,服務器本身也有類庫依賴的問題,一般來說,基于安全考慮,服務器所使用的類庫應該與應用程序的類庫互相獨立。
支持JSP應用的Web服務器 ,大多數都需要支持HotSwap功能。我們知道,JSP文件最終要編譯成Java
Class才能由虛擬機執行,但JSP文件由于其純文本存儲的特性,運行時修改的概率遠遠大于第三方類庫或程序自身的Class文件。而且ASP、PHP和JSP這些網頁應用也把修改后無須重啟作為一個很大的“優勢”來看待,因此“主流”的Web服務器都會支持JSP生成類的熱替換,當然也有“非主流”的
,如運行在生產模式( Production Mode ) 下的WebLogic服務器默認就不會處理JSP文件的變化。
由于存在上述問題,在部署Web應用時
,單獨的一個ClassPath就無法滿足需求了,所以各種Web服務器都“不約而同”地提供了好幾個ClassPath路徑供用戶存放第三方類庫,這些路徑一般都以“lib”或“classes”命名。被放置到不同路徑中的類庫,具備不同的訪問范圍和服務對象,通常,每一個目錄都會有一個相應的自定義類加載器去加載放置在里面的Java類庫。
現在 ,筆者就以Tomcat服務器為例,看一看Tomcat具體是如何規劃用戶類庫結構和類加載器的。
在Tomcat目錄結構中,有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所做的事情僅僅是公開父類(即
中的protected方法defineClass()
,我們將會使用這個方法把提交執行的
或findClass() 方法 ,因此如果不算外部手工調用loadByte()
方法的話,這個類加載器的類查找范圍與它的父類加載器是完全一致的,在被虛擬機調用時,它會按照雙親委派模型交給父類加載。構造函數中指定為加載HotSwapClassLoader類的類加載器也為父類加載器,這一步是實現提交的執行代碼可以訪問服務端引用類庫的關鍵,下面我們來看看代碼清單9-3。
第二個類是實現將java.lang.System替換為我們自己定義的HackSystem類的過程,它直接修改符合Class文件格式的byte[]數組中的常量池部分,將常量池中指定內容的
CONSTANT_UtfB_info常量替換為新的字符串,具體代碼如代碼清單9-4所示。
ClassModifier中涉及對byte[]數組操作的部分,主要是將byte[]與int和String互相轉換,以及把對byte[]數據的替換操作封裝在代碼清單9-5所示的ByteUtils中。
代碼清單9-4
ClassModifier的實現
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的實現
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
經過ClassModifier處理后的byte[]數組才會傳給HotSwapClassLoader.loadByte()方法進行類加載,byte[]數組在這里替換符號引用之后,與客戶端直接在
,又避免了服務端修改標準輸出后影響到其他程序的 輸出。下面我們來看看代碼清單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的名稱一樣
// 實現都是字節轉調System的對應方法
// 因版面原因,省略了其他方法
}
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個支持類已經講解完畢,我們來看看最后一個類JavaClassExecuter ,
它是提供給外部調用的入口,調用前面幾個支持類組裝邏輯,完成類加載工作。方法,如果期間出現任何異常,將異常信息打印到HackSystemout中,最后把緩沖區中的信息、作為方法的結果返回。JavaClassExecuter的實現代碼如代運清單9-
7所示。
代碼清單9-7
JavaClassExecuter的實現
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
驗證
遠程執行功能的編碼到此就完成了,接下來就要檢驗一下我們的勞動成果了。如果只是測試的話,那么可以任意寫一個Java類
,內容無所謂,只要向System.out輸出信息即可,取名為TestClass,
同時放到服務器C盤的根目錄中。然后,建立一個JSP文件并加入如代碼清單9-
8所示的內容,就可以在瀏覽器中看到這個類的運行結果了。
總結
以上是生活随笔為你收集整理的java逻辑第九章_深入理解jvm-(第九章)类加载及执行子系统的案例与实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电动车断电下坡滑行感觉有阻力并伴有声响是
- 下一篇: 在java中如何实现声音,我如何在Jav