Java运行时环境---ClassLoader类加载机制
背景:听说ClassLoader类加载机制是进入BAT的必经之路。
ClassLoader总述:
普通的Java开发其实用到ClassLoader的地方并不多,但是理解透彻ClassLoader类的加载机制,无论是对我们编写更高效的代码还是进BAT都大有裨益;而从“黄埔军校”出来的我对ClassLoader的理解都是借鉴了很多书籍和博客,站在了各大博主的肩膀上,感谢你们!上菜,Classloader最主要的作用就是将Java字节码文件(后缀为.class)加载到JVM中,JVM在启动时不会一次性加载所有的class文件,而是根据需要动态加载class文件,毕竟一次性加载太多jar包的class文件JVM吃不消;下面主要研究Bootstrap ClassLoader、Extention ClassLoader和AppClassLoader这三种类加载器。
谈到ClassLoader就想到我们安装JDK的时候都会在控制台输入java、javac验证是否安装成功,而这个javac就是Java ClassLoader,测试是否能把Java源文件正确编译成Java字节码文件,下面的截图就是个javac的小例子,javac之后加载器把Java源文件编译成TestClassLoader.class字节码文件。

由于下面要讲到ClassLoader的加载路径,这里顺便把Java的环境变量也复习一遍。
JAVA_HOME:
指的是安装JDK的位置,如:JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home" 。
PATH:
配置PATH(程序的路径)的作用将就是能够在命令行窗口直接键入程序的名字了,而不再需要键入它的全路径,比如上面代码中我用的到javac和java两个命令。如:PATH=".$PATH:$JAVA_HOME/bin" ;就是在JAVA_HOME路径上添加了JDK下的bin目录即可。
CLASSPATH:
CLASSPATH就是指向jar包的路径,如:PATH=".$PATH:$JAVA_HOME/bin" ;"."表示当前目录。
ClassLoader类加载流程:
三个Class Loader的执行顺序是:Bootstrap CLassloder -> Extention ClassLoader -> AppClassLoader;
1、Bootstrap CLassloder是最顶层的加载类,主要是加载核心类库,也就是%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等资源;并且,可以通过启动JVM时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录,下面有个小荔子。
2、Extention ClassLoader是扩展的类加载器,其加载的是目录%JRE_HOME%\lib\ext目录下的jar包和class文件;它同样也可以加载-D java.ext.dirs选项指定的目录。
3、Appclass Loader是用于加载当前应用的classpath的所有类,其也称为SystemAppClass。
另外有兴趣的还可以看下Launcher类的源码,源码中规定了三个加载器的环境属性分别为B:sun.boot.class.path、E:java.ext.dirs和A:java.class.path;下面通过代码来简单测试写,如图:
打印输出结果:
BootstrapClassLoader:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/sunrsasign.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/classes
ExtClassLoader:
/Users/apple/Library/Java/Extensions:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext:
/Library/Java/Extensions:/Network/Library/Java/Extensions:
/System/Library/Java/Extensions:/usr/lib/java
AppClassLoader:
/TJT/Eclipse/workspace/tjt/bin:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar
为了更好的理解三者之间加载的关系,我们来测试一个类的加载器和它的父类加载以及一些不是我们创建的类如String、Double、int等基础类:

