类的加载过程java
(一)简述类加载过程:数组
类加载过程: JVM虚拟机把.class文件中类信息加载进内存安全
.class文件: 经过javac命令将java文件编译成字节码 ,此时生成的字节码文件称为.class文件网络
类加载的通俗举例: JVM在执行某段代码时,遇到了class A,此时内存中并无class A的相关信息 ,JVM就会到相应的class文件中去寻找class A的类信息,并加载进内存中 。数据结构
注: JVM不是一开始就把全部的类都加载进内存中, 而是只有第一次遇到某个须要运行的类时才会加载,且只加载一次jvm
(二)Java虚拟机中类加载的全过程测试
参考:http://www.javashuo.com/article/p-bywnjqun-eq.htmlthis
https://blog.csdn.net/qq_31156277/article/details/80188110
1)加载.net
1.一、将.class字节码经过类加载器加载到内存 .class字节码来源: 本地路径下编译生成的.class文件 ,从jar包中的.class文件 ,从远程网络,以及动态 代理实时编译。 类加载器的分类:启动类加载器、扩展类加载器 、应用类加载器 、用户的自定义类加载器 。 类加载的三个阶段: 一、经过一个类的全限定名 (例:java/lang/Thread格式,使用 / 相隔) 获取定义此类的二进制字节流 二、将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构 三、在内存中生成一个表明这个类的java.lang.Class对象,做为方法区这个类的各类数据的访问入口
2)验证代理
确保 Class文件的字节流中包含的 信息符合jvm虚拟机的要求,而且不会危害虚拟机自身的安全
验证的动做: 2.一、文件格式的验证(基于二进制流进行) 验证字节流是否符合Class文件格式的规范 ,保证输入的字节流能正确地解析并存储于方法区以内 注: 验证是基于二进制字节流进行的 ,只有经过了这个阶段的验证后,字节流才能进入内存中的方法 区进行存储。 例:常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其余信息? 2.二、元数据验证(基于方法区的存储结构 ) 对字节码描述的信息进行语义分析 ,使其符合Java语言规范 例:该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载? 2.三、字节码验证(基于方法区的存储结构 ) 对类的方法体进行校验分析 ,保证被校验类的方法在运行时不会作出危害虚拟机安全的事件 经过数据流和控制流分析,肯定程序语义是合法的、符合逻辑的 例: 2.四、符号引用验证(基于方法区的存储结构 ) 校验符号引用中经过全限定名是否可以找到对应的类?校验符号引用中的访问性(private,public等) 是否可被当前类访问 注:验证与加载交替执行,单是加载顺序老是优于验证执行
3)准备
为类变量分配内存并设置类变量初始值 ,这些变量所使用的内存都将在方法区中进行分配
类变量:static修饰的变量
类变量初始值:public static int value=123; 在准备阶段value的初始值为0.而不是123
public static final int value=123;, 当被final修饰事物类变量 准备阶段的初始值为123
4)解析
将常量池内的符号引用替换为直接引用的过程 、
符号引用:即一个字符串,可是这个字符串给出了一些可以惟一性识别一个方法,一个变量,一个类的相关信息
直接引用:能够理解为一个内存地址,或者一个偏移量
即把全部的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用
解析内容:
验证、准备、解析统称为链接
5)初始化
执行类构造器<clinit>()方法,即为类变量赋值+执行静态语句块 static{}的过程
对类变量初始化 ,执行类构造器 ,只对static修饰的变量或语句进行初始化
初始化的顺序:
(三)类的实例建立过程
参考:https://blog.csdn.net/qq_38537709/article/details/88750605
1.当建立子类实例时,先对父类的类变量(satic修饰的变量)和 static{}代码块进行初始化,再对子类的类变量(satic修饰的变量)和 static{}代码块进行初始化
2.对子父类完成初始化后调用构造器,子类的构造器第一行隐式的调用了父的空的构造器,在对父类构造器初始化
时先为成员变量分配内存空间,再对构造器进行初始化
3.成员变量的赋值优先于构造方法里的语句。
class C{ C() { System.out.println("正执行SuperClass类的构造方法对成员变量初始化,为其成员变量C分配内存空间"); } } class SuperClass { C c = new C(); static{ System.out.println("SuperClass类 正在初始化"); } SuperClass() { this("正在调用SuperClass的有参构造方法"); System.out.println("正在执行SuperClass的无参构造方法"); } SuperClass(String s) { System.out.println(s); } } public class SubClass extends SuperClass{ C c = new C(); static{ System.out.println("SubClass类 正在初始化"); } SubClass() { /*在子类构造方法的第一句,隐式的调用父类的构造方法;*/ System.out.println("正在执行子类SubClass的构造方法"); } public static void main(String[] args) { new SubClass(); } }
执行结果:
SuperClass类 正在初始化 SubClass类 正在初始化 正执行SuperClass类的构造方法对成员变量初始化,为其成员变量C分配内存空间 正在调用SuperClass的有参构造方法 正在执行SuperClass的无参构造方法 正执行SuperClass类的构造方法对成员变量初始化,为其成员变量C分配内存空间 正在执行子类SubClass的构造方法
当子类调用父类的静态字段时只有定义这个字段的类即父类才进行初始化,子类不初始化
父类
public class SuperClass { public static int value; public SuperClass() { } static { System.out.println("SuperClass Init"); value = 123; } }
子类
public class SubClass extends SuperClass { public SubClass() { } static { System.out.println("SubClass Init"); } }
测试:
public class Test { public Test() { } public static void main(String[] args) { System.out.println(SubClass.value); } }
结果:
SuperClass Init 123
使用类调用一个static final修饰的常量,该类不会进行初始化
public class ConstantClass { static{ System.out.println("ConstantClass Init"); } public static final String HELLOWOELD="hello world"; } public class NotInitialization { public static void main(String[] args) { System.out.println(ConstantClass.HELLOWOELD); } }
输出结果:
hello world
当建立一个类类型的数组时,该类不会进行初始化
public class SupperClass { static{ System.out.println("SuperClass Init"); } public static int value=123; } public class SubClass { public static void main(String[] args) { SupperClass[] supperClasses = new SupperClass[10]; } }
结果
无输出结果:
总结:
一个类加载过程包含5个过程
一):加载, 首先将编译过的字节码文件经过类加载器加载入内存,将类转为二进制流的形式,将静态时的存储结构转为 方法区运行时的额数据结构,并生成java.lang.class对象
二):链接,链接的过程主要包括验证、准备、解析三个步骤
验证的过程是检验二进制流是否符合java规范,是否对jvm产生伤害,验证分为文件格式验证(验证二进制流是否符合.class文件规范),元数据验证(对语义进行分析),字节码验证(对方法体进行校验分析),符号引用验检验是否能经过类名找到类对象,以及类的修饰符(private、public)
准备的过程是对类变量赋默认初值
解析的过程是将常量池内的符号引用替换为直接引用的过程(将间接引用转为直接引用; 即将变量名称转为对应的地址值或偏移量)
三):初始化,初始化是为类变量赋值+执行静态语句块 static{}的过程
不会引起类初始化的3种状况
1.子类引用父类的类变量,子类不会进行初始化,父类进行初始化
2.一个类引用本类的常量即 static fianl修饰的变量不会进行初始化
3.声明一个类类型数组时也不会进行初始化
子类进行实例化的过程
1.父类先初始化,子类再初始化(先给父类的类变量赋初值+执行静态块static{},再执行子类的)
2.先执行父类的构造方法,在执行子类的构造方法
3.在执行构造方法前先对类的成员变量进行赋值