Java Class Loader

Reference:

[1] http://www.cnblogs.com/kevin2chen/p/6714214.html

 

当调用 java命令运行一个java程序时,会启动一个java虚拟机进程。同一个jvm的所有线程、所有变量都处于同一个进程里,都使用该jvm进程的内存区。 jvm进程终止,jvm内存中的数据将全部丢失。


jvm进程终止的情况:

1.程序运行到最后正常结束。

2.遇到System.exit()或Runtime.getRuntime.exit()。

3.遇到未捕获的异常或错误

4.程序所在的平台强制结束了JVM进程


类的加载

当程序主动使用某个类时,如果该类还未被加载到内存中,系统会进行类加载。类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class的实例。因为java中万物皆为对象,类也是java.lang.Class类型的对象。

类加载具体有加载、连接和初始化3个步骤,加载阶段需要完成的事情有:

  1)通过一个类的全限定名来获取定义此类的二进制字节流。

  2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

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


类的加载,是由类加载器来完成的,类加载器通常由JVM提供。通过使用不同的类加载器,可以加载以下不同来源的类的二进制数据:

  1.从本地文件系统加载class文件

  2.从jar包中直接加载class文件

  3.通过网络加载class文件

  4.把一个java源文件动态编译并加载

类的连接

负责把类的二进制数据合并到JRE(java运行环境)中

  1.验证:检测被加载的类是否有正确的内部结构,是否被破坏或包含不良代码,并和其他类协调一致

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

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

类初始化

主要对类的静态属性进行初始化。可以在声明静态属性时指定初始值和通过静态初始化块指定初始值,JVM会按照这些语句在程序中的顺序依次执行。

类加载器

每个类加载器(除了根类加载器)都是java.lang.ClassLoader的实例,负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象。

同一个.class不会被同一个类加载器加载两次,如何判断是同一个类: java中,一个类用其全限定类名标识--包名+类名;jvm中,一个类用其全限定类名+其类加载器标识---包名+类名+类加载器名

加载器层次结构:

JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:

1.Bootstrap ClassLoader 根类加载器(引导类加载器),加载java的核心类(限定的加载路径:jdk1.8_02/jre/lib/rt.jar)。它不是java.lang.ClassLoader的子类,而是由JVM自身实现的。虚拟机为了安全性以及功能的完整性,并不是任何存在于启动类加载器路径下的jar都会被加载,只有可信类(trusted classes )会被加载,所以开发者不要将自定义的类放在此目录。

2.Extension ClassLoader 扩展类加载器,加载JRE的扩展目录中JAR的类包(限定的加载路径:%JAVA_HOME%/jre/lib/ext/或java.ext.dirs系统属性指定的目录)

3.System ClassLoader 系统类加载器,加载java.class.path系统属性或CLASSPATH环境变量所指定的jar包和类路径,可通过ClassLoader的静态方法ClassLoader.getSystemClassLoader()获取系统类加载器。如果没有特别指定,用户自定义的类加载器以该类加载器作为它的父加载器。也可以通过Class实例的getClassLoader()方法当前类的类加载器。

4.自定义类加载器

JVM系统自带的类加载器在程序运行中只能加载对应路径的.class文件,无法改变其搜索路径。如果想在运行时从其他路径加载类,就要编写自定义的类加载器。

类加载的机制

1.全盘负责:某类所依赖及其引用的所有类,都由同一个加载器负责加载,除非显示使用另外一个加载器。

2.双亲委托: 当一个类加载器收到类加载的请求,首先将请求委派给父类加载器,递归到Bootstrap ClassLoader。然后加载器根据请求尝试搜索和加载类,若无法加载该类时则向子类加载器反馈信息(抛出ClassNotFoundException),由子类加载器自己去加载。

类加载期之间的父子关系并不是类继承上的父子关系,是采用组合的方式实现双亲委派模型的。

