JAVA之ClassLoader

JAVA基础系列之ClassLoader

一,Java类的加载、链接与初始化

1,加载:查找并加载类的二进制数据

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

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

• 在内存中生成一个代表这个类的java.lang.Class类对象,作为方法区这个类的各种数据的访问入口。

2,链接

验证:

确保被加载类的正确性

准备:

为类的静态变量分配内存,并将其初始化为默认值

解析:

把类中的符号引用转化为直接引用

3,初始化

为类的静态变量赋予正确的初始值

二,JVM加载类的主要方式

从本地系统中直接加载

通过网络下载.class文件

从zip,jar等归档文件中加载.class文件

从专有数据库中提取.class文件

讲Java的源文件动态编译为.class

三,JVM加载类的种类及功能

JVM通过CLassLoader(类加载器)来动态加载某个class文件到内存当中的。ClassLoader总共分为以下四种:

1,根(Bootstrap)类加载器

该类加载器没有父加载器,他负责加载虚拟机的核心类库,如java.lang.*等。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。跟类加载器的实现依赖于底层操作系统属于虚拟机实现的一部分,它并没有继承java.lang.ClassLoader类。

2,扩展(Extension)类加载器

它的父类加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK安装目录的jrelibext子目录(扩展目录加载类库),如何用户创建的JAR问你件放在这个目录下也会被自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。

3,系统类加载器

也称为应用类加载器,它的父加载器为扩展类加载器。他从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader类的子类。

除了以上虚拟机自带的加载器,用户也可以继承java.lang.ClassLoader类实现自定义加载器。

四,类加载器的原理

1,原理介绍

ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。

2,使用双亲委托模型的原因

因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。

3,如何判断两个class相同

JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。就算两个class是同一份class字节码,如果被两个不同的ClassLoader实例所加载,JVM也会认为它们是两个不同class。比如网络上的一个Java类org.classloader.simple.NetClassLoaderSimple,javac编译之后生成字节码文件NetClassLoaderSimple.class,ClassLoaderA和ClassLoaderB这两个类加载器并读取了NetClassLoaderSimple.class文件,并分别定义出了java.lang.Class实例来表示这个类,对于JVM来说,它们是两个不同的实例对象,但它们确实是同一份字节码文件,如果试图将这个Class实例生成具体的对象进行转换时,就会抛运行时异常java.lang.ClassCaseException,提示这是两个不同的类型。

4,常用的方法

(1) loadClass方法

ClassLoader.loadClass()是ClassLoader的入口点。该方法的定义如下:

Class loadClass(String name,boolean resolve);

name是加载的类的名称,resolve是告诉方法是不中需要解析类PS:并不是所有的类都需要解析,如果JVM只想知道这个类是否存在或找出该类的超类,那么就不需要解析该类

(2) defineClass方法

defineClass方法接受由原始字节组成的数组,并把它转换成Class的对象。原始数组包含如从文件系统或网络装入的数据。defineClass管理JVM的许多复杂的实现层面——它把字节码分析成运行时数据结构、校验有效性等,因为defineClass方法被标记成final的,所以不能覆盖它。

(3) findSytemClass方法

findSystemClass方法就是查找本地类Class文件,然后装入

(4) resolveClass方法

我们在调用编写自己的loadClass方法的时候可以调用resolveClass方法来获得resolve参数

(5) findLoadedClass方法

在调用loadClass方法之前可以调用改方法来查看地ClassLoader是否已经装入了这个类,这样可以避免重新装入这个类

(6) findClass方法

在loadClass默认实现调用这个新方法。findClass的用途包含classLoader的所有特殊代码,而无须复制其他代码

(7) getSystemClassLoader方法

在如果覆盖findClass或loadClass,getSystemClassLoader能以实际的ClassLoader对象访问系统ClassLoader(而不是固定地从findSystemClass调用它)。为了将类请求委托给父类ClassLoader,这个新方法允许ClassLoader获取它的父类ClassLoader.当使用特殊方法,定制的ClassLoader不能找到类时,可以使用这种方法。

