《深入理解java虚拟机》读书笔记之类加载案例实战

##前言

最近在读《深入理解java虚拟机》,第三部分-虚拟及执行子系统。作者从类文件结构讲到虚拟机类加载机制、虚拟机字节码执行引擎,最后还分析了几种常见的类加载器架构,其中我们就看到了熟悉的动态代理了,最后最后作者提供了一个实战例子:自己动手实现远程执行功能。

  在网络上搜索了一下,大部分笔记文章都是在总结理论知识点,本人觉得从实例中验证理论会更有体感,就尝试跟着作者的思路,实现一下这个例子,所谓“纸上得来终觉浅,绝知此事要躬行”

源代码主要是书中(《深入理解java虚拟机》(周志明著第二版(蓝版))的第九章)的代码,资料可下载。

##笔记       

###目标

实现客户端编写的代码可以在服务端执行,并能返回服务端执行的结果到客户端。

###作者给的思路

解决3个问题

**1.如何编译提交到服务器的java代码**

[在客户端(本地)编译之后,将字节码而不是java代码传到服务端;如果在服务器端编译的话,传过去java代码,可以使用tools.jar包中的com.sun.tools.javac.Main类来编译java文件,其实和使用javac命令编译时一样,但是需要引入额外的jar包,依赖于sun jdk。]

**2.如何执行编译之后的java代码**

 [让类加载器加载这个类生成的class对象,然后反射调用一下某个方法既可以了,考虑到不实现任何接口,可以借用java中人人皆知的main()方法。考虑更多一点,还需要能够多次加载同一类,因为一般会反复修改代码。]

 

**3.如何收集java代码的执行结果**

 [考虑到不影响原程序的目的,可以直接在执行的类中,把对System.out的符号引用替换为我们准备的PrintStream的符号引用]

###实现

####一. 其他按下不说,先把程序跑起来

1. idea建一个java工程(jdk7),把书中的5个类导入进去。

2. 自己写一个TestClass, 也就是要被执行的“临时代码”。 

3. 再写一个RunTest,调用一下。原文中是使用jsp文件来模拟调用,这里我就自己写了一个main方法. 

4. 手动编译一下各个文件,build->compile,  没改配置的话,class文件会在out目录下

5. 执行一下,RunTest, 就能看到效果了。不过这么跑,是没有远程传输文件的感觉了,要想有远程的感觉,得搞台服务器,使用idea的remote host,可以尝试一下。文档参考:http://blog.csdn.net/lqleo323/article/details/51153890 。

![image.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/acdc53c85d7268939c62f10b5e1cd898.png)

TestClass.java

“`java

package org.fenixsoft.classloading.execute;

/**

 * @author siyao.hq

 * @date 18/2/11

 */

public class TestClass {

    public static void main(String[] args) {

        System.out.println(“siyao is testing! siyao is so lovely!”);

    }

}

“`

RunTest.java

“`java

package org.fenixsoft.classloading.execute;

import java.io.IOException;

import java.io.InputStream;

/**

 * @author siyao.hq

 * @date 18/2/11

 */

public class RunTest {

    public static void main(String[] args) {

        try {

            InputStream is = RunTest.class.getResourceAsStream(“TestClass.class”);

            byte[] b = new byte[is.available()];

            is.read(b);

            is.close();

            System.out.println(JavaClassExecuter.execute(b));

        }catch (IOException e){

            e.printStackTrace();

        }

    }

}

“`

运行结果:

![image.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/e61243c73eccda3159a8d5bcaea761c5.png)

####二. 顺着思路,看看源代码,知其所以然。

1.先看执行类JavaClassExecuter,作者注释还是蛮清楚的。

  每次执行execute方法,都会生成一个classloader实例。

  

![image.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/0196f17a014ca7763ef7ae042f46bda8.png)

2.HotSwapClassLoader  自定义了一个ClassLoader

![image.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/4807f5d5086b959e397842f0fcfb985f.png)

**为啥会有不能多次载入执行类的问题呢?**

如果忘了前面内容,这时候可以看一下ClassLoader的源码,就能想起原因了,以及所谓的“双亲委派模型”, parent delegation model。(起初听见这个名词“双亲委派模型”,还以为多么的高深,注意放在了双亲上,还想着难道是意味着两个父类动态选择吗?结果就是parent,我真是想太多了……)

![image.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/4ec18ed92ea99b338c6374dcdda67e7c.png)

**如何解决多次载入执行类问题的呢?**

看作者的思路,是使用了defineClass()这个方法,没有自己实现findClass()方法。

翻一翻第七章节,看到作者的一行注释,即“即使自定义了类加载器,强行用defineClass()方法去加载一个以“java.lang”开头的类也不会成功,如果尝试这样做的话,将会收到一个虚拟机自己抛出的“java.lang.SecurityException:Prohibited package name:java.lang”异常”。 这是解释说,在parent delegation model下,保证了一定稳定性,类似rt.jar这种基础类库,不能自己重加载。

这时候再看一下ClassLoader.defineClass()的源码,先读一下注释(又要暴露自己的英文水平了…)。

方法说明中一长段,理解一下,就是把一段字节码转化为类 ,其中后面一段是关于“保护域”ProtectionDomain的说明,也就是最后一个参数,关于ProtectionDomain感觉是又是一章节,粗略理解一下权限控制,类装载器在装载类的时候(或者执行类)会调用安全管理器,安全管理器,则通过判断策略来判断我们是不是允许加载这个类,或者执行某些操作,或者某个文件的读写啊之类的。

defineClass中主要是调用了native的defineClass1,也没啥可操作性了。

网上搜到一个资料,有说到“defineClass 方法可以灵活地用来实现分布式动态计算,hadoop mapreduce应该就是使用了这一方法来保证服务器集群间的处理类的传输和运行。” ,待查查。

![image.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/68f5de58db769d460d11c4d917fa2696.png)

热部署插件,比如hotcode,应该也会有类似实现,可查看查看。

此外,一个class 的唯一标识:(类名,包名,加载器名),因为每次加载都生成了一个类加载器实例,所以加载的class是不一样的,可以实现重复加载。

测试了一下,JavaClassExecuter增加一个传入classLoarder的参数的方法,修改RunTest如下,执行结果如图所示,最后一行打印会抛异常。

![image.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/caf7638ac836be5e9f2f82df827db2ab.png)

![image.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/b787cccc166c4d2ffe579cae4db5c8d8.png)

3.ClassModifier 修改class文件中的常量

这个操作,主要是根据书中第6章关于类文件结构的内容来实现的,替换掉System.out的符号引用。具体方法中的Byte操作,看得我眼带蚊香,果断放弃,知道有这种方法就好了。HackSystem类,ByteUtils主要是支持类。

####三、执行测试

我自己刚开始纠结于怎么执行的,是不是一定要到服务器端才能验证出效果,表示确实是实现了远程执行代码。后来发现是有点想歪了,不一定非要到服务器上去执行,本地跑一下,体会一下类加载的过程,也是OK的啦。 作者的实例设计说要得到执行的结果,私以为应该是想让读者验证一下类文件结构。

##后记

主要是一篇读书笔记,分享一下主要是自己备忘,如果也有跟我一样的小白同学也可以互相帮助一把^_^  , 光看书多么容易犯困啊….

    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/happy524648459/article/details/79593109
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