转载自: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-(第九章)类加载及执行子系统的案例与实战相关推荐

  1. 深入理解Java虚拟机(第二版) 第九章:类加载及执行子系统的案例与实战

    第九章 类加载及执行子系统的案例与实战 9.1 概述 9.2 Tomcat: 正统的类加载器架构 9.3 OSGi:灵活的类加载器架构 9.4 字节码生成技术与动态代理的实现 9.5 Retrotra ...

  2. JVM——类加载及执行子系统的案例与实战(Tomcat)

    摘要 本文将深入的学习与分析JVM虚拟机的原理和相关的调优的相关实例. 类加载及执行子系统的案例与实战 Tomcat: 正统的类加载器架构 主流的Java Web服务器, 如Tomcat. Jetty ...

  3. java visualvm远程监控_深入理解JVM虚拟机12:JVM性能管理神器VisualVM介绍与实战

    本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutori ...

  4. java虚拟机内存监控_深入理解JVM虚拟机9:JVM监控工具与诊断实践

    本文转自: https://juejin.im/post/59e6c1f26fb9a0451c397a8c 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到 ...

  5. fegin需要实现类_深入理解JVM(六)--虚拟机类加载机制

    虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 类从被加载到虚拟机内存开始,到卸载出内存为止 ...

  6. 【深入理解JVM】:类加载器与双亲委派模型

    转载自  [深入理解JVM]:类加载器与双亲委派模型 类加载器 加载类的开放性 类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因.在类加载的第一阶段" ...

  7. 商用短链平台_第8章_ 账号微服务注册模块+短信验证码+阿里云OSS开发实战

    商用短链平台_第8章_ 账号微服务注册模块+短信验证码+阿里云OSS开发实战 文章目录 商用短链平台_第8章_ 账号微服务注册模块+短信验证码+阿里云OSS开发实战 第八章 账号微服务注册模块+短信验 ...

  8. java jvm垃圾回收算法_深入理解JVM虚拟机2:JVM垃圾回收基本原理和算法

    本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 喜欢的话麻烦点下Star哈 文章将同步到我的个人博客: www.how ...

  9. java 句柄池_深入理解JVM之Java对象的创建、内存布局、访问定位详解

    本文实例讲述了深入理解JVM之Java对象的创建.内存布局.访问定位.分享给大家供大家参考,具体如下: 对象的创建 一个简单的创建对象语句Clazz instance = new Clazz();包含 ...

最新文章

  1. main方法_错误: 在类 ZiFUChuan.Pyramid 中找不到 main 方法, 请将 main 方法定义为:
  2. Flutter开发之诊断布局调试工具:inspector(12)
  3. ant+svn+tomcat实现项目自动部署
  4. 【Android 应用开发】 ActionBar 样式详解 -- 样式 主题 简介 Actionbar 的 icon logo 标题 菜单样式修改
  5. 关于面试,我也有说的
  6. springmvc 配置和spring配置?
  7. Swift之深入解析可选链的功能和使用
  8. RabbitMQ, ZeroMQ, Kafka 是一个层级的东西吗, 相互之间有哪些优缺点
  9. mac obs 录屏黑屏_有了它,我把其他录屏软件都卸载了!
  10. java5 离线安装包_ElasticSearch 5.5 离线环境的完整安装及配置详情,附kibana、ik插件配置及安装包下载路径...
  11. 暨南大学人文社科a类期刊_关于调整人文社科B类和C类期刊目录的通知
  12. 数据库分页LIMIT
  13. SCC(五):ACT
  14. 无心剑汉英双语诗003. 《书海》
  15. Vscode Remote SSH 远程连接失败过程报错:试图写入的管道不存在
  16. 输入法的一些设置,以及解决 输入法 ctrl+c 等快捷键不能操作问题?
  17. 第一卷清晨的帝国 第一百五十一章 起步
  18. PHP之 微信支付 查询企业付款银行卡API 或 查询给企业付款是否到账 功能业务处理
  19. dubbo-admin0.3.0安装教程
  20. 【专题5: 硬件设计】 之 【16.二极管/三极管的钳位功能】

热门文章

  1. 自动登录126邮箱的脚本
  2. 输入对5层网络迭代次数的影响
  3. php 截取字符串的方法,php截取字符串的方法介绍
  4. 【Paper】2015_异构无人机群鲁棒一致性协议设计_孙长银
  5. Floats and marginpars not allowed inside `multicols' Unknown float option `H'. 基于LaTex+VSCode+MAC
  6. 【编程通识】正则表达式
  7. 【强化学习】DDPG
  8. 基于Simulink的高速跳频通信系统抗干扰性能分析
  9. python基础学习11----函数
  10. 目前中关村在线上面的CPU排行情况