在约翰·冯·诺伊曼的计算机模型中,任何程序都需要加载到内存才能与CPU进行交流。
其中此类的二进制流,不仅仅是本地的.class文件,也可以是从jar,war包中的,或者使用java自带的基于接口的动态代理,或者基于cglib动态生成的二进制流,或者是老式的jsp,以及加密解密的class文件等等
连接分为三个:验证,准备,解析
验证是为了运行当前代码不会危害虚拟机自身的安全
为类变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始化。
但是常量(final修饰)在编译javac将常量生成ConstantValue属性,在准备阶段根据ConstantValue设置赋值。
java虚拟机将常量池内的符号引用替换为直接引用的过程。
<clinit> 方法 clinit 方法前会调用其父类的 clinit 的方法,最初调用应该是 java.lang.object 的 clinit 方法 clinit 方法(要避免clinit是个死锁) clinit 主要是对被static修饰的变量和静态代码块进行赋值。如果都不存在,就没有 clinit // 这是java mysql jdbc最基本的方式
// 会通过类加载机制,执行static方法块,在DriverManger里面注册一个Mysql驱动
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_index?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC","root","happyday");
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from test limit 10");
while (resultSet.next()){
System.out.println(resultSet.getInt("id"));
}
//我们来看下,这个com.mysql.cj.jdbc.Driver里面的clinit
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
复制代码
public class ClinitTest1 {
static class Father{
public static int a = 1;
static {
a = 2;
}
}
static class Son extends Father{
public static int b = a;
}
public static void main(String[] args) {
System.out.println(Son.b);
}
}
// 输出 2
复制代码
// 我举了一个不知恰不恰当的例子,通过数组定义引用类并不会触发此类的初始化
public class TestClass {
static {
System.out.println("static method");
}
public static void main(String[] args) {
TestClass[] testClasses = new TestClass[10];
}
}
// 输出 static method
public class TestClass2 {
public static void main(String[] args) {
TestClass[] testClasses = new TestClass[10];
}
}
// 无输出
复制代码
public class ClassInitTest {
private static int num ;
static {
num = 2;
number = 20;
System.out.println(num);
// System.out.println(number);非法前像引用
}
private static int number = 10;
public static void main(String[] args) {
System.out.println(num);
System.out.println(number);
}
}
// 输出
/**
2
2
10
*/
/**
clinit方法
0 iconst_2
1 putstatic #2 <org/example/jvm/classload/ClassInitTest.num>
4 bipush 20
6 putstatic #3 <org/example/jvm/classload/ClassInitTest.number>
9 getstatic #4 <java/lang/System.out>
12 getstatic #2 <org/example/jvm/classload/ClassInitTest.num>
15 invokevirtual #5 <java/io/PrintStream.println>
18 bipush 10
20 putstatic #3 <org/example/jvm/classload/ClassInitTest.number>
23 return
*/
/**
反编译的class文件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.example.jvm.classload;
public class ClassInitTest {
private static int num = 2;
private static int number = 20;
public ClassInitTest() {
}
public static void main(String[] args) {
System.out.println(num);
System.out.println(number);
}
static {
System.out.println(num);
number = 10;
}
}
*/
复制代码
还有很多我没有举例到,如多线程竞争static方法,数组。。。
java 团队有意把类的加载阶段的“通过一个类的全限定名来获取描述该类的二进制字节流”放到java虚拟机外部实现,以便让程序自己决定去获取所需类。
这个时候,如果是两个对象比较是否相等的前提必须是同一个类加载器加载比较才有意义。
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
ClassLoader classLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
};
Object obj = classLoader.loadClass("org.example.jvm.classload.ClassLoaderTest").newInstance();
System.out.println(obj);
System.out.println(obj instanceof org.example.jvm.classload.ClassLoaderTest);
Object obj2 = new ClassLoaderTest();
System.out.println(obj2 instanceof org.example.jvm.classload.ClassLoaderTest);
}
}
// 输出
org.example.jvm.classload.ClassLoaderTest@6e0be858
false
true
复制代码
public class ClassLoaderTest {
public static void main(String[] args) {
// 获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);// sun.misc.Launcher$AppClassLoader@18b4aac2
// 获取扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);// sun.misc.Launcher$ExtClassLoader@135fbaa4
// 试图获取bootstrap classloader 但没有获取到为空
System.out.println(extClassLoader.getParent());
// String 是通过引导类来获取的==>java 核心库都是通过bootstrap class loader 来引导的
System.out.println(String.class.getClassLoader());
// sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(ClassLoaderTest.class.getClassLoader());
}
}
// 输出
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@4b67cf4d
null
null
sun.misc.Launcher$AppClassLoader@18b4aac2
复制代码
双亲委派模型应该是叫溯源委派加载模型,起初加载类时,是依次向上询问是否已加载过, 然后再向下逐层询问是否可加载。一般在主流中间件都有自定义类加载器,实现类的隔离,防止冲突。