How own handwriting a heat load (rpm)

Source:  How own handwriting a hot load

 

Thermal Load: without stopping running, the dynamic replacement of class (object)

Java ClassLoader Brief

The Java classes from being loaded into memory so far to unload the memory, experienced a total of seven stages: loading, validation, preparation, parsing, initialization, use and uninstall.

Next, we focus on explaining these two steps to load and initialize

load

In the loading phase, the virtual machine needs to do three things:

  • To obtain such is defined by the fully qualified name of a class of binary byte stream
  • The byte stream that represents the static memory structure into the method area run-time data structure
  • Generating in memory a representative of the class of java.lang.Classobjects, a method for accessing the various data entry areas of this class.

These three steps are implemented by the class loader. The official definition of the Java class loader have BootstrapClassLoader, ExtClassLoader, AppClassLoader. The three were responsible for loading class loader to load different paths class. And forming a parent-child structure.

Class loader name Responsible for loading catalog
BootstrapClassLoader The highest level in the hierarchy of the class loader is responsible for loading class sun.boot.class.path path, the default is the core API or -Xbootclasspath options under jre / lib directory specified jar package
ExtClassLoader Loading path java.ext.dirs, default jre / lib / ext -Djava.ext.dirs specified directory or directory loaded jar package
AppClassLoader Load path java.class.path, the default value for the environment variable CLASSPATH set. You can also be specified with the -classpath Selection

By default, for example, we use keywords newor Class.forNameare through AppClassLoaderto load the class loader

  正因为是此父子结构,所以默认情况下如果要加载一个类,会优先将此类交给其父类进行加载(直到顶层的BootstrapClassLoader也没有),如果父类都没有,那么才会将此类交给子类加载。这就是类加载器的双亲委派规则。

初始化

当我们要使用一个类的执行方法或者属性时,类必须是加载到内存中并且完成初始化的。那么类是什么时候被初始化的呢?有以下几种情况

  • 使用new关键字实例化对象的时候、读取或者设置一个类的静态字段、以及调用一个类的静态方法。
  • 使用java.lang.reflect包的方法对类进行反射调用时,如果类没有进行初始化,那么先进行初始化。
  • 初始化一个类的时候,如果发现其父类没有进行初始化,则先触发父类的初始化。
  • 当虚拟机启动时,用户需要制定一个执行的主类(包含main()方法的那个类)虚拟机会先初始化这个主类。

如何实现热加载?

  在上面我们知道了在默认情况下,类加载器是遵循双亲委派规则的。所以我们要实现热加载,那么我们需要加载的那些类就不能交给系统加载器来完成。所以我们要自定义类加载器来写我们自己的规则。

实现自己的类加载器

  要想实现自己的类加载器,只需要继承ClassLoader类即可。而我们要打破双亲委派规则,那么我们就必须要重写loadClass方法,因为默认情况下loadClass方法是遵循双亲委派的规则的。

public class CustomClassLoader extends ClassLoader{

    private static final String CLASS_FILE_SUFFIX = ".class";

    //AppClassLoader的父类加载器
    private ClassLoader extClassLoader;

    public CustomClassLoader(){
        ClassLoader j = String.class.getClassLoader();
        if (j == null) {
            j = getSystemClassLoader();
            while (j.getParent() != null) {
                j = j.getParent();
            }
        }
        this.extClassLoader = j ;
    }

    protected Class<?> loadClass(String name, boolean resolve){

        Class cls = null;
        cls = findLoadedClass(name);
        if (cls != null){
            return cls;
        }
        //获取ExtClassLoader
        ClassLoader extClassLoader = getExtClassLoader() ;
        //确保自定义的类不会覆盖Java的核心类
        try {
            cls = extClassLoader.loadClass(name);
            if (cls != null){
                return cls;
            }
        }catch (ClassNotFoundException e ){

        }
        cls = findClass(name);
        return cls;
    }

    @Override
    public Class<?> findClass(String name) {
        byte[] bt = loadClassData(name);
        return defineClass(name, bt, 0, bt.length);
    }