根类加载器是由c++实现的,不是由java语言实现,没有继承ClassLoader,所以扩展类加载器调用parent()返回的是null。但扩展类加载器实际上仍可以委派给根类加载器。

使用双亲委托模式的原因:被父类加载器加了的类可以避免避免被子类重新加载,因为在JVM中由全限定名+类加载器名标识类。另外可以避免加载到同sun公司核心API同名的恶意类。

自定义类加载器

JVM中除了根加载器之外,所有类加载器都是ClassLoader子类的实例,开发者通过扩展ClassLoader并重写ClassLoader所包含的方法来实现自定义的类加载器。

如上源码所示,loadClass()方法内部主要是双亲委派模型的实现,在最后调用findClass()方法加载类,所以自定义类加载器重写findClass()比重写loadClass()方便,可以避免覆盖默认类加载器的父类委托和缓冲机制两种策略。

ClassLoader类的核心方法:

  1. protected synchronized Class<?> loadClass(String name, boolean resolve)根据指定的binary name(由两个不同部分组成的名字,即全限定类名)来加载类,返回Class对象。

  2. protected Class<?> findClass(String name)根据二进制名称来查找类,返回Class对象。此方法应该被自定义类加载器的实现重写,在通过父类加载器检查所请求的类后,被loadClass方法调用。

  3. final defineClass(String name,byte[] b,int off,int len)将指定类的字节码文件读入字节数组内,并把它转为Class实例,该字节码文件可以来源于网络或本地。

  4. findLoadedClass(String name)返回jvm装载的name类的Class实例,若无返回null。

  5. static getSystemClassLoader()返回系统类加载器。

  6. findSystemClass(String name)从本地文件系统加载class文件,并生成Class对象。

使用自定义类加载器可以实现如下功能:

  执行代码前自动验证数字签名;

  根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译class文件;

  根据用户需求来动态的加载类;

  根据应用需求把其他数据以字节码的形式加载到应用中;

URLClassLoader 类

java 为ClassLoader提供了一个实现类URLClassLoader ,该类也是系统类加载器和扩展类加载器的父类,它可以从本地文件系统和远程主机获取二进制文件来加载类。

URLClassLoader的两个构造器

  URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的系列路径来查询并加载类。

  URLClassLoader(URL[] urls, ClassLoader parent):使用指定的父类加载器创建。。。

一旦获得URLClassLoader对象后,就可以调用类加载器的 loadClass()方法来加载指定的类。

加载图片、视频、文本等非类资源

ClassLoader除了用于加载类外,还可以用于加载图片、视频等非类资源,也是采用双亲委派模型将加载资源的请求传递到顶层的Bootstrap ClassLoader,在其对应的目录下搜索资源,若失败才逐层返回并搜索。

相关的实例方法:

  URL getResource(String name):资源名称name是以 '/' 分隔的标识资源的路径名称,返回资源的 URL 对象的枚举。

  InputStream getResourceAsStream(String name):返回读取指定资源的输入流。

  Enumeration<URL> getResources(String name)

1)class.getResource(String path):返回URL对象

  path以'/'开头时,'/'表示ClassPath,从项目的classpath下查询资源;

  path不以'/'开头时,是从此类所在的包下查询资源;

步骤:先递归在所有parent classLoader的classpath里查找resource,如果未找到,则在JVM内置的calss loader的路径中查找。

2)class.getClassLoader().getResource(String name):返回URL对象

The name of a resource is a '/'-separated path name that identifies the resource.

name不能以'/'开头,指资源标识符(即相对于classpath的路径)。一般web项目的classpath路径为……/webapps/appName/WEB-INF/classes/。如果资源不在此目录下,需要用使用相对路径做调整。

String path= MyService.class.getClassLoader().getResource("config.properties").getPath();
Properties prop = new Properties();
prop.load(new FileReader(path));

原文地址:https://www.cnblogs.com/codingforum/p/6735050.html