JVM--类加载器
概念
类加载器:只参与加载过程中的字节码获取并加载到内存中的部分;java虚拟机提供给应用程序去实现获取类和接口字节码数据的一种技术,也就是说java虚拟机是允许程序员写代码去获取字节码信息
类加载是加载的第一步,主要有以下三个步骤:
(1)通过全类名获取定义此类的二进制字节流;
(2)将字节码文件的静态存储结构转换成方法区运行时的数据结构;
(3)在内存中生成一个该类的class对象,作为方法区的访问入口;
类加载器:
(1)类加载器是负责加载类的一个对象,用于类加载中加载这一步;
(2)每一个java类都有一个引用指向加载他的ClassLoader;
(3)数组类不是通过ClassLoader创建的(数组类没有对应的二进制字节流),是有jvm直接生成的
其实除了加载类之外,类加载器还可以加载 Java 应用所需的资源如文本、图像、配置文件、视频等等文件资源。本文只讨论其核心功能:加载类。
类加载器的分类
(1)jvm底层源码实现:实现语言和虚拟机底层语言一致,比如Hotspot使用C++。主要目的是保证Java程序运行中基础类被正确地加载,比如java.lang.String,Java虚拟机需要确保其可靠性。
(2)java代码实现:jdk默认提供的类加载器,或者是程序员按照需求定制,所有java实现的类加载类都需要继承classloader这个抽象类。
jdk8以前:
启动类加载器
-
启动类加载器(Bootstrap ClassLoader)是由Hotspot虚拟机提供的、使用C++编写的类加载器。
-
默认加载Java安装目录/jre/lib下的类文件,比如rt.jar,tools.jar,resources.jar等。
/**
* 启动程序类加载器案例
*/
public class BootstrapClassLoaderDemo {
public static void main(String[] args) throws IOException {
ClassLoader classLoader = String.class.getClassLoader();
System.out.println(classLoader);
System.in.read();
}
}
上面的代码打印出来的类加载器为null,因为启动类加载器在jdk8中是由c++来编写的,在java代码中获取不安全,所以返回了null。
如果用户想扩展一些比较基础的jar包,让启动类加载器加载,有两种途径:
-
放入jre/lib下进行扩展。不推荐,尽可能不要去更改JDK安装目录中的内容,会出现即时放进去由于文件名不匹配的问题也不会正常地被加载。
-
使用参数进行扩展。推荐,使用-Xbootclasspath/a:jar包目录/jar包名 进行扩展,参数中的/a代表新增。
扩展类加载器和应用程序加载器
(1)扩展类加载器和应用程序加载器都是由jdk提供,使用java代码编写;
(2)它们的源码都位于sun.misc.Launcher中,是一个静态内部类。继承自URLClassLoader(具备通过目录或者指定jar包将字节码文件加载到内存中)
扩展类加载器
扩展类加载器(Extension Class Loader)是JDK中提供的、使用Java编写的类加载器。默认加载Java安装目录/jre/lib/ext下的类文件。
/**
* 扩展类加载器
*/
public class ExtClassLoaderDemo {
public static void main(String[] args) throws IOException {
ClassLoader classLoader = ScriptEnvironment.class.getClassLoader();
System.out.println(classLoader);
}
}
通过扩展类加载器去加载用户jar包:
-
放入/jre/lib/ext下进行扩展。不推荐,尽可能不要去更改JDK安装目录中的内容。
-
使用参数进行扩展使用参数进行扩展。推荐,使用-Djava.ext.dirs=jar包目录 进行扩展,这种方式会覆盖掉原始目录,可以用;(windows):(macos/linux)追加上原始目录
如下图中:
使用引号
将整个地址包裹起来,这样路径中即便是有空格也不需要额外处理。路径中要包含原来ext文件夹,同时在最后加上扩展的路径。
应用程序加载器
/**
* 应用程序类加载器案例
*/
public class AppClassLoaderDemo {
public static void main(String[] args) throws IOException, InterruptedException {
//当前项目中创建的Student类
Student student = new Student();
ClassLoader classLoader = Student.class.getClassLoader();
System.out.println(classLoader);
//maven依赖中包含的类
ClassLoader classLoader1 = FileUtils.class.getClassLoader();
System.out.println(classLoader1);
Thread.sleep(1000);
System.in.read();
}
}
双亲委派机制
概念:
向上检查是加载过这个类,向下加载;在类加载的过程中,每个类加载器都会先检查是
否已经加载了该类,如果已经加载则直接返回,否则会将加载请求委派给父类加载器。
例子:
B类在扩展类加载器加载路径中,同样应用程序类加载器接到了加载任务,按照案例1中的方式一层一层向上查找,发现都没有加载过。那么启动类加载器会首先尝试加载。它发现这类不在它的加载目录中,向下传递给扩展类加载器。
如果第二次再接收到加载任务,同样地向上查找。扩展类加载器发现已经加载过,就可以返回了。
作用:
1.保证类加载的安全性。通过双亲委派机制避免恶意代码替换JDK中的核心类库,比如
java.lang.String,确保核心类库的完整性和安全性。
2.避免重复加载。双亲委派机制可以避免同一个类被多次加载。
打破双亲委派机制:
(1)自定义类加载器;
(2)线程上下文加载器;
(3)osgi框架的类加载器;