JVM类加载机制浅析
类的生命周期
加载->验证->准备->解析->初始化->使用->卸载
注意:(类加载过程中包括了加载、验证、准备、解析、初始化。但是加载、验证、准备、初始化是按顺序发生而解析阶段不一定)这几个阶段是按顺序开始而不是按顺序进行或完成,通常都是交叉混合进行的,在一个阶段的过程中调用或激活另一个阶段。
- 加载
(1)通过一个类的全限定名来获取限定的二进制流
(2)把这个字节流代表的静态储存结构转换成方法区运行的数据结构
(3)在java堆中生成一个代表这个类的java.lang.class对象,来作为对方法区中这些数据的访问入口
- 验证
- 文件格式验证--是否符合Class文件格式的规范
- 元数据验证--保证信息符合java语言规范的要求
- 字节码验证--通过数据流和控制流分析,确定程序语义合法,符合逻辑
- 符号引用验证--确保解析动作正常进行
- 准备
- 进行内存分配包括类变量,不包括实例变量,实例变量会在对象实例化的时候随对象一块分配在堆中。
- 设置的初始值一般是数据类型默认的零值(0,null,false等)而不是被在java代码中被赋予的值。
例如:
一个类变量定义为: private static int value = 3;
value 在准备阶段过后的初始值为0,而不是3,是因为这时候尚未开始执行任何java放法,把value赋值为3的指令是在程序编译后存在类构造器
<clinit>() 方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。
注意点:
(1)对于全局变量和类变量,如果不显式的赋值而直接使用,系统会赋予默认的零值(0,null,false等),但是对于局部变量,在使用前必须进行显式赋值,否则会发生编译不通过。
(2)只被final修饰的常量既可以在声明时显式的赋值又可以在类初始化时显式的赋值,总之在使用前必须显式的赋值,系统不会赋予默认零值。
(3)对于引用数据类型reference,如数组引用,对象引用等,若没有显式赋值,系统都会赋予默认零值,即NULL
(4)数组在初始化时没有对数组中各元素赋值,则各元素会根据对应的数据类型被系统赋予零值
- 解析
把类中符号引用转换成直接引用,符号引用就是一组符号来描述目标,可以是任何字面量,直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
- 初始化
为类的静态变量赋予正确的初始值,JVM对类初始化,主要是对类变量进行初始化。对类进行初始值设定有两种方式:
- 声明类变量是指定初始值
- 使用静态代码块为类变量的指定初始值
JVM初始化步骤
- 如还没有被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还没被初始化,则先初始化父类
- 若父类有初始化语句,则系统依次执行初始化语句
类主动初始化时机
- 创建类的实例,就是new
- 访问某个类或接口的静态变量,或者给静态变量赋值
- 调用类的静态方法
- 反射 Class.forName(“”)
- 初始化某个类的子类,则父类也会被初始化
- Jvm启动时被标明为启动类的类(java Test),直接使用java.exe命令运行某个主类
结束生命周期
- 执行System.exit()方法
- 正常执行结束
- 遇到异常或错误
- 操作系统出现错误
- 类加载器
- 启动类加载器(bootstrap Classloader)负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
- 扩展类加载器(Extension ClassLoader)sun.misc.Launcher$ExtClassLoader来实现,它负责加载DK\jre\lib\ext目录中或者由java.ext.dirs系统变量指定的路径中所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器
- 应用程序类加载器(Application ClassLoader),该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
- 自定义类加载器:
(1)在执行非置信代码之前,自动验证数字签名
(2)动态的创建符合用户特定需要的定制化构建类
(3)从特定的场所取得java class 例如数据库中和网络中
- JVM类加载机制
- 全盘负责,当一个类加载器负责加载某个class时,改class所依赖和引用的其他class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入
- 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
- 缓存机制缓存机制,缓存机制将会保证加载过的class都会被缓存当程序需要使用某个class时,类加载器先从缓存区寻找改class只有缓冲区不存在,系统才会读取带该类对应的二进制数据,将转换成class对象,存入缓存区。所以在修改class后需要重启JVM
- 类的加载
- 命令行启动时由JVM初始化加载
- 通过class.forname()动态加载
- 通过classLoader.loadClass()方法动态加载
注意:
Class.forname和classloader.loadclass区别
(1)class.forname将类的class文件加载到jvm中,还会对类进行解释,并执行到static块。
(2)classloader.loadclass是将class文件加载到jvm,并不执行static中内容,只有在newInstance才会执行static块
- 双亲委派模型
- 工作流程:如果一个类加载器收到类加载的请求,首先不会自己去尝试加载这个类,而是委托给父加载器去完成,依次向上,因此所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它搜索范围中没有找到所需的类时,即无法完成该加载,子加载才会尝试自己去加载该类。
- 双亲委派机制
- 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
(2)当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
(3)如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
(4)若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
- 自定义类加载器
1.自定义加载器一般都是继承自classloader类,从上面对loadclass方法来分析来看,我们只需要重写findclass方法即可
原文地址:http://www.cnblogs.com/ityouknow/p/5610232.html