欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 运维知识 > windows >内容正文

windows

java逻辑第九章_深入理解jvm-(第九章)类加载及执行子系统的案例与实战

发布时间:2025/4/5 windows 53 豆豆
生活随笔 收集整理的这篇文章主要介绍了 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-(第九章)类加载及执行子系统的案例与实战的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。