深入理解JVM-类加载器深入解析(2)

时间:2023-03-09 09:01:09
深入理解JVM-类加载器深入解析(2)

深入理解JVM-类加载器深入解析(2)

加载:就是把二进制形式的java类型读入java虚拟机中

连接:

  • 验证:

  • 准备:为类变量分配内存,设置默认值.但是在到达初始化之前,类变量都没有初始化为真正的初始值

  • 解析:解析过程就是在类型的变量的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程

初始化:为类变量赋予正确地初始值

类实例化:

为新的对象分配内存

为实例变量赋默认值

为实例变量赋正确地初始值

java编译器为它编译的每一个类都至少生成一个实例初始化方法,在java的class文件中,这个实例初始化方法被称为"".针对源代码中每一个类的构造方法,java编译器都产生一个方法

类的加载

类的加载的最终产品是位于内存中的Class对象

class对象封装了类在方法区内的数据结构,并且向java程序员提供了访问方法区内的数据结构的接口

有两种类型的类加载器

  1. java虚拟机自带的加载器
  • 根类加载器(Bootstrap)

  • 扩展类加载器(Extension)

  • 系统(应用)类加载器(System)

  1. 用户自定义的类加载器
  • java.lang.ClassLoader的子类

  • 用户可以定制类的加载方式

类加载器并不需要等到某个类被"首次主动使用"时再加载它

JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)

如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

类的验证

类被加载后,就进入了连接阶段.连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去.

类的验证的内存

  • 类文件的结构检查

  • 语义检查

  • 字节码验证

  • 二进制兼容性的验证

类的准备

在准备阶段,Java虚拟机为类的静态变量分配内存,并设置默认的初始值.例如对于以下Sample类,在准备阶段,将为int类型的静态变量a分配4个字节的内存空间,并且赋予默认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值0.


public class Sample{ private static int a = 1;
public static long b;
static {
b=2;
}
}
类的初始化

在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值.在程序中,静态变量的初始化有两种途径;

  1. 静态变量的生命处进行初始化;

  2. 在静态代码块中进行初始化.

例如在以下代码中,静态变量a和b都被显示初始化,而静态变量c没有被显示初始化,它将保持默认值0


public class Sample{ private static int a=1; //在静态变量的声明处进行初始化
public static long b;
public static long c;
static{
b=2; // 在静态代码块中进行初始化
} }

静态变量的声明语句,以及静态代码块都被看做类的初始化语句,java虚拟机会按照初始化语句在类文件中的先后顺序来一次执行它们.例如当一下Sample类被初始化后,它的静态变量a的取值为4.


