类装载条件:java
Parent:apache
public class Parent {
static {
System.out.println("Parent init");
}
public static int v = 100;
}
复制代码
Child:数组
public class Child extends Parent {
static {
System.out.println("Child init");
}
}
复制代码
测试:bash
public class Main {
public static void main(String[] args) {
Child child = new Child();
}
}
复制代码
输出:数据结构
Parent init
Child init
复制代码
修改测试类:jvm
public class Main {
public static void main(String[] args) {
System.out.println(Child.v);
}
}
复制代码
输出:ide
Parent init
100
复制代码
引用一个字段时,只有直接定义该字段的类,才会被初始化。测试
虽然 Child 类没有被初始化,可是此时 Child 类以及被系统加载,只是没有进入到初始化阶段。ui
使用 -XX:+TraceClassLoading 打印:this
[Loaded Parent from file:/D:/workspace/tutorial-jvm/out/production/tutorial-jvm/]
[Loaded Child from file:/D:/workspace/tutorial-jvm/out/production/tutorial-jvm/]
Parent init
100
复制代码
若是修改 Parent 静态变量,用 final 修饰,再次执行:
100
复制代码
javac 在编译时,将常量直接植入目标类,再也不使用被引用类。
虚拟机会为这个类分配相应的内存空间,并设置初始值。
将类、接口、字段和方法的符号引用专为直接引用。
类装载的最后阶段,开始执行 Java 字节码。
ClassLoader,类装载器。CLassLoader 在 Java 中有着很是重要的左右,主要工做在 Class 装在的加载阶段,其主要做用是从系统外部得到 Class 二进制数据流。
全部的 Class 都是由 ClassLoader 进行加载的,ClassLoader 负责经过各类法师将 Class 信息的二进制数据流读入系统,而后交给 Java 虚拟机进行链接、初始化等操做。
ClassLoader 是一个抽象类,主要方法以下:
ClassLoader 中,还有一个重要的字段 parent,也是一个 ClassLoader 的实例,表示这个 ClassLoader 的双亲。类加载过程当中, ClassLoader 可能会将某些请求交予本身的双亲处理。
标准 Java 程序中,Java 虚拟机会建立三种 ClassLoader 为整个应用程序服务:
自下往上为本身的双亲。当系统须要使用一个类时,在判断类是否已经被加载时,会先从当前底层类加载器进行判断。当系统须要加载一个类时,会从顶层类开始加载,一次向下尝试,直到成功。
public class Main {
public static void main(String[] args) {
ClassLoader classLoader = Main.class.getClassLoader();
while (classLoader != null) {
System.out.println(classLoader);
classLoader = classLoader.getParent();
}
}
}
复制代码
输出:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
复制代码
Main 类家在与 AppClassLoader,它的双亲为 ExtClassLoader。可是 ExtClassLoader 没法再取得启动类加载器,是由于这是一个系统级的纯 C 实现。所以任何加载在启动类加载器中的类是没法获取其 ClassLoader 实例的:
String.class.getClassLoader() // null
复制代码
在类加载的时候,系统会判断当前类是否已经被加载,若是已经被加载,就会直接返回可用的类,不然就会尝试加载。在尝试加载时,会先请求双亲处理,若是双亲请求失败,则会本身加载。
参考 ClassLoader 中 loadClass() 方法:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
复制代码
双亲为 null 有两种状况,第一,其双亲就是启动类加载器;第二,当前加载器就是启动类加载器。
判断类是否加载时,应用类加载器会顺着双亲路径网上判断,直到启动类加载器。可是启动类加载器不会往下询问,这个委托路线是单向的,理解这点很重要。
顶层的 ClassLoader 没法访问底层的 ClassLoader 所加载的类。
当在系统类中,提供一个接口,接口须要在应用中得以实现。该接口绑定一个工厂方法,用于建立该接口的实例。因为启动类加载器没法向下询问,就会出现该工厂发发没法建立由应用类加载器加载的应用实例。拥有这种问题组件有不少,好比 JDBC、Xml Parser 等。
以 javax.xml.parses 中实现 XML 文件解析功能模块为例,构造一个 DocumentBuilderFactory 的实例(加载在启动类加载器中):
public static DocumentBuilderFactory newInstance() {
return FactoryFinder.find(
/* The default property name according to the JAXP spec */
DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory"
/* The fallback implementation class name */
"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
}
复制代码
跟进这个方法,最后阅读到:
ClassLoader getContextClassLoader() throws SecurityException{
return (ClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
ClassLoader cl = null;
//try {
cl = Thread.currentThread().getContextClassLoader();
//} catch (SecurityException ex) { }
if (cl == null)
cl = ClassLoader.getSystemClassLoader();
return cl;
}
});
}
复制代码
Thread 有两个方法:
经过这两个方法,能够把一个 ClassLoader 置于一个线程实例之中,使其相对共享,默认状况下上下文加载器就是应用类加载器。
public class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 重写类加载流程
return super.loadClass(name);
}
}
复制代码
热替换是指在程序的运行过程当中,不中止服务,只经过替换程序文件来修改程序的行为。基本上大部分脚本语言天生支持热替换,例如 PHP。
两个不一样 ClassLoader 加载同一个类,在虚拟机内部,会认为这 2 个类是彻底不一样的。
自定义 ClassLoader:
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
/** * @author caojiantao */
public class MyClassLoader extends ClassLoader {
private String fileName;
public MyClassLoader(String fileName) {
this.fileName = fileName;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(name);
if (clazz == null) {
try (FileInputStream is = new FileInputStream(fileName + name + ".class");
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
byte[] bytes = baos.toByteArray();
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
}
}
return super.findClass(name);
}
}
复制代码
热替换的 Java 类:
/** * @author caojiantao */
public class Hello {
public void sayHello() {
System.out.println("hello");
}
}
复制代码
测试类:
import java.lang.reflect.Method;
/** * @author caojiantao */
public class Main {
public static void main(String[] args) {
while (true) {
MyClassLoader classLoader = new MyClassLoader("C:\\Users\\caojiantao\\Desktop\\");
try {
Class clazz = classLoader.loadClass("Hello");
Object instance = clazz.newInstance();
Method say = instance.getClass().getMethod("sayHello");
say.invoke(instance);
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
}
复制代码
将 Hello.java 编译后的 class 文件放在桌面,执行程序:
hello
hello
复制代码
修改 Hello 类,从新编译成 class 文件替换桌面文件:
/** * @author caojiantao */
public class Hello {
public void sayHello() {
System.out.println("hello world");
}
}
复制代码
输出:
hello
hello
hello world
复制代码
因为双亲没法加载 Hello 类,每次都由自定义的 ClassLoader 加载,从而达到热替换的效果。