父类ClassLoader被定义成创建该ClassLoader所包含代码的对象的ClassLoader.

(8) forName方法

在Class类中有一个静态方法forName,这个方法和ClassLoader中的loaderClass方法的目的是一样的,都是用来加载Class的,但是两者在作用上却有所区别:

loadClass加载实际上就是加载的时候并不对该类进行解释,因此不会初始化该类。而Class类的forName方法则相反,使用forName加载的时候就会将Class进行解释和初始化

五,类加载器的使用

使用URLClassLoader去加载类

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class ClassLoaderTest {
 public static void main(String args[]) {
 try {
                URL url = new URL("file:/C:/Users/spark/Desktop/logs-analyzer.jar");
 URLClassLoader myClassLoader1 = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader());
 Class<?> clazz = myClassLoader1.loadClassQ("study.ClassLoaderTest.TestAction");
 Method mainClass = clazz.getMethod("action");
 Constructor<?> constructor = clazz.getConstructor();
 Object obj = constructor.newInstance();
 System.out.println(mainClass.invoke(obj));
 } catch (Exception e) {
            e.printStackTrace();
 }
    }
}

首先定义好一个类,然后打包成jar

public class TestAction{
 public String action()
    {
 return "this ActionTest class";
 }
}

六,Spark中的URLClassLoader简述

Spark使用内部使用的最多的类加载器就是URLClassloader。

private[spark] class MutableURLClassLoader(urls: Array[URL], parent: ClassLoader)
 extends URLClassLoader(urls, parent) {

 override def addURL(url: URL): Unit = {
 super.addURL(url)
  }

 override def getURLs(): Array[URL] = {
 super.getURLs()
  }

}

这样取决于Spark分布式计算的特性,后面源码系列讲述到运行环境的时候会详细说道这个问题。

原文发布于微信公众号 - Spark学习技巧(bigdatatip)

原文发表时间:2017-12-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏博岩Java大讲堂

多线程--同步与锁

2353
来自专栏前端侠2.0

co yield避免嵌套详细代码示例。

1471
来自专栏python爬虫日记

转载、Python的编码处理(二)

然后,大多数人的做法是,调用encode/decode进行调试,并没有明确思考为何出现乱码

932
来自专栏Ryan Miao

java并发编程读书笔记(1)-- 对象的共享

1. 一些原则 RIM(Remote Method Invocation):远程方法调用 Race Condition:竞态条件 Servlet要满足多个线程的...

3608
来自专栏我和PYTHON有个约会

16.pass关键字

在项目开发的过程中,某些情况下,我们定义了函数,但是对于函数中具体的代码临时没有确定,如:

1023
来自专栏木木玲

“类加载机制”详解

2791
来自专栏JMCui

多线程编程学习二(对象及变量的并发访问).

一、概念 非线程安全:会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是"脏读",也就是取到的数据其实是被更改过的. 线程安全:获得的实例变...

39714
来自专栏Java编程技术

什么是重排序与中断

Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序可以保证最终执行的结果是与程序顺序执行的结果一致,并且只会对不存在数据依赖性的指令进行重排序,...

822
来自专栏蓝天

文件和SOCKET跨线程安全吗?

将一个文件或SOCKET的句柄fd传递给多个线程,进行读、写和Close操作,是否安全了?答案是“否”,这类似于new一个指针后,这个指针传递给多线程是否安全,...

592
来自专栏码洞

知道Python语言的Google Fire项目么,我将它移植到了Java上

最近尝试了Python语言的开源命令行便捷工具库Google Fire,它是用来加速用户编写命令行程序的一个小工具库,该工具使用非常方便,节省了编写命令行程序繁...

892

扫码关注云+社区

领取腾讯云代金券