我在学校的某个失眠的夜晚去看了SpringBoot,对里面的热部署有了一些兴趣和了解,后来也发现热部署也是个比较重要的知识点,于是呢在这个有???太阳的下午好好写一篇文章给大家分享一波自己的对于类加载器以及热部署的认知.???

类加载器


类加载器概述

Java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被Java虚拟机直接使用的Java类型,这就是虚拟机的类加载机制.JVM中用来完成上述功能的具体实现就是类加载器.类加载器读取.class字节码文件将其转换成java.lang.Class类的一个实例.每个实例用来表示一个java类.通过该实例的newInstance()方法可以创建出一个该类的对象.

java模块热插拔 java热加载原理_反射

类加载

类加载机制的核心:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。

  • 当系统运行时,类加载器将.class文件的二进制数据从外部存储器(如光盘,硬盘)调入内存中,CPU再从内存中读取指令和数据进行运算,并将运算结果存入内存中。内存在该过程中充当着"二传手"的作用,通俗的讲,如果没有内存,类加载器从外部存储设备调入.class文件二进制数据直接给CPU处理,而由于CPU的处理速度远远大于调入数据的速度,容易造成数据的脱节,所以需要内存起缓冲作用。
  • 类将class文件加载至运行时的方法区后,会在堆中创建一个Java.lang.Class对象,用来封装类位于方法区内的数据结构,该Class对象是在加载类的过程中创建的,每个类都对应有一个Class类型的对象,Class类的构造方法是私有的,只有JVM能够创建。因此Class对象是反射的入口,使用该对象就可以获得目标类所关联的.class文件中具体的数据结构。
  • 类加载的最终产物就是位于堆中的Class对象(注意不是目标类对象),该对象封装了类在方法区中的数据结构,并且向用户提供了访问方法区数据结构的接口,即Java反射的接口。

java模块热插拔 java热加载原理_类加载器_02

链接过程

将java类的二进制代码合并到JVM的运行状态之中的过程

  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
  • 准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配
  • 解析:虚拟机常量池的符号引用替换为字节引用过程
初始化

初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收藏类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生代码从上往下执行。

  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
  • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步
  • 当范围一个Java类的静态域时,只有真正声名这个域的类才会被初始化

下面写段代码简单描述一下初始化里面的一些常见的问题

✍上代码:

java模块热插拔 java热加载原理_java模块热插拔_03


??说明:这种情况下加载是先加载静态代码块再加载构造方法.✍上代码:

java模块热插拔 java热加载原理_热部署_04


??说明:这个代码和上面那个代码就只是初始化交换了位置.这里"类构造器()方法是由编译器自动收藏类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生代码从上往下执行。"简单来说类变量的初始化和static语句块是合并产生的,代码是重上往下执行.

这段代码实质上相当于:

private static int age=0;
	static {
		age=60;
		System.out.println("2.这是TestStatic类的静态代码块");
	}
	private static int age=50;
	public TestStatic() {
	System.out.println("1.这是TestStatic类的构造方法");
	}

还有一种情景:

✍上代码:

java模块热插拔 java热加载原理_反射_05


??说明:静态变量只初始化一次!


至于类加载机制暂时就说这么多.我记得在我谈反射的文章里也谈过类加载器.在这里就不做个重点介绍了.

接下来就谈谈如何实现热部署-? 讲一下我知道的吧,肯定还有其他的热部署方式,我们从原理上来刨一刨!再简单的用代码来实现一下热部署.

热部署


热部署概述

对于Java应用程序来说,热部署就是在运行时更新Java类文件。也就是不重启服务器的情况下实现java类文件的替换修改等.后面如果有时间学习一下SpringBoot的话 他就可以很优雅的实现一个应用的热部署,大家有兴趣可以去了解一下.

热部署有什么用

可以不重启应用的情况下,更新应用。举个例子,就像电脑可以在不重启的情况下,更换U盘。
OSGI也正是因为它的模块化和热部署,才显得热门。

热部署的原理是什么

简单一句话让JVM重新加载新的class文件!

这个时候问题就来了,如果我们希望将java类卸载,并且替换更新版本的java类,该怎么做呢?

  • 既然在类加载器中,java类只能被加载一次,并且无法卸载。那是不是可以直接把类加载器给换了?答案是可以的,我们可以自定义类加载器,并重写ClassLoader的findClass方法。想要实现热部署可以分以下三个步骤:
    1、销毁该自定义ClassLoader
    2、更新class类文件
    3、创建新的ClassLoader去加载更新后的class类文件。

我们以实现一个基础的自动的热部署代码.来解开热部署的真面目~

下面是我代码实现后的效果,对原GirlFrend类的add方法实现了一个动态的替换.敢肯定的是实现热部署的方式不止这一种,欢迎大家交流分享.哈哈 寂寞的男人 – 我是张学友的铁粉?

java模块热插拔 java热加载原理_类加载器_06


✍代码实现:

package com.xianglei.hot;

public class GirlFrend {
	
	public void add () {
		System.out.println("1.這是熱部署1.0版本.");
	}

}



package com.xianglei.hot;

import java.io.IOException;
import java.io.InputStream;

/**
 * 实现热部署的核心代码:主要是对类加载器的自定义,通过类加载器来创建新的对象进行JVM的加载操作
 * 
 * @author Xianglei @version1.0
 *
 */
public class MyClassLoader extends ClassLoader {
	/*
	 * 实现热部署核心路子: 0.继承ClassLoder 1.获取文件名你 2.读取文件流 3.读取字节 4.数据给JVM识别Class对象
	 */
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {

		try {
			// 文件名称
			String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
			// 读取文件流
			InputStream is = this.getClass().getResourceAsStream(fileName);
			// 读取字节
			byte[] b = new byte[is.available()];
			is.read(b);
			//数据给JVM识别Class对象
			return defineClass(name,b, 0, b.length);
		} catch (IOException e) {
			throw new ClassNotFoundException();
		}
		
	}

}


package com.xianglei.hot;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MainTest {

	public static void main(String[] args) throws InterruptedException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
	
		System.out.println("下面执行的是原来的版本");
		loadUser();
		System.gc();
		//通过File实现文件的替换从而实现一个自动化的热部署
		File oldFile = new File("D:\\JavaM\\Hot\\target\\classes\\com\\xianglei\\hot\\GirlFrend.class");
		oldFile.delete();
		File newFile = new File("C:\\Users\\Administrator\\Desktop\\GirlFrend.class");
		
		//实现新老class文件的替换
		boolean isRenameTo = newFile.renameTo(oldFile);
		if(!isRenameTo) {
			System.out.println("热部署失败!");
		}
		else {
			System.out.println("下面执行的是现在的版本");
			loadUser();
		}
		
	}

	// 通过反射机制创建对象
	public static void loadUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException,
			NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
		MyClassLoader myLoader = new MyClassLoader();
		Class<?> class1 = myLoader.findClass("com.xianglei.hot.GirlFrend");
		Object obj1 = class1.newInstance();
		Method method = class1.getMethod("add");
		method.invoke(obj1);
		System.out.println(obj1.getClass());
		System.out.println(obj1.getClass().getClassLoader());
	}
}

总结

关于热部署的知识我暂时就了解了这么多,在牺牲了一个冬日温暖的午后,终于让我熟悉了热部署的原理以及手动实现一个基础的热部署.挺有意思的! 什么时候都不能停下学习的脚步,时间过的挺快的我下半年也要出去实习了.快过年了,提前???祝大家新年快乐!!!??? 也祝各位和我一样的大学生coder们来年里能顺利的找到一个好工作!???