2、Java 类加载机制(多理解)

2.1 类的加载方式

类加载分为动态加载和静态加载。动态加载是从外存储器中加载类,一般类加载机制分析的也是动态加载。而静态加载本质上是从内存中创建类的实例对象,此时类已经被加载到内存中。

一.静态加载

java动态加载 jar是代码还是旧的 java静态加载和动态加载_加载

通过new关键字来创建Test的实例对象。

二.动态加载

java动态加载 jar是代码还是旧的 java静态加载和动态加载_java_02

通过Class.forName()来加载类,然后调用类的newInstance()方法实例化对象。

java动态加载 jar是代码还是旧的 java静态加载和动态加载_java_03

通过类加载器的loadClass()方法来加载类,然后调用类的newInstance()方法实例化对象。

这里有几个需要比较的地方:

1.通过new关键字实例化类的对象和通过Class.forName()加载类是当前类加载器,即this.getClass.getClassLoader,只能在当前类路径或者导入的类路径下寻找类。而用指定的classLoader来加载类可以从当前路径外寻找类,这里的classLoader甚至可以用户自定义。

2.有两个异常

静态加载类时出现的一般是NoClassDefFoundError。

动态加载类时出现的一般是ClassNotFoundException。

这两者经常被用来比较,其实区别很大。NoClassDefFoundError是错误,不方便被捕捉也不需要被捕捉,不应该尝试从error中恢复程序。他是由于在使用new关键字实例化类的对象时,在内存中找不到对象了,一般比较少见,在运行时发生,即编译时可以找到类运行时却找不到了。而ClassNotFoundException是异常,是可以被捕捉的,应该捕捉并处理尝试恢复程序。这是由于利用类名动态加载类的时候,在外存储器类路径下找不到该类或者其依赖的jar包,还有一个导致其的原因是在同一个包中同一个类被不同的类加载器加载了两遍。

2.2 类加载的过程

类加载的生命周期:加载(Loading)–>验证(Verification)–>准备(Preparation)–>解析(Resolution)–>初始化(Initialization)–>使用(Using)–>卸载(Unloading)

java动态加载 jar是代码还是旧的 java静态加载和动态加载_类加载器_04

主要就是三大过程:加载—>连接(验证、准备、解析)—> 初始化

加载(重点)

这个阶段通常也被称作“装载”,主要完成:

1.通过“类全名”来获取定义此类的二进制字节流

2.将字节流所代表的静态存储结构转换为方法区的运行时数据结构

3.在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口

验证

这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求。

准备

准备阶段是正式为变量分配内存并设置初始值,这些内存都将在方法区中进行分配,这里的变量仅包括类标量不包括实例变量。

解析

解析是虚拟机将常量池的符号引用替换为直接引用的过程。

初始化

初始化阶段是执行类构造器()方法的过程

2.3 JVM三种预定义类型类加载器

a. Bootstrap ClassLoader/启动类加载器
主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作.

b. Extension ClassLoader/扩展类加载器
主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作

c. System ClassLoader/系统类加载器
主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作.

d. User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性.

2.4 双亲委派加载

JVM在加载类时默认采用的是双亲委派机制, 先往上 让上层加载器去加载

java动态加载 jar是代码还是旧的 java静态加载和动态加载_类加载器_05


双亲委派模式

当一个类需要加载时,判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为 null 时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器。

沙箱安全机制(了解)

本地代码被认为是安全的,而远程代码是不被信任的,因此就需要这样的一个机制去对这些远程代码进行访问授权。

2.5 由不同的类加载器加载的指定类型还是相同的类型吗(不同)

在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间. 所以是不相同的

2.6 在代码中直接调用Class.forName(String name)方法,到底会触发那个类加载器进行类加载行为?

Class.forName(String name)默认会使用调用类的类加载器来进行类加载(谁引用谁去加载)

2.7 在编写自定义类加载器时,如果没有设定父加载器,那么父加载器是?

在不指定父类加载器的情况下,默认采用系统类加载器(AppClassLoader);

2.8 编写自定义类加载器时,一般有哪些注意点?

一般尽量不要覆写已有的loadClass(…)方法中的委派逻辑; 这样做极有可能引起系统默认的类加载器不能正常工作

2.9 如何在运行时判断系统类加载器能加载哪些路径下的类?

一 是可以直接调用ClassLoader.getSystemClassLoader()或者其他方式获取到系统类加载器(系统类加载器和扩展类加载器本身都派生自URLClassLoader),调用URLClassLoader中的getURLs()方法可以获取到;

二 是可以直接通过获取系统属性java.class.path 来查看当前类路径上的条目信息 , System.getProperty(“java.class.path”)

2.9 在Java的反射中,Class.forName和ClassLoader的区别

ClassLoader就是遵循双亲委派模型最终调用启动类加载器的类加载器
Class.forName()方法实际上也是调用的ClassLoader来实现的;在这个forName0方法中的第二个参数被默认设置为了true,这个参数代表是否对加载的类进行初始化,设置为true时会类进行初始化,代表会执行类中的静态代码块,以及对静态变量的赋值等操作。
Class.forName 默认会进行初始化,执行静态代码块;有参数可以设置

2.10 Java 类加载机制及常见异常

1.ClassNotFoundException 无法找到目标类 ,发生在“读取”阶段。

2.ClassNotFoundError发生在“链接”阶段。
两者区别是ClassNotFoundException发生时,可以认为是没有分配内存的,至多是一个byte[]的内存(存放.class字节码)。
而ClassNotFoundError会为Class对象准备好内存。

3.NoClassDefFoundError 当目前执行的类已经编译,但是找不到它的定义时。 通常发生在“链接”阶段。
也就是说你如果编译了一个类B,在类A中调用,编译完成以后,你又删除掉B,运行A的时候那么就会出现这个错误。

2.11 HelloWorld类的加载过程

java HelloWorld

命令的时候,JVM会将HelloWorld.class加载到内存中,并形成一个Class的对象HelloWorld.class。

其中的过程就是类加载过程:

1、寻找jre目录,寻找jvm.dll,并初始化JVM;

2、产生一个Bootstrap Loader(启动类加载器);

3、Bootstrap Loader自动加载Extended Loader(标准扩展类加载器),并将其父Loader设为Bootstrap Loader。

4、Bootstrap Loader自动加载AppClass Loader(系统类加载器),并将其父Loader设为Extended Loader。

5、最后由AppClass Loader加载HelloWorld类。