基础概念
将CLASS文件加载到JVM中。但是由于JAR包很多,一次加载内存可能会崩溃,所以要动态的加载这些类。
关于CLASS文件
JVM虚拟机只识别class文件,所以如果你用C或者别的语言编写完成徐并编译为class文件,JVM虚拟机也可以识别
命令
javac 编译为.class
java ~ 加载该类
关于环境变量
- JAVA_HOME
只是一个JDK的目录路径
指的是你JDK安装的位置,一般默认安装在C盘,如
C:\Program Files\Java\jdk1.8.0_91
- PATH
将程序路径包含在PATH当中后,在命令行窗口就可以直接键入它的名字了,而不再需要键入它的全路径,比如上面代码中我用的到javac和java两个命令。 也就是在原来的JAVA_HOME路径上添加JDK目录下的bin目录和jre目录的bin(我的只添加了JDK路径,JDK应该是包含JRE的)
JRE只是提供运行环境,即核心类库JVM等,而JDK包含了JRE而且还提供开发工具,即像javac java这样的命令
PATH=%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;%PATH%;
- CLASSPATH
一看就是指向jar包路径。 我理解为JAVA提供的JAR包都在这里。
CLASSPATH=.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
需要注意的是前面的.;,.代表当前目录。
- 命令
查看环境变量
echo %JAVA_HOME%
echo %PATH%
echo %CLASSPATH%
三个类加载器
- Bootstrap ClassLoader
最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。(JRE_HOME 使用Eclipse时候取决于给项目设定的JRE环境,你可以选择JDK下的JRE也可以使用独立的JRE,我认为使用命令行编译的时候会默认使用JDK下面的JRE环境,因为没有设置%JRE_HOME%这个变量)。
java -Xbootclasspath/a:path
将指定的path追加到默认的bootstrap路径中。
测试:System.out.println(System.getProperty("sun.boot.class.path"));
此处JRE环境选择为独立JRE
C:\Program Files\Java\jre1.8.0_111\lib\resources.jar;C:\Program Files\Java\jre1.8.0_111\lib\rt.jar;C:\Program Files\Java\jre1.8.0_111\lib\sunrsasign.jar;C:\Program Files\Java\jre1.8.0_111\lib\jsse.jar;C:\Program Files\Java\jre1.8.0_111\lib\jce.jar;C:\Program Files\Java\jre1.8.0_111\lib\charsets.jar;C:\Program Files\Java\jre1.8.0_111\lib\jfr.jar;C:\Program Files\Java\jre1.8.0_111\classes
- Extention ClassLoader
扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。
测试:
System.out.println(System.getProperty("java.ext.dirs"));
C:\Program Files\Java\jre1.8.0_111\lib\ext;C:\Windows\Sun\Java\lib\ext
Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。
测试下System.out.println(System.getProperty("java.class.path"));
结果为
E:\javastudy\测试区\bin
这个路径其实就是当前java工程目录bin,里面存放的是编译生成的class文件。
关于类加载器的父类加载器
测试,编写一个测试类Test,然后
ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
结果
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
这个说明,AppClassLoader的父加载器是ExtClassLoader。那么ExtClassLoader的父加载器又是谁呢?
System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString());
结果
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
Exception in thread "main" ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
java.lang.NullPointerException
at ClassLoaderTest.main(ClassLoaderTest.java:13)
又是一个空指针异常,这表明ExtClassLoader也没有父加载器,先抛出结论ExtClassLoader的父加载器正是Bootstrap ClassLoader。
Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。然后呢,我们前面已经分析了,JVM初始化sun.misc.Launcher并创建Extension ClassLoader和AppClassLoader实例,并将ExtClassLoader设置为AppClassLoader的父加载器。(具体如何设置请参考ClassLoader Launcher的源码,此处不叙述)
关于类的加载过程
一个类请求加载的时候,首先查找他的类加载器有没有加载,如果没有加载,则委托给父类类加载器查找其缓存是否加载,一直如此递归到Bootstrap ClassLoader
。如果Bootstrap ClassLoader
也没有加载(到缓存),那么他就会去自己指定的路径下面去寻找,也就是sun.mic.boot.class
里面找。找到就返回,没有找到,让子类加载器去找。也就是让ExtClassLoader
在自己的路径下java.ext.dirs
下去找,查找成功则返回,否则再让子类加载器去找,即AppClassLoader
去自己指定路径下面java.class.path
去找,找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。
我们可以发现委托是从下向上,然后具体查找过程却是自上至下。
重要方法
- LoadClass()
protected Class<?> loadClass(String name,
boolean resolve)
throws ClassNotFoundException
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检测类是否已经加载在缓存中
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加载器不为空则调用父加载器的loadClass,此处即委托中向上递归的体现
c = parent.loadClass(name, false);
} else {
//父加载器为空则调用Bootstrap Classloader,解释了ExtClassLoader的父类为null指针的原因
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//父加载器的缓存中没有找到,则调用findclass
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//调用resolveClass()
resolveClass(c);
}
return c;
}
}
总结下
上面是方法原型,一般实现这个方法的步骤是
1. 执行findLoadedClass(String)去检测这个class是不是已经加载过了。
2. 执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
3. 如果向上委托父加载器没有加载成功,则通过findClass(String)查找。
自定义ClassLoader
此处只做简要介绍具体请看关于ClassLoeder
步骤
1.编写一个类继承自ClassLoader抽象类。
2.复写它的findClass()方法。
3.在findClass()方法中调用defineClass()。注意
一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。