深入类加载器(一)
那么在加载的时候究竟发生了什么呢?其实我们知道刚开始进来的是字节码文件,那么经过加载了以后,那么这些静态的数据就会变为方法区中的一些动态的二进制数据,并且在堆中生成指向这个这个类的java.lang.class对象,这个对象指向的是方法区中的运行时的数据。那么我们就可以通过这个class对象来操纵这个类了。反射就是基于这个原理的。那么简单的来说,就是一些字节码文件的数据进来,经过加载了以后,就在方法区中多了一些二进制的动态的数据,并且在堆中生成了一个java.lang.class对象,这个对象指向的是方法区中的动态的数据结构。通过这个对象,我们就可以来操纵这个类了。
链接的过程就是将方法区中的动态的二进制数据整合到JVM中,就会形成运行时的数据。这样的一个过程就是链接。链接的过程也可以分为三类。一个是:验证,准备,和解析。
验证的过程是:确保安全,加载的类的信息是否符合jvm的规范,是否会危害我们的虚拟机。
准备过程是:正式为类变量(也就是static变量)分配内存,这些static的内存都在方法区中进行分配。并且为这些类变量赋初始值。也就是赋默认值。比如说我在类中定义了一个变量。Public static int a=3.那么在这个阶段,其实a=0;因为真正a=3的时候是在初始化的过程。
解析过程:就是将虚拟机中的符号引用替换为直接引用。在一个类中会有很多的常量。比如说,我们的类名就是一个常量。还有一些字符串常量,还有一些数字常量。其实每一个类在加载了以后,都会在方法区中形成一个常量池。在虚拟机的符号引用我们是不能直接使用的
。因为它只是一些抽象的符号,还不能够直接使用。在讲符号引用转化为直接引用的就是使虚拟机可以直接访问这些符号数据。在由符号引用到直接引用的一个重要的过程就是,指定这些符号对应得数据的存储地址。这个地址可以是直接的地址,直接就告诉你在哪里存放的,也可以是相对的地址。经过了这个过程以后,那么我们的程序就具备了初始化的条件。
那么在加载的时候究竟发生了什么呢?其实我们知道刚开始进来的是字节码文件,那么经过加载了以后,那么这些静态的
数据就会变为方法区中的一些动态的二进制数据,并且在堆中生成指向这个这个类的java.lang.class对象,这个对象指向的
是方法区中的运行时的数据。那么我们就可以通过这个class对象来操纵这个类了。反射就是基于这个原理的。
那么简单的来说,就是一些字节码文件的数据进来,经过加载了以后,就在方法区中多了一些二进制的动态的数据,
并且在堆中生成了一个java.lang.class对象,这个对象指向的是方法区中的动态的数据结构。通过这个对象,我们就可以来操纵这个类了。
链接的过程就是将方法区中的动态的二进制数据整合到JVM中,就会形成运行时的数据。这样的一个过程就是链接。
链接的过程也可以分为三类。一个是:验证,准备,和解析。
验证的过程是:确保安全,加载的类的信息是否符合jvm的规范,是否会危害我们的虚拟机。
准备过程是:正式为类变量(也就是static变量)分配内存,这些static的内存都在方法区中进行分配。
并且为这些类变量赋初始值。也就是赋默认值。比如说我在类中定义了一个变量。Public static int a=3.
那么在这个阶段,其实a=0;因为真正a=3的时候是在初始化的过程。
解析过程:就是将虚拟机中的符号引用替换为直接引用。在一个类中会有很多的常量。
比如说,我们的类名就是一个常量。还有一些字符串常量,还有一些数字常量。其实每一个类在加载了以后,都会在方法区中形成一个常量池。在虚拟机的符号引用我们是不能直接使用的。因为它只是一些抽象的符号,还不能够直接使用。在讲符号引用转化为直接引用的就是使虚拟机可以
直接访问这些符号数据。在由符号引用到直接引用的一个重要的过程就是,指定这些符号对应得数据的存储地址。
这个地址可以是直接的地址,直接就告诉你在哪里存放的,也可以是相对的地址。
经过了这个过程以后,那么我们的程序就具备了初始化的条件。那么我们具体怎么怎么来说这一个解析的过程呢?我们可以
举一个简单的例子,比如说班长要找小明,但是小明究竟在哪里,班长究竟在哪里,我们都是不知道的。那么这些都是一些抽象的过程。这就相当于是符号引用。在这里,班长就相当于是虚拟机,而小明就相当于是一些符号的数据。
但是如果我告诉你,班长在一米处,小明在100米处,那么这个就相当于是直接给出地址了,班长就可以直接找到小明了。
但是如果我说,小明在班长的右面20米处,那么这个过程就相当于是间接给出地址了。这个过程就是相对寻址的过程了。
初始化的过程;
1.初始化的阶段是执行类构造器class init 的过程。注意这个是类构造器的过程,而不是普通对象通过new对构造对象的过程。其实类构造器是是由编译器自动收集类变量的所有的赋值动作和静态块中的所有的语句集合而成的。
2当初始化一个类的时候,如果发现其父类还没有被初始化,那么就需要先初始化父类。这其实是主动引用和被动和被动引用的问题。
3.在执行类构造器的过程当中,一定保证了它在多线程的环境中是线程安全的,并且是被正确加锁的。
4当访问一个静态域的时候,一定是真正声明这个域的类才会被初始化。这其实也是一个主动引用和被动引用的过程。
那么我们下面来看一个例子:
package com.lg.test;
public class Demo01 {
public static void main(String[] args){
ADemo a=new ADemo();
System.out.println(a.width);
}
}
class ADemo{
public static int width=300;
static{
System.out.println("静态初始化类A");
width=100;
}
public ADemo(){
System.out.println("创建A类的对象");
}
}
最后的执行的结果是:
静态初始化类A
创建A类的对象
100
为什么会这样呢?我们知道必须执行类加载的过程,然后才能真正去创建A类的对象。也就是去new一个对象。
在类加载过程的初始化阶段,有一个过程就是将静态变量和静态块整合到类加载器重,所以就会得到上面的结果。
我们来分析一下这个类加载的过程的内存的变化:
需要提醒的一个概念是:在执行每一个方法的时候,会在栈中为每一个方法形成一个栈帧。方法中的一些
变量当然也是在栈帧中的。如果有方法的套用的话,当然也会有栈帧的套用。
如果你调用a.length.那么就会指向方法区中的相对应的静态的变量。