    private byte[] loadClassData(String className) {
        // 读取Class文件呢
        InputStream is = getClass().getClassLoader().getResourceAsStream(className.replace(".", "/")+CLASS_FILE_SUFFIX);
        ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
        // 写入byteStream
        int len =0;
        try {
            while((len=is.read())!=-1){
                byteSt.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 转换为数组
        return byteSt.toByteArray();
    }

    public ClassLoader getExtClassLoader(){
        return extClassLoader;
    }
}

为什么要先获取ExtClassLoader类加载器呢?其实这里是借鉴了Tomcat里面的设计,是为了避免我们自定义的类加载器覆盖了一些核心类。例如java.lang.Object

为什么是获取ExtClassLoader类加载器而不是获取AppClassLoader呢?这是因为如果我们获取了AppClassLoader进行加载,那么不还是双亲委派的规则了吗?

监控class文件

这里我们使用ScheduledThreadPoolExecutor来进行周期性的监控文件是否修改。在程序启动的时候记录文件的最后修改时间。随后周期性的查看文件的最后修改时间是否改动。如果改动了那么就重新生成类加载器进行替换。这样新的文件就被加载进内存中了。

首先我们建立一个需要监控的文件

public class Test {

    public void test(){
        System.out.println("Hello World! Version one");
    }
}

我们通过在程序运行时修改版本号,来动态的输出版本号。接下来我们建立周期性执行的任务类。

public class WatchDog implements Runnable{

    private Map<String,FileDefine> fileDefineMap;

    public WatchDog(Map<String,FileDefine> fileDefineMap){
        this.fileDefineMap = fileDefineMap;
    }

    @Override
    public void run() {
        File file = new File(FileDefine.WATCH_PACKAGE);
        File[] files = file.listFiles();
        for (File watchFile : files){
            long newTime = watchFile.lastModified();
            FileDefine fileDefine = fileDefineMap.get(watchFile.getName());
            long oldTime = fileDefine.getLastDefine();
            //如果文件被修改了,那么重新生成累加载器加载新文件
            if (newTime!=oldTime){
                fileDefine.setLastDefine(newTime);
                loadMyClass();
            }
        }
    }

    public void loadMyClass(){
        try {
            CustomClassLoader customClassLoader = new CustomClassLoader();
            Class<?> cls = customClassLoader.loadClass("com.example.watchfile.Test",false);
            Object test = cls.newInstance();
            Method method = cls.getMethod("test");
            method.invoke(test);
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

可以看到在上面的gif演示图中我们简单的实现了热加载的功能。

优化

在上面的方法调用中我们是使用了getMethod()方法来调用的。此时或许会有疑问,为什么不直接将newInstance()强转为Test类呢?

如果我们使用了强转的话,代码会变成这样Test test = (Test) cls.newInstance()。但是在运行的时候会抛ClassCastException异常。这是为什么呢?因为在Java中确定两个类是否相等,除了看他们两个类文件是否相同以外还会看他们的类加载器是否相同。所以即使是同一个类文件,如果是两个不同的类加载器来加载的,那么它们的类型就是不同的。

WatchDog类是由我们new出来的。所以默认是AppClassLoader来加载的。所以test变量的声明类型是WatchDog方法中的一个属性,所以也是由AppClassLoader来加载的。因此两个类不相同。

该如何解决呢?问题就出在了=号双方的类不一样,那么我们给它搞成一样不就行了吗?怎么搞?答案就是接口。默认情况下,如果我们实现了一个接口,那么此接口一般都是以子类的加载器为主的。意思就是如果没有特殊要求的话,例如A implements B 如果A的加载器是自定义的。那么B接口的加载器也是和子类是一样的。

所以我们要将接口的类加载器搞成是AppClassLoader来加载。所以自定义加载器中加入这一句

if ("com.example.watchfile.ITest".equals(name)){
    try {
        cls = getSystemClassLoader().loadClass(name);
    } catch (ClassNotFoundException e) {

    }
    return cls;
}

建立接口

public interface ITest {

    void test();
}

这样我们就能愉快的调用了。直接调用其方法。不会抛异常,因为=号双方的类是一样的。

CustomClassLoader customClassLoader = new CustomClassLoader();
Class<?> cls = customClassLoader.loadClass("com.example.watchfile.Test",false);
ITest test = (ITest) cls.newInstance();
test.test();

Reference article







 

Guess you like

Origin www.cnblogs.com/myseries/p/11756439.html