1. 类的加载,连接和初始化
1. JVM和类
当我们调用java命令运行某个java程序时,该命令将会启动一条java虚拟机进程,不管该java程序有多么复杂,改程序启动了多少个线程,它们都处于该java虚拟机进程里,同一个JVM的所有线程,所有变量都处于同一个进程里,它们都是用该JVM进程的内存区
以下情况,JVM进程将被终止:
程序运行到最后正常结束
程序运行到使用System.exit()或Runtime.getRuntime().exit()代码结束程序
程序执行过程中遇到未捕获的异常或错误而结束
程序所在的平台强制结束了JVM进程
两次运行java程序处于两个不同的JVM进程中,两个JVM之间并不会共享数据。
JVM进程结束,该进程在内存中的状态将会丢失。
1. 类的加载
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载,连接,初始化三个步骤来对该类进行初始化,JVM将会连续完成这三个步骤,也把这三个步骤统称为类加载或类初始化。
类加载指的是将类Class文件读入内存,并为之创建一个java.lang.Class对象,也就是说程序使用任何类时,系统都会为之建立一个java.lang.Class对象
类是某一类对象的抽象,类是概念层次的东西,但类也是一种对象,每个类是一批具有相同特征的对象的抽象,而系统中所有类,它们实际上也是对象,都是java.lang.Class的实例
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器,开发者也可以通过继承ClassLoader基类来创建自己的类加载器
通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:1.从本地文件系统来加载Class文件,这是大部分实例程序的类加载方式
2从jar包中加载class文件,如JDBC编程时用到的数据库驱动类就是放在jar文件中,JVM可以从jar文件中直接加载该Class文件
3.通过网络加载class文件
4.把一个java源文件动态编译,并执行加载。
类加载器通常无需等到首次使用该类时才加载该类,java虚拟机规范允许系统预先加载某些类。
2. 类的连接:
当类加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段将会负责把类的二进制数据合并到jre中,类连接分为如下三个阶段:
1. 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
2. 类准备阶段:负责为类的静态属性分配内存,并设置默认初始值
3. 解析:将类的二进制数据中的符号引用替换成直接引用
类的初始化:
虚拟机负责对类进行初始化,主要是对静态属性进行初始化,在java中对静态属性指定初始化值有两种方式:
1.声明静态属性时指定初始值
2.使用静态初始化块为静态属性指定初始值
JVM初始化一个类包含如下步骤:
1. 假如这个类还没有被加载和连接,程序先加载并连接该类
2. 假如该类的直接父类还没有被初始化,则先初始化其直接父类
3. 假如类中有初始化语句,则系统依次执行这些初始化语句。
当执行第二步时,系统对直接父类的初始化步骤也遵循1,2,步骤,如果该直接父类又有直接父类,系统再次重复这三个步骤来先初始化这个父类。。。。以此类推,所以JVM最先初始化的总是java.lang.Object类。当程序主动使用任何一个类时,系统会保证该类以及所有父类(包括直接父类和间接父类)都会被初始化。
类初始化的时机:
Java程序首次通过下面6种方式使用某个类或接口时,系统会初始化该类和接口
1. 创建类的实例 new操作符来创建实例,通过反射来创建实例,通过反序列化方式来创建实例。
2. 调用某个类的静态方法
3. 访问某个类或接口的静态属性,或为该静态属性赋值
4. 使用反射来强制创建某个类或接口对应的java.lang.Class对象:Class.forName(“person”),如果系统还未初始化Person类,则这行代码将导致该Person类被初始化,并返回Person类对应的java.lang.Class对象
5. 初始化某个类的子类,当初始化某个类的子类时,该子类的所有父类都会被初始化
6. 直接使用java.exe命令来运行某个主类,当运行某个主类时,程序会先初始化该主类
对于一个final型的静态属性,如果该属性可以在编译时就得到属性值,则可认为该属性可被当成编译时常量,当程序使用编译时常量时,系统会认为这是对该类的被动使用,不会导致该类的初始化,当某个静态的属性使用final修饰,而且它的值可以在编译时得到,那么程序其他地方使用该静态属性时,实际上并不会使用该静态属性,而是相当于使用常量,反之如果final类型的静态属性值不能在编译时得到,必须等到运行时才可以确定该属性的值,如果通过该类来访问该静态属性,则可以认为是主动访问使用该类,将会导致该类被初始化
当使用ClassLoader类的loadClass()方法来加载某个类时,该方法只是加载该类,并不执行该类的初始化,当使用Class的forName(“”)静态方法才会导致强制初始化该类
类加载器:
类加载器:负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象。
一个类被载入JVM中,同一个类就不会被载入了,一个载入JVM的类也有一个唯一的标志,在java中一个类用其全限定类名(包名和类名)作为标志,但在JVM中一个类用其全限定类名和其类加载器的实例作为唯一标志。
当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:
Booterstrap Classloader:根类加载器
负责加载java的核心类,它并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。
获得根类加载器加载了哪些核心类库。
Public class BootstrapTest{
Public static void main(String[] args){
//获取跟类加载器所加载的全部URL数组
URl[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
//遍历输出根类加载器加载的全部URL
For(int I = 0;i<urls.length;i++)
{
System.out.println(urls[i].toExternalForm());
}
}
}
Extension Classloader:扩展类加载器
负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的目录)中的JAR的类包,通过这种方式可以为java扩展核心类以外的新功能,只要我们把自己的类打包成jar文件,然后放入JAVA_HOME/jre/lib/ext路径即可
System Classloader:系统类加载器
负责在JVM启动时加载来自命令java中的-classpath选型或java.class.path系统属性或CLASSPATH环境变量所指定的JAR包或类路径
类加载器机制:
JVM的类加载机制主要有如下三种机制:
全盘负责:当一个类加载器负责加载某个Class的时候,该Class所依赖的和引用的其他的Class也将由该类加载器负责载入,除非显示的使用另一个类加载器来载入
父类委托:先让 parent类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
缓存机制:保证所有被加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存中搜寻该Class,只有当缓存中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转化成Class对象,并存入cache,这就是为什么修改了Class后,程序必须重新启动JVM,程序所作的修改才会生效的原因
类加载器之间的父子关系并不是类继承上的父子关系,这里的父子关系是类加载器实例之间的关系
根类加载器
|
|
|
扩展类加载器
|
|
|
系统类加载器
|
|
用户类加载器
除了java类提供的加载器之外,开发者也可以实现自己的类加载器,自定义的类加载器通过继承Classloader来实现
Public class ClassLoaderPropTest
{
Public static void main(String[] args)
{
//获取系统类加载器
ClassLoader systemloader = Classloader.getSystemClassLoader();
/*
获取系统类加载器的加载路径通常由ClassPath环境变量指定,如果操作系统没有指定ClassPath环境变量,默认以当前路径为系统类加载器的加载路径
*/
Enumration<URL> eml = systemloader.getResources(“”);
While(eml.hasMoreElements())
{
System,out.println(eml.nextElement());
}
//获取系统类加载器的父类加载器,应得到扩展类加载器
Classloader extensionLader = systemloader.getParent();
System.out.println(“扩展类加载器”+extensionlader);
System.out.println(“扩展类加载器的加载路径”+system.getProperty(“java.ext.dir”));
System.out.println(“扩展类加载器的parent”+extensionlader.getParent());
}
}
系统类加载器的加载路径是程序运行的当前路径,扩展类加载器的加载路径是JAVA_HOPM\jre\lib\ext。扩展类加载器的父类加载器是null.并不是根类加载器,这是因为根类加载器并没有继承ClassLoader抽象类,所以扩展类加载器的getParent()方法返回null.实际上根类加载器确实是扩展类加载器的父类加载器
程序看到系统类加载器是AppClassLoader的实例,扩展类加载器是ExtClassLoader的实例,实际上这两个类都是URLClassLoader类的实例
类加载器加载Class大致经8个步骤
1. 检测该Class是否存在(缓存中是否有有该Class),如果有直接进入第8步,否则执行第2步
2. 如果父类加载器不存在(如果没有父类加载器,则要么parent一定是根加载器,要么本身就是根加载器),直接跳到第4步,如果父类加载器存在执行第3步
3. 请求父类加载器载入目标类,如果成功载入则跳到第8步,不成功接着执行第5步
4. 请求使用根加载器来载入目标类,如果成功载入跳到第8步,不成功执行第5步
5. 寻找Class文件(从于此ClassLoader相关的类路径中寻找),如果找到则执行第6步,如果找不到则跳到第7步
6. 从文件中载入Class,成功后跳到第8步
7. 抛出ClassnotFoundException
8. 返回Class
其中5,6步允许重写Classloader的findClass方法来实现自己的载入策略,甚至重写loadClass方法来实现自己的载入过程