public class Sample{ static int a= 1;
static {a=2;}
static {a=4;}
public static void main(String args[]){
System.out.println("a="+a)//打印a=4 } }

类的初始化步骤:

  • 加入这个类还没有被加载和连接,那就先进行加载和连接

  • 加入类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类

  • 加入类中存在初始化语句,那就一次执行这些初始化语句

当java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口.

  • 在初始化一个类时,并不会先初始化它所实现的接口.

  • 在初始化一个接口时,并不会先初始化它的父接口


/** * 当一个接口在初始化时,并不要求其父接口都完成了初始化 * 只有在真正使用到父接口的时候(如引用接口中所定义的常量时),才会初始化 * * 在加上-XX:TraceClassLoading这个参数以后我们可以得知虚拟机的加载情况 * 如果是MyChild5是Class类,并且b变量是public static的时候可以看得到 * MyParent5和MyChild5都被加载了;如果b变量改为了public static final类型的 * 则可以看到MyParent5和MyChild5都没有被加载 * * 如果MyChild5是接口的话,则b变量默认是public static final类型的产量,是在 * 所以在编译阶段就会存入调用这个常量所在的方法的类的常量池中,所以并不会去加载MyParent5和MyChild5字节码文件 * * 初始化: * 我们在MyParent5中加入一个静态变量,如果MyParent5接口被初始化,一定会打印出MyParent5 invoked * 但是实际上并不会打印出,如果把MyParent5 改成了class类的话,则会打印出, * 说明在初始化一个类时,并不会先初始化它所实现的接口 * * 我们再定义MyParent5_1和MyGrandpa5_1,在直接调用MyParent5_1的时候我们可以知道MyParent5_1被初始化了, * 但是并没有打印出MyGrandpa5_1 invoked,说明接口的父类并不会被初始化 */ public class MyTest5 { public static void main(String[] args) {
//System.out.println(MyChild5.b);
System.out.println(MyParent5_1.THREAD);
}
} interface MyGrandpa5{ public static Thread THREAD = new Thread(){
{
System.out.println("MyGrandpa5 invoked");
}
};
} interface MyParent5 {
//class MyParent5 {
public static int a = 5;
//public static int a = new Random().nextInt(5);
public static Thread THREAD = new Thread(){
{
System.out.println("MyParent5 invoked");
}
};
} class MyChild5 implements MyParent5 { public static int b = 6;
//public static int b = new Random().nextInt(4); } interface MyGrandpa5_1{ public static Thread THREAD = new Thread(){
{
System.out.println("MyGrandpa5_1 invoked");
}
};
} interface MyParent5_1 { public static int a = 5;
//public static int a = new Random().nextInt(5);
public static Thread THREAD = new Thread(){ {
System.out.println("MyParent5_1 invoked");
}
}; public static Thread THREAD2 = new Thread(){
{
System.out.println("MyParent5_1 invoked2");
}
};
}

因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化.只有当程序首次适用特定接口的静态变量时,才会导致该接口的初始化.

只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用

调用ClaossLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化

类加载器

类加载器用来把类加载到java虚拟机中.从JDK1.2版本开始,类的加载过程才用父类委托机制,这种机制能更好地保证java平台的安全.在此委托机制中,除了java虚拟机自带的根类加载器以外,其余的类加载器都有且只有一个父加载器.当java程序请求加载器loader1加载Sample类时,loader1首先委托自己的父加载器去加载Sample类,若父加载器能加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载Sample类

在父亲委托机制中,各个加载器按照父子关系形成了树形结构,除了根类加载器之外,其余的类加载器都有且只有一个父加载器

java虚拟机自带了以下几种加载器:

  • 根类加载器(Bootstrap):该加载器没有付加载器.它负责加载虚拟机的核心类库,如java.lang.*等.根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库.根类加再起的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承java.lang.ClassLoader类

  • 扩展类加载器(Extension):它的父加载器为根类加载器.它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre\lib\ext子目录(扩展目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载.扩展类加载器是纯java类,是java.lang.ClassLoader类的子类

  • 系统类加载器(System):也称为应用类加载器,它的附加在其为扩展类加载器.它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器.系统类加载器是纯java类,是java.lang.ClassLoader类的子类

除了以上虚拟机自带的加载器外,用户还可以定制自己的类加载器.java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器都应该继承ClassLoader类

从表象上来看这些类加载器是一种继承关系,实际上应该是一种包含关系

class Parent2 {
static int a =3;
static {
System.out.println("parent2 static block");
}
} class Child2 extends Parent2{
static int b = 4;
static {
System.out.println("Child2 static block");
}
} public class MyTest10 { static {
System.out.println("Mytest10 static block");
} public static void main(String[] args) {
/**
* Mytest10 static block
* ----
* parent2 static block
* ----
* 3
* ----
* Child2 static block
* 4
*/
Parent2 parent2;
System.out.println("----");
parent2 = new Parent2();
System.out.println("----");
System.out.println(parent2.a); System.out.println("----");
System.out.println(Child2.b); }
}
class Parent3 {
static int a = 3; static {
System.out.println("Parent3 static block");
} static void doSomething() {
System.out.println("do something");
}
} class Child3 extends Parent3 {
static {
System.out.println("Child3 static block");
}
}
public class MyTest11 { public static void main(String[] args) {
/**
* Parent3 static block
* 3
* ----
* do something
* 调用Child3.a定义在父类中,所以是对父类的一个主动使用,
* 谁拥有这个静态变量就是对谁的一个主动使用
* 如果是用子类去调用父类的静态变量也好,静态方法也好,都是对父类的主动使用
*
*/
System.out.println(Child3.a);
System.out.println("----");
Child3.doSomething();
}
}
class CL{
static {
System.out.println("Class CL");
}
}
//调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化
public class MyTest12 {
public static void main(String[] args) throws Exception {
/**
* class jvm.classloader.CL
* ----
* Class CL
* class jvm.classloader.CL
*
*/
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> aClass = loader.loadClass("jvm.classloader.CL");
System.out.println(aClass); System.out.println("----");
aClass = Class.forName("jvm.classloader.CL");
System.out.println(aClass);
}
}

java学习笔记# #jvm#