从上图中可用看出,自己编写的类Test2.class文件是由AppClassLoader加载的,并且AppClassLoader有父加载器ExtClassLoader,但ExtClassLoader的父加载器为null;Double.class这个Java基础类的加载器为null,其父加载也为空且程序还会报空指针异常错误;其实呢,Double.class是有Bootstrap CLassLoader加载的,也并不是每个加载器都有父加载器;总的来说就是JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,诸如一些int.class,String.class都是由它加载;JVM初始化sun.misc.Launcher并创建Extension ClassLoader和AppClassLoader实例,且将ExtClassLoader设置为AppClassLoader的父加载器;而Bootstrap虽然没有父加载器,但是它却可以作为一个ClassLoader的父加载器;另外,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader;
双亲委托:
当一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先会判断这个class是不是已经加载成功,如果没有加载的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后是由自身去查找这些对象;这种机制就叫做双亲委托。 
从上图可用看出ClassLoader的加载序列,委托是从下往上,查找过程则是从上向下的,以下有几个注意事项:
1、一个AppClassLoader查找资源时,首先会查看缓存是否有,若有则从缓存中获取,否则委托给父加载器;
2.、重复第一步的递归操作,查询类是否已被加载;
3.、如果ExtClassLoader也没有加载过,则由Bootstrap ClassLoader加载,它首先也会查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class下面的路径,找到就返回,找不到就让子加载器自己去找。
4.、Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader自己在java.ext.dirs路径中去查找,查找成功就返回,查找不成功则再向下让子加载器找。
5.、若是ExtClassLoader查找不成功,则由ppClassLoader在java.class.path路径下自己查找查找,找到就返回,如果没有找到就让子类找,如果没有子类则会抛出各种异常。
自定义CLassLoader:
在ClassLoader中有四个很重要实用的方法loadClass()、findLoadedClass()、findClass()、defineClass(),可以用来创建属于自己的类的加载方式;比如我们需要动态加载一些东西,或者从C盘某个特定的文件夹加载一个class文件,又或者从网络上下载class主内容然后再进行加载等。分三步搞定:
1、编写一个类继承ClassLoader抽象类;
2、重写findClass()方法;
3、在findClass()方法中调用defineClass()方法即可实现自定义ClassLoader;
需求:自定义一个classloader其默认加载路径为"/TJT/Code"下的jar包和资源。首先创建一个Test.java,然后javac编译并把生成的Test.class文件放到"/TJT/Code"路径下,然后再编写一个DiskClassLoader继承ClassLoader,最后通过FindClassLoader的测试类,调用再Test.class里面的一个find()方法。
package www.baidu;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; public class DiskClassLoader extends ClassLoader{
//自定义classLoader能将class二进制内容转换成Class对象
private String myPath; public DiskClassLoader(String path) {
myPath = path;
} //findClass()方法中定义了查找class的方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
String fileName = getFileName(name);
File file = new File(myPath,fileName);
try {
FileInputStream is = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = ;
try {
while((len = is.read()) != -) {
bos.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = bos.toByteArray();
is.close();
bos.close();
//数据通过defineClass()生成了Class对象
return defineClass(name, data,,data.length );
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
} private String getFileName(String name) {
int lastIndexOf = name.lastIndexOf('.');
if (lastIndexOf == -) {
return name + ".class";
}else {
return name.substring(lastIndexOf + ) + ".class";
}
}
}
测试结果如下:找到了自定义的加载路径并且调用了类中的find()方法

package www.baidu;
import java.lang.reflect.Method; public class FindClassLoader {
public static void main(String[] args) throws ClassNotFoundException {
//创建自定义classloader对象
DiskClassLoader diskL = new DiskClassLoader("/TJT/Code");
System.out.println("classloader is: "+diskL);
try {
//加载class文件
Class clazz = diskL.loadClass("www.baidu.Test");
if (clazz != null) {
Object object = clazz.newInstance();
Method declaredMethod = clazz.getDeclaredMethod("find", null);
//通过反射调用Test类的find()方法
declaredMethod.invoke(object, null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结:
除此之外,ClassLoader还可以进行程序加密(比如你写了比较骚的jar包),这样我们就可以在程序中加载特定了类,并且这个类只能被我们自定义的加载器进行加载,提高了程序的安全性,但是用的不多;反正我们在项目上是不允许用ClassLoader加密,宁愿裸奔,了解一下。另外就是tomcat的类加载机制也是遵循双亲委派机制的,并且大部分的加载机制和JVM类加载机制一样,理解了Bootstrap ClassLoader、Extention ClassLoader和AppClassLoader这三种加载器后再看tomcat类的加载就可以横着走了。
Java运行时环境---ClassLoader类加载机制的更多相关文章
- Java运行时环境---内存划分
背景:听说Java运行时环境的内存划分是挺进BAT的必经之路. 内存划分: Java程序内存的划分是交由JVM执行的,而不像C语言那样需要程序员自己买单(C语言需要程序员为每一个new操作去配对del ...
- kubernetes实战-交付dubbo服务到k8s集群(三)安装配置maven和java运行时环境的底包镜像
maven 官方地址: 官方地址 下载maven,shdd7-200 # cd /opt/src # wget https://archive.apache.org/dist/maven/maven- ...
- java 运行时环境和编译器环境
必须要保证运行环境高于编译环境 1.编译器的环境设置 单击项目右键-> Properties -> Java Compiler -> 5或6 如果编译器的环境高于运行时环境会报错. ...
- Windows7下Java运行时环境搭建
第一步:下载JDK 地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html,(由于Sun于2009年被oracle收 ...
- java 常用类库:操作系统System类,运行时环境Runtime
System类: System 类代表Java程序的运行平台,程序不能创建System类的对象, System类提供了一些类变量和类方法,允许直接通过 System 类来调用这些类变量和类方法. Sy ...
- ClassLoader类加载机制&&JVM内存管理
一.ClassLoader类加载机制 在java中类加载是遵循委派双亲加载的:通过调用loadClass方法逐级往上传递委派加载请求,当找不到父ClassLoader时调用其findClass方法尝试 ...
- java运行时内存模式学习
学习java运行时内存模式: 各区介绍: 方法区(线程共享):用于存放被虚拟机加载的类的元数据:静态变量,常量,以及编译和的代码(字节码),也称为永久代(所有该类的实例被回收,或者此类classLoa ...
- 运行时环境(The Runtime Environment)
App Engine应用响应网络请求.当一个客户端(典型的是用户的Web浏览器)使用HTTP请求(比如获取在URL上的网页)连接上应用的时候,网络请求就开始了.当App Engine接收到请求时,它会 ...
- Jvm基础(1)-Java运行时数据区
最近在看<深入理解Java虚拟机>,里面讲到了Java运行时数据区,这是Jvm基本知识,把读书笔记记录在此.这些知识属于常识,都能查到的,如果我有理解不对的地方,还请指出. 首先把图贴上来 ...
随机推荐
- 响应式编程知多少 | Rx.NET 了解下
1. 引言 An API for asynchronous programming with observable streams. ReactiveX is a combination of the ...
- Logistic回归二分类Winner or Losser----台大李宏毅机器学习作业二(HW2)
一.作业说明 给定训练集spam_train.csv,要求根据每个ID各种属性值来判断该ID对应角色是Winner还是Losser(0.1分类). 训练集介绍: (1)CSV文件,大小为4000行X5 ...
- MyEclipse 10导入JDK1.7或1.8
1.在MyEclipse,选择“windows”>"preferences"选择,打开“perference”窗口(如下图) 2.展开“perference”窗口左侧“jav ...
- 二分搜索树实现Java的Map(下)
二分搜索树Map public class BSTMap<K extends Comparable<K>,V> implements Map<K,V> { priv ...
- 彻底理解浏览器的缓存机制(http缓存机制)
一.概述 浏览器的缓存机制也就是我们说的HTTP缓存机制,其机制是根据HTTP报文的缓存标识进行的,所以在分析浏览器缓存机制之前,我们先使用图文简单介绍一下HTTP报文,HTTP报文分为两种: 同步s ...
- 『发呆』.Net 2.0 ~ .Net 4.0 所实现了那些底层
随着时间的推移,程序越写越大,代码越写越少. 今天突然发呆,就想比较全面的汇总一下 .Net 2.0 和 .Net 4.0 都实现的功能. .Net 2.0 的大部分常见程序集 (已经过滤掉了一部分和 ...
- forEach循环对集合进行循环时,需判断是否为null;
分析forEach的源码会发现:foreach源码例子: public class Foreach { public static void main(String[] args) { List< ...
- 兼容IE8浏览器移除class样式的方法
项目中发现,IE8下不兼容JQ的removeClass和addClass,页面无效果 网上找了很多方法都没有效果,最终找到一个很好的笨方法 var div=document.getElementByI ...
- 解决Error:All flavors must now belong to a named flavor dimension. Learn more at...
低版本的gradle里面不会出现这个错误,高版本出现,不多说,看如何解决 在defaultConfig{}中添加:flavorDimensions "default" 保证所有的f ...
- Java中char,short,int,long占几个字节和多少位
1.字节:byte:用来计量存储容量的一种计量单位:位:bit 2.一个字节等于8位 1byte = 8bit char占用的是2个字节 16位,所以一个char类型的可以存储一个汉字. 整型: b ...