类的加载机制与反射

当java程序运行时,将会启动一个jvm虚拟机。不管该程序多复杂,有多少线程,都处于同一个进程中。两次运行的java程序属于两个不同的jvm进程,数据 不会共享。因为当java程序运行结束时,jvm进程结束,进程在内存中的状态将会丢失。

类的加载

如果类还没有被加载到内存中,系统会通过加载,链接,初始化三个步骤。一般jvm连续完成,所以统称为加载或初始化

类加载是指将class文件读入内存,并为之创建一个java.lang.Class对象。类是java.lang.Class的实例。

类的加载由类加载器完成,类加载器有JVM提供。开发者可继承ClassLoader基类来创建自己的类加载器。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:

从本地文件系统来加载class文件,这是前面绝大部分示例程序的类加载方式。

从JAR包中加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就是放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。

通过网络在加载class文件。 把一个Java源文件动态编译、并执行加载。

加载器通常无须等到首次使用该类才加载,jvm运行预先加载某些类

类的连接

当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段将会负责把类的二进制数据合并到JRE中。类连接又可分为如下三个阶段:

验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。

准备:类准备阶段则负责为类的静态属性分配内存,并设置默认初始值。

解析:将类的二进制数据中的符号引用替换成直接引用。

类的初始化

类的初始化,主要是对类变量(静态变量)进行初始化

jvm会按照类初始化语句的顺序进行初始化。

初始化步骤

(1)假如这个类还没有被加载和连接,程序先加载并连接该类。

(2)假如该类的直接父类还没有被初始化,则先初始化其直接父类。

(3)假如类中有初始化语句,则系统依次执行这些初始化语句。

类初始化时机

创建类的实例。为某个类创建实例的方式包括使用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例。

调用某个类的静态方法。 访问某个类或接口的静态属性,或为该静态属性赋值。 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。例如代码:Class.forName("Person")。

初始化某个类的子类,当初始化某个类的子类时,该子类的所有父类都会被初始化。

直接使用java.exe命令来运行某个主类,当运行某个主类时,程序会先初始化该主类。

final型的静态属性,如果该属性可以在编译时就得到属性值,则可认为该属性可被当成编译时常量。当程序使用编译时常量时,系统会认为这是对该类的被动使用,所以不会导致该类的初始化。

类加载器

JVM的类加载机制主要有如下三种机制:

全盘负责:所谓全盘负责,就是说当一个类加载器负责加载某个Class的时候,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。

父类委托:所谓父类委托则是先让parent(父)类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。

缓存机制:缓存机制将会保证所有被加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存中搜寻该Class,只有当缓存中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,并存入cache。这就是为什么我们修改了Class后,程序必须重新启动JVM,程序所作的修改才会生效的原因。

根类加载器

扩展类加载器

系统类加载器

用户类加载器

扩展类加载器的父类是根类加载器,但是根类加载器不是java实现的,所以扩展类加载器获取父类加载器时返回null

他们之间的父子关系并不是继承上的,是类加载器实例上的。

类加载器加载Class大概有8步

1.检查是否载入过(在内存中是否有此Class),如果有进入第8步,否则下一步

2.如果父类加载器不存在(幺妹父类一定是根类加载器,要么自己就是根类加载器)则调到第4步,否则如果父类加载器存在第3步

3.请求使用父类加载器载入目标类,成功则第8,失败第5

4.请求使用根类加载器区载入目标类,如果成功则第8步,否则第7

5当前类加载器尝试寻找Class文件,如果找到顺利执行第6,找不到第7

6从文件中载入Class, 成功到第8

7抛出ClassNotFoundException异常

8返回对应的java.lang.Class对象

其中第5,6步允许重写ClassLoader的findClass方法来实现自己的载入策略,甚至重写loadClass方法来实现自己的载入过程。

ClassLoader类有如下二个关键方法:

loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象。

findClass(String name):根据二进制名称来查找类。 如果需要实现自定义的ClassLoader,可以通过重写以上两个方法来实现,当然我们推荐重写findClass()方法,而不是重写loadClass()方法。

执行代码前自动验证数字签名。 根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译class文件。 根据用户需求来动态地加载类。 根据应用需求把其他数据以字节码的形式加载到应用中。

Java为ClassLoader提供了一个URLClassLoader实现类,该类也是系统类加载器和扩展类加载器的父类(此处是父类,而不是父类加载器,这里是类与类之间的继承关系),URLClassLoader功能比较强大,它既可以从本地文件系统获取二进制文件来加载类,也可以从远程主机获取二进制文件来加载类。 实际上应用程序中可以直接使用URLClassLoader来加载类,URLClassLoader类提供了如下两个构造器: URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的系列路径来查询、并加载类。 URLClassLoader(URL[] urls, ClassLoader parent):使用指定的父类加载器创建一个ClassLoader对象,其他功能前一个构造器相同。

猜你喜欢

转载自www.cnblogs.com/tomato190/p/12505696.html