类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、解析、和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制 懒加载
类加载生命周期:
加载
加载源:
• 文件 class 文件 Jar文件 • 网络 • 计算生成二进制流 • 数据库 • 其他文件生成 jsp
验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
准备
准备阶段正式为类变量分配内存并设置变量的初始值。这些变量使用的内存都将在方法区中进行分配
代码语言:javascript复制class Sub{
public static int age = 10;
}
解析
解析阶段是虚拟机将常量池中的方法引用替换为直接引用的过程 • 类或者接口解析 • 字段解析 • 类方法解析 • 接口方法解析
初始化
初始化是执行<clinit>()
方法的过程
<clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问
package jvm;
/**
* 编译报错非法向前引用
*/
public class Demo1 {
static {
i=0;
System.out.println(i);
}
public static void main(String[] args) {
}
static int i =1;
}
编译器报错:非法向前引用
Java虚拟机必须保证一个类的<clinit>()
方法在多线程环境中被正确地加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的<clinit>()
方法,其他线程都需要阻塞等待,直到活动线程执行完毕<clinit>()
方法。如果在一个类的<clinit>()
方法中有耗时很长的操作,那就可能造成多个进程阻塞 [2] ,在实际应用中这种阻塞往往是很隐蔽的
package jvm;
public class DeadLoopClass {
static class Hello{
static {
System.out.println(Thread.currentThread().getName() "init...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() " start");
Hello hello = new Hello();
System.out.println(Thread.currentThread().getName() " end");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() " start");
Hello hello = new Hello();
System.out.println(Thread.currentThread().getName() " end");
}
}).start();
}
}
运行结果:
类加载器
双亲委派:
java.lang.ClassLoader的loadClass()方法
代码语言:javascript复制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;
}
}
先检查请求加载的类型是否已经被加载过,若没有则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。假如父类加载器加载失败,抛出ClassNotFoundException异常的话,才调用自己的findClass()方法尝试进行加载
自定义类加载器:
- 定义一个类,继承ClassLoader
- 重写loadClass方法
- 实例化Class对象
package jvm;
import java.io.IOException;
import java.io.InputStream;
public class Demo4 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//自定义classLoader
ClassLoader myClassLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
//jvm.Demo4
String fileName = name.substring(name.lastIndexOf(".") 1) ".class";
InputStream inputStream = getClass().getResourceAsStream(fileName);
if (inputStream == null) {
//名字不存在让父类加载
return super.loadClass(name);
}
try {
byte[] buff = new byte[inputStream.available()];
inputStream.read(buff);
return defineClass(name,buff,0,buff.length);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
};
//加载自定义classloader的是哪个classloader
System.out.println(myClassLoader.getClass().getClassLoader());
System.out.println(Demo4.class.getClassLoader());
//用自定义加载器加载demo4的实例
Object oj= myClassLoader.loadClass("jvm.Demo4").newInstance();
System.out.println(oj.getClass().getClassLoader());
//对象是否为demo4的实例
System.out.println(oj instanceof Demo4);
}
}
运行结果:
只有被同一个类加载器加载的类才可能会相等