------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
类加载器:
一、ClassLoader(类加载器):
Java虚拟机与程序的生命周期中在如下几种情况下,java虚拟机将结束生命周期:
1、程序执行到 System.exit( )。
2、程序正常执行完。
3、程序再执行过程中遇到的异常或者错误而异常终止。
4、由于操作系统出现错误而导致java虚拟机进程终止。
二 、类的加载、连接与初始化。
类从硬盘上的文件到执行所经过的过程。
1、加载:查找并加载类的二进制数据(加载到内存)
类的加载是指将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后再堆区创建一个java.lang.Class对象,用来封装类再放大区的数 据结构。
加载.class文件的方式:
从本地系统中直接加载、
通过网络下载.class文件 URLClassLoader、
从.zip,jar等归档文件中加载类的.class文件、
从专有数据库中提取.class文件、
将Java源文件动态编译为.class文件。
类加载的最终产品是位于堆区中的Class对象。
Class对象封装了类在方法区的数据结构,并且想java程序员提供了访问方法区内的数据结构的接口
2、连接:
-验证:确保被加载的类的正确性。
-准备:为类的静态变量分配内存,并将其初始化为默认值。
-解析:把类的符号引用转换为直接引用。
3、初始化:为类的静态变量赋予正确的初始值(包括用户为静态变量所赋的值)。
Java程序对类的使用方式分类两种
-主动使用:
创建类的实例
访问某个类或者接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射(Class.forName(“com.tai.Test”))
初始化一个类的子类
Java虚拟机启动被标明为启动的类(java Test )
-被动使用
除了以上六种主动使用之外其他的情况都是被动使用,都不会导致类的初始化。
所有的java虚拟机实现必须再每个类或接口被java程序”首次主动使用“时,才会初始化他们。
类加载器(分类):
-java虚拟机自带的类加载器
-根类加载器(BootStrap) c++ java代码中无法获得
-扩展类加载器(Extension) java
-系统类加载器 应用加载器(Ssystem) java
-用户自定义的类加载器
java.lang.ClassLaoader的子类
用户可以定制类的加载方式
类加载器不需要等到某个类被“首次主动使用”时再加载它。
JVM规范允许类加载器再预料某个类将要被使用时就预先加载它,如果预先加载的过程中遇到了.class文件缺失或者存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError)错误
如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误
类的验证
类被加载后,就进入连接阶段。连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。连接先要对类进行验证。
类验证的内容:
-类文件的结构检查
确保类文件遵从java类文件的固定格式
-语义检查
确保类本身符合java语言的语法规定,比如验证final类型的类没有子类,以及final 类的方法没有被覆盖
-字节码验证
确保字节码流可以被java虚拟机安全的执行。字节码流代表java方法(包括静态方法 以及实例方法),它是有被称作操作码的单字节指令组成的序列,每一个操作码后都跟 着一个或多个操作数。字节码验证步骤会检查每个操作码是否合法,即是否有合法的操 作数。
-二进制兼容性的验证
确保相互引用的类之间协调一致。例如再Worker类的gotoWork()方法中会调用Car类 的run()方法。Java虚拟机再验证Woker类时,会检查在方法区内是否存在Car类的 run方法,加入不存在(当Woker类和Car类的版本不兼容的时候就会发生),就会抛 出NoSuchMethodError.
类的准备阶段
在准备阶段,java虚拟机为类的静态变量分配内存,并设置默认的初始值。例如对于 以下Sample类,再准备阶段,将int类型的静态变量a分配4个字节的内存空间, 赋予默认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值 0.
public class Sample {
private static int a = 1;
private static long b;
static{
b=2;
}
}
类的解析
再类的解析阶段,java虚拟机会把类的二进制数据中的符号引用替换为直接引用。例如再Worker类的gotoWork()方法中会引用Car类的run方法
public void gotowork(){
Car.run();
}
在Worker类的二进制数据中,包含了一个对Car类的run()方法的符号引用,它由run方法的全名和相关描述符组成。再解析阶段,java虚拟机会把做这个符号引用替换为一个指针,还指针指向Car类的run方法再方法区内的内存位置,这个指针就是直接引用。
类的初始化:
静态变量的声明语句,以及静态代码块都被看做类的初始化语句,Java虚拟机会按照初始化语句再类文件中的先后顺序类一次执行它们。例如当一下Sample类被初始化后它的静态变量a的值为4.
public class Sample {
static int a = 1;
static{
a = 2;
}
static{
a = 4;
}
}
一下是关于类加载过程中最容易犯错的一个列子:
package com.tai.classloader;
class Singleton
{
private static Singleton singleton = new Singleton();
public static int counter1;
public static int counter2 = 0;
private Singleton()
{
counter1++;
counter2++;
}
public static Singleton getInstance()
{
return singleton;
}
}
public class MyTest
{
public static void main(String[] args)
{
Singleton singleton = Singleton.getInstance();
System.out.println("counter1 = " + singleton.counter1);
System.out.println("counter2 = " + singleton.counter2);
}
}
打印结果往往出乎所料 1 0 ;
分析原因:
private static Singleton singleton = new Singleton();
public static int counter1;
public static int counter2 = 0;
加载
Null 0 0
初始化
new Singleton();的时候后面俩值为 1 1
之后再给连个变量赋值 第一个没有赋值就保持不变 第二有值就赋值为0
类的初始化步骤
加入这个类还没有被加载和链接,那就先加载和链接
假如存在直接的父类,并且这个父类还没有初始化就先初始化父类
假如类中存在初始化语句,那就一次执行这写初始化语句。
当java虚拟机初始化一个类的时候,要求它的所有的父类都已经被初始化,但是这条规则并不适用于接口。
再初始化一个类时,并不会先初始化它所实现的接口。
再初始化一个接口时,并不会先初始化它的父接口。
因此,一个父接口并不会因为它的子接口或者实现类的初始化的而初始化。只有当程序首次适用特定接口的静态变量时,才会导致该接口的初始化。
程序中对子类的“主动使用”会导致父类的初始化;但对父类的主动使用并不会导致子类初始化。
类的初始化时机:
只有当程序访问的静态变量或者静态方法确实再当前类或当前接口中定义时候才可以认为是对接口或类的主动使用
调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。