JVM类加载器机制与类加载过程

jvm虚拟机的种类:

Hotspot(Oracle)(基本上都是在说这个)
J9, JikesRVM(IBM)
Zulu, Zing (Azul)

Launcher是一直用于启动JVM进程的启动器,有两种:

一种windows平台下运行时会保留在控制台
一种用于执行Java的GUI程序,不会显示任何程序的输出信息

Launcher只是一个封装了虚拟机的执行外壳,由它负责装载JRE环境和windows平台下的jvm.dll动态链接库

补充:

HotSpot虚拟机对象的创建、内存布局、访问定位

PerformanceCounter 表示 Windows NT 性能计数器组件

一:跨平台:

Java语言之所以说它是跨平台的、因为Java语言的运行环境是在Java虚拟机中。

 Java虚拟机对各个平台而言,实质上是各个平台上的一个可执行程序。java虚拟机对于windows而言,就是一个java.exe进程而已。

二:Java虚拟机启动、加载类过程分析

package org.luanlouis.jvm.load;
import sun.security.pkcs11.P11Util;
public class Main{
public static void main(String[] args) {
System.out.println("Hello,World!");
ClassLoader loader = P11Util.class.getClassLoader();
System.out.println(loader);
}
}

执行java    org.luanlouis.jvm.load.Main

windows开始运行{JRE_HOME}/bin/java.exe程序,java.exe 程序将完成以下步骤:

. 给jvm分配内存空间: 根据JVM内存配置要求,为JVM申请特定大小的内存空间;
. 类加载器创建和加载系统类到方法区:创建一个引导类加载器实例,初步加载系统类到内存方法区区域中;
. 启动器创建和获取类加载器:创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader;
. 类加载器加载自定义类:使用上述获取的ClassLoader实例加载我们定义的 org.luanlouis.jvm.load.Main类;
. jvm执行main方法:加载完成时候JVM会执行Main类的main方法入口,执行Main类的main方法;
. 结束,java程序运行结束,JVM销毁。

1,根据JVM内存配置要求,为JVM申请特定大小的内存空间

2,创建一个引导类加载器实例,初步加载系统类到内存方法区区域中;

JVM申请好内存空间后,JVM会创建一个引导类加载器(Bootstrap Classloader)实例,加载系统类:

引导类加载器是使用C++语言实现的,负责加载JVM虚拟机运行时所需的基本系统级别的类,如java.lang.String, java.lang.Object等等。

引导类加载器(Bootstrap Classloader)会读取 {JRE_HOME}/lib 下的jar包和配置,然后将这些系统类加载到方法区内。

也可以使用参数 -Xbootclasspath 或 系统变量sun.boot.class.path来指定的目录来加载类。

一般而言,{JRE_HOME}/lib下存放着JVM正常工作所需要的系统类,如下表所示:

文件名                 描述
rt.jar 运行环境包,rt即runtime,J2SE 的类定义都在这个包内
charsets.jar 字符集支持包
jce.jar 是一组包,它们提供用于加密、密钥生成和协商以及 Message Authentication Code(MAC)算法的框架和实现
jsse.jar 安全套接字拓展包Java(TM) Secure Socket Extension
classlist 该文件内表示是引导类加载器应该加载的类的清单
net.properties JVM 网络配置信息

引导类加载器(Bootstrap ClassLoader) 加载系统类后,JVM内存会呈现如下格局:

1,引导类加载器将类信息加载到方法区中,以特定方式组织,对于某一个特定的类而言,
在方法区中它应该有 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用,对应class实例的引用等信息。
2,类加载器的引用,由于这些类是由引导类加载器(Bootstrap Classloader)进行加载的,而引导类加载器是有C++语言实现的,所以是无法访问的,故而该引用为NULL
3,对应class实例的引用, 类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。

补充:

1,也就是说程序员使用的类的数据都是(通过类加载器创建的每个类的class)Class实例来获取的。(方法区和堆的交互是加载类完成的)

2,当我们在代码中尝试获取系统类如java.lang.Object的类加载器时,你会始终得到NULL:

System.out.println(String.class.getClassLoader());//null
System.out.println(Object.class.getClassLoader());//null
System.out.println(Math.class.getClassLoader());//null
System.out.println(System.class.getClassLoader());//null

3,创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader

上述步骤完成,JVM基本运行环境就准备就绪了。要让JVM工作起来:运行我们定义的程序 org.luanlouis,jvm.load.Main。

需要JVM虚拟机调用已经加载在方法区的类sun.misc.Launcher 的静态方法getLauncher(),  获取sun.misc.Launcher 实例:

sun.misc.Launcher launcher = sun.misc.Launcher.getLauncher(); //获取Java启动器
ClassLoader classLoader = launcher.getClassLoader(); //获取类加载器ClassLoader用来加载class到内存来

启动器做了什么:

创建扩展类加载器和应用类加载器,并将应用加载器指定给当前线程作为线程上下文加载器。
 public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
} try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//将AppClassLoader设置成当前线程的上下文加载器
Thread.currentThread().setContextClassLoader(this.loader);
//.......
}

sun.misc.Launcher 使用了单例模式设计,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例。

在Launcher的内部,其定义了两个类加载器(ClassLoader),分别是sun.misc.Launcher.ExtClassLoader和sun.misc.Launcher.AppClassLoader,

这两个类加载器分别被称为拓展类加载器(Extension ClassLoader) 和 应用类加载器(Application ClassLoader).如下图所示:

图上的指向引导类加载器的虚线表示:

除了引导类加载器(Bootstrap Class Loader )外的所有类加载器,都有一个能力,就是判断某一个类是否被引导类加载器加载过,
如果加载过,可以直接返回对应的Class<T> instance,如果没有,则返回null.

从Launcher源码中可以看到

public class Launcher {
private static URLStreamHandlerFactory factory = new Factory();
private static Launcher launcher = new Launcher(); public static Launcher getLauncher() {
return launcher;
} private ClassLoader loader; //ClassLoader.getSystemClassLoader会调用此方法
public ClassLoader getClassLoader() {
return loader;
} public Launcher() {
// 1. 创建ExtClassLoader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader");
} // 2. 用ExtClassLoader作为parent去创建AppClassLoader
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader");
} // 3. 设置AppClassLoader为ContextClassLoader
Thread.currentThread().setContextClassLoader(loader);
//...
} static class ExtClassLoader extends URLClassLoader {
private File[] dirs; public static ExtClassLoader getExtClassLoader() throws IOException
{
final File[] dirs = getExtDirs();
return new ExtClassLoader(dirs);
} public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
this.dirs = dirs;
} private static File[] getExtDirs() {
String s = System.getProperty("java.ext.dirs");
File[] dirs;
//...
return dirs;
}
} /**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader { public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[] : getClassPath(s); URL[] urls = (s == null) ? new URL[] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
} AppClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent, factory);
} /**
* Override loadClass so we can checkPackageAccess.
* 这个方法似乎没什么必要,因为super.loadClass(name, resolve)时也会checkPackageAccess
*/
public synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
int i = name.lastIndexOf('.');
if (i != -) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
//
sm.checkPackageAccess(name.substring(, i));
}
}
return (super.loadClass(name, resolve));
} }
}

launcher.getClassLoader() 方法将会返回 AppClassLoader 实例(AppClassLoader将ExtClassLoader作为自己的父加载器)。
当AppClassLoader加载类时:

1,判断自己是否已经加载,
2,会首先尝试让父加载器ExtClassLoader进行加载,如果父加载器ExtClassLoader加载成功,则AppClassLoader直接返回父加载器ExtClassLoader加载的结果;
3,如果父加载器ExtClassLoader加载失败,AppClassLoader则会判断该类是否是引导的系统类(即是否是通过Bootstrap类加载器加载,这会调用Native方法进行查找);
4,若要加载的类不是系统引导类,那么ClassLoader将会尝试自己加载,加载失败将会抛出“ClassNotFoundException”。

再这里就说点一个重要的概念:双亲委派模型(parent-delegation model):

对于某个特定的类加载器而言,应该为其指定一个父类加载器,当用其进行加载类的时候:

1. 委托父类加载器帮忙加载;
2. 父类加载器加载不了,则查询引导类加载器有没有加载过该类;
3. 如果引导类加载器没有加载过该类,则当前的类加载器应该自己加载该类;
4. 若加载成功,返回 对应的Class<T> 对象;若失败,抛出异常“ClassNotFoundException”。

请注意:

双亲委派模型中的"双亲"并不是指它有两个父类加载器的意思,一个类加载器只应该有一个父加载器。

上面的步骤中,有两个角色:

1. 父类加载器(parent classloader):它可以替子加载器尝试加载类
2. 引导类加载器(bootstrap classloader): 子类加载器只能判断某个类是否被引导类加载器加载过,而不能委托它加载某个类;

4. 使用类加载器ClassLoader加载Main类

通过 launcher.getClassLoader()方法返回AppClassLoader实例,接着就是AppClassLoader加载 org.luanlouis.jvm.load.Main类

ClassLoader classloader = launcher.getClassLoader();//取得AppClassLoader类
classLoader.loadClass("org.luanlouis.jvm.load.Main");//加载自定义类

上述定义的org.luanlouis.jvm.load.Main类被编译成org.luanlouis.jvm.load.Main class二进制文件,

这个class文件中有一个叫常量池(Constant Pool)的结构体来存储该class的常亮信息。

常量池中有CONSTANT_CLASS_INFO类型的常量,表示该class中声明了要用到那些类:

当AppClassLoader要加载 org.luanlouis.jvm.load.Main类时,会去查看该类的定义,
发现它内部声明使用了其它的类:
sun.security.pkcs11.P11Util、
java.lang.Object、
java.lang.System、
java.io.PrintStream、
java.lang.Class;
org.luanlouis.jvm.load.Main类要想正常工作,首先要能够保证这些其内部声明的类加载成功。
所以AppClassLoader要先将这些类加载到内存中。
(注:为了理解方便,这里没有考虑懒加载的情况,事实上的JVM加载类过程比这复杂的多)

加载顺序:

1. 加载java.lang.Object、java.lang.System、java.io.PrintStream、java,lang.Class
AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;
而ExtClassLoader发现不是其加载范围,其返回null;
AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过,
结果表明这些类已经被BootstrapClassLoader加载过,则无需重复加载,直接返回对应的Class<T>实例;
2. 加载sun.security.pkcs11.P11Util
此在{JRE_HOME}/lib/ext/sunpkcs11.jar包内,属于ExtClassLoader负责加载的范畴。
AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;
而ExtClassLoader发现其正好属于加载范围,故ExtClassLoader负责将其加载到内存中。
ExtClassLoader在加载sun.security.pkcs11.P11Util时也分析这个类内都使用了哪些类,
并将这些类先加载内存后,才开始加载sun.security.pkcs11.P11Util,
加载成功后直接返回对应的Class<sun.security.pkcs11.P11Util>实例;
3. 加载org.luanlouis.jvm.load.Main
AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现不是其加载范围,其返回null;
AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过。
而结果表明BootstrapClassLoader 没有加载过它,这时候AppClassLoader只能自己动手负责将其加载到内存中,
然后返回对应的Class<org.luanlouis.jvm.load.Main>实例引用;

以上三步骤都成功,才表示classLoader.loadClass("org.luanlouis.jvm.load.Main")完成,上述操作完成后,JVM内存方法区的格局会如下所示:

JVM方法区的类信息区是按照类加载器进行划分的,每个类加载器会维护自己加载类信息;
某个类加载器在加载相应的类时,会相应地在JVM内存堆(Heap)中创建一个对应的Class<T>,用来表示访问该类信息的入口

5. 使用Main类的main方法作为程序入口运行程序

6. 方法执行完毕,JVM销毁,释放内存

三:类加载器有哪些?其组织结构是怎样的?

JVM自身定义了三个类加载器:

引导类加载器(Bootstrap Class Loader)、拓展类加载器(Extension Class Loader )、应用加载器(Application Class Loader又叫系统类加载器)。

当然,我们有时候也会自己定义一些类加载器来满足自身的需要。

1,引导类加载器(Bootstrap Class Loader): 该类加载器使JVM使用C/C++底层代码实现的加载器,用以加载JVM运行时所需要的系统类,
这些系统类在{JRE_HOME}/lib目录下(或-Xbootclasspath选项指定)的jar包加载到内存中。
由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用。
但是,我们可以查询某个类是否被引导类加载器加载过。 我们经常使用的系统类如:java.lang.String,java.lang.Object,java.lang*.......
这些都被放在 {JRE_HOME}/lib/rt.jar包内,
当JVM系统启动的时候,引导类加载器会将其加载到 JVM内存的方法区中。
2,拓展类加载器(Extension Class Loader): 该加载器是用于加载 java 的拓展类 ,
拓展类一般会放在 {JRE_HOME}/lib/ext/ 目录下(或-Djava.ext.dir指定位置)的类库加载到内存中,用来提供除了系统类之外的额外功能。
拓展类加载器是是整个JVM加载器的Java代码可以访问到的类加载器的最顶端(即是超级父加载器,拓展类加载器是没有父类加载器的)。
3,应用类加载器(Applocatoin Class Loader): 该类加载器是用于加载用户代码,是用户代码的入口。
它负责将系统类路径java -classpath,即环境变量classpath(或-Djava.class.path指定的目录)的类库加载到内存中 我经常执行指令 java xxx.x.x.XClass , 实际上,JVM就是使用的AppClassLoader加载 xxx.x.x.XClass 类的。 应用类加载器将拓展类加载器当成自己的父类加载器,当其尝试加载类的时候,首先尝试让其父加载器(拓展类加载器加载);
如果拓展类加载器加载成功,则直接返回加载结果Class<T> instance,
加载失败,则会询问是否引导类加载器已经加载了该类;
只有没有加载的时候,应用类加载器才会尝试自己加载。 4,用户自定义类加载器(Customized Class Loader):用户可以自己定义类加载器来加载类。所有的类加载器都要继承java.lang.ClassLoader类。 由于Class<T>是整个用户代码的入口,在Java虚拟机规范中,称其为 初始类(Initial Class).

四:双亲加载模型的逻辑和底层代码实现是怎样的?

通过JDK源码看java.lang.ClassLoader的核心方法 loadClass()的实现:

//提供class类的二进制名称表示,加载对应class,加载成功,则返回表示该类对应的Class<T> instance 实例
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
} protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查是否已经被当前的类加载器记载过了,如果已经被加载,直接返回对应的Class<T>实例
Class<?> c = findLoadedClass(name);
//初次加载
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果有父类加载器,则先让父类加载器加载
c = parent.loadClass(name, false);
} else {
// 没有父加载器,则查看是否已经被引导类加载器加载,有则直接返回
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();
// 自己尝试加载
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(c);
}
return c;
}
}

完全的ClassLoader源码:

public abstract class ClassLoader {  

    // 父ClassLoader
private ClassLoader parent; // 被此classLoader加载过的Class对象
private Vector classes = new Vector(); // The packages defined in this class loader. Each package name is mapped
// to its corresponding Package object.
private HashMap packages = new HashMap(); // 由虚拟机调用
void addClass(Class c) {
classes.addElement(c);
} // The packages defined in this class loader. Each package name is mapped
// to its corresponding Package object.
private final HashMap<String, Package> packages = new HashMap<String, Package>(); // 指明parent
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
} // 不指名parent时使用SystemClassLoader
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
} // 默认resolve=false
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
} protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 1 检查此class是否被此classloader加载过,
// 最终是有native方法返回,native方法会使用到classes集合
Class c = findLoadedClass(name);
// 1.1 未被加载
if (c == null) {
try {
// 1.1.1 此classloader有parent,委托parent去load
if (parent != null) {
c = parent.loadClass(name, false);
} else {// 1.1.2 此classloader无parent = 启动类装载器去加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
// 如果没有找到class,自己去加载试试
if (c == null) {
c = findClass(name);
}
}
// 找到的Class对象是否需要连接操作
if (resolve) {
resolveClass(c);
}
// 1.2 被加载过,直接返回
return c;
} protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
} // 如果name里包含/,或者虚拟机不支持class数组你使用了数组的时候返回false,其它情况返回true,包括空的name
// eg com.jyz.component.core.collection.Tuple return true
private boolean checkName(String name) {
if ((name == null) || (name.length() == ))
return true;
if ((name.indexOf('/') != -)
|| (!VM.allowArraySyntax() && (name.charAt() == '[')))
return false;
return true;
} // 检查package是否可访问
private void checkPackageAccess(Class cls, ProtectionDomain pd) {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// ...
}
domains.add(pd);
} // 自定义classloader时重写此方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
} // 将byte数组转换成一个Class对象,最终有native方法实现
// 同一个byte数组,被不同的classloader加载,产生两个不同的Class对象
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError {
return defineClass(name, b, off, len, null);
} /*
* Determine protection domain, and check that: - not define java.* class, -
* signer of this class matches signers for the rest of the classes in
* package.
*/
//native的defineClass时会调用此方法检查name是否合法
//首先checkName,然后还需要!name.startsWith("java.")
//所以我们定义了java.mypackage包,都将异常
//java.lang.SecurityException: Prohibited package name: java.mypackage
private ProtectionDomain preDefineClass(String name,
ProtectionDomain protectionDomain) {
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException("Prohibited package name: "
+ name.substring(, name.lastIndexOf('.')));
}
//...
} // protected的resolveClass方法,可以在自定义的classloader调用
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
} //获得appClassLoader,实际调用Launcher完成
public static ClassLoader getSystemClassLoader() {
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
return l.getClassLoader();
} }

五:类加载器与Class<T>  实例的关系

六:线程上下文加载器

我们平时并没有使用加载器,是因为jvm帮我调用了。(会将加载器关联到线程上)

Java 任何一段代码的执行,都有对应的线程上下文。如果我们在代码中,想看当前是哪一个线程在执行当前代码,我们经常是使用如下方法:

Thread  thread = Thread.currentThread();//返回对当当前运行线程的引用  

相应地,我们可以为当前的线程指定类加载器。

在上述的例子中, 当执行   java    org.luanlouis.jvm.load.Main  的时候,JVM会创建一个Main线程,而创建应用类加载器AppClassLoader的时候,

会将AppClassLoader  设置成Main线程的上下文类加载器:

 public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
} try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//将AppClassLoader设置成当前线程的上下文加载器
Thread.currentThread().setContextClassLoader(this.loader);
//.......
}

线程上下文类加载器是从线程的角度来看待类的加载,为每一个线程绑定一个类加载器,可以将类的加载从单纯的 双亲加载模型解放出来,进而实现特定的加载需求。

线程上下文加载器的意义何在???

七:扩展加载器和应用加载器的继承结构:

特别注意的是:虽然都是URLClassLoader的子类,但是都是Launcher类的静态内部类,jdk的api是没有的。

都是sun/misc/Launcher.class的静态成员内部类,声明如下:
static class AppClassLoader extends URLClassLoader
static class ExtClassLoader extends URLClassLoader

八:类加载器加载路径的测试

Launcher类的部分源码(比较老的一个版本),主要是引导类加载器加载的路径

private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path"); public static Launcher getLauncher() {
return launcher;
} private ClassLoader loader; public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader");
} // Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader");
} // Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader); // Finally, install a security manager if requested
String s = System.getProperty("java.security.manager");
......
} /*
* Returns the class loader used to launch the main application.
*/
public ClassLoader getClassLoader() {
return loader;
}

测试:

public static void main(String[] args) throws Exception {  

        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader); System.out.println();
System.out.println("bootstrapLoader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = ; i < urls.length; i++) {
System.out.println(urls[i]);
} System.out.println();
System.out.println("extClassloader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs")); System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path")); }
}

eclipse下测试:

dos下测试:

总结:

可以发现应用类加载器加载就是环境变量classpath设置的。
(至于eclipse中不同,是因为eclipse临时修改了自己的classpath。)

九:测试加载器加载类的路径是被限制的:

默认引导类加载器只会加载{jre.lib}下的,并且还做了限制只会加载系统类。
默认扩展类加载器只会加载{jre.ext.lib}下的。
默认系统类加载器(应用类加载器)只会加载classpath下的。

1,在同一目录中建立2个类:

需要加载的类:
public class TestBean {
public TestBean() { }
}
测试类:
public class ClassLoaderTest { public static void main(String[] args) {
try {
//调用加载当前类的类加载器(这里即为系统类加载器)加载TestBean
Class typeLoaded = Class.forName("TestBean");
//查看被加载的TestBean类型是被那个类加载器加载的
System.out.println(typeLoaded.getClassLoader());
} catch (Exception e) {
e.printStackTrace();
} } }

输出:(因为eclipse默认classpath是当前工程下)补充:eclipse的classpath(build path)和classpaht几种设置的方式

sun.misc.Launcher$AppClassLoader@73d16e93  

2,将TestBean.class复制打包进test.jar剪贴到<JRE_Home>/lib/ext目录下(现在工程目录下和JRE扩展目录下都有待加载类型的class文件)。

sun.misc.Launcher$ExtClassLoader@15db9742  

对比测试一和测试二,可以验证前面说的双亲委派机制:

系统类加载器在接到加载TestBean类型的请求时,首先将请求委派给父类加载器(标准扩展类加载器),标准扩展类加载器抢先完成了加载请求。

3,将test.jar拷贝一份到<JRE_Home>/lib下,运行测试代码:

sun.misc.Launcher$ExtClassLoader@15db9742  

放置到<JRE_Home>/lib目录下的TestBean对应的class字节码并没有被加载,这其实和前面讲的双亲委派机制并不矛盾。

虚拟机出于安全等因素考虑,不会加载<JRE_Home>/lib存在的陌生类,不会加载非JDK自身的类。

如果将工程和jre/lib/ext下的TestBean移除,只保留jre/lib下的,会发现findBootstrapClass0()会抛出异常,找不到类。

jvm(1)类加载(一)(加载过程,双亲加载)的更多相关文章

  1. jvm的类加载器,类装载过程

    混沌初开,在一片名为jvm的世界中,到处都是一片虚无,直到一个名为BootstrapClassLoader的巨人劈开了世界,据说它是由名叫C++的女神所造,它从一个叫做jre/lib的宝袋中拿出一把开 ...

  2. JVM(二)类加载的时机及其过程

    类从被加载到虚拟机内存中开始,到卸载出内存为止,它的的整个生命周期包括: 加载(Loading),验证(Verification),准备(Preparation),解析(Resolution),初始化 ...

  3. java web.xml被文件加载过程及加载顺序小结

    web.xml加载过程(步骤): 1.启动WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml.读两个节点: <listener></listener> ...

  4. <JVM中篇:字节码与类的加载篇>03-类的加载过程(类的生命周期)详解

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  5. Java JVM——2.类加载器子系统

    概述 类加载器子系统在Java JVM中的位置 类加载器子系统的具体实现 类加载器子系统的作用 ① 负责从文件系统或者网络中加载.class文件,Class 文件在文件开头有特定的文件标识. ② Cl ...

  6. JVM学习——类加载机制(学习过程)

    JVM--类加载机制 2020年02月07日14:49:19-开始学习JVM(Class Loader) 类加载机制 类加载器深入解析与阶段分解 在Java代码中,类型的加载.连接与初始化过程中都是在 ...

  7. JVM之类加载器、加载过程及双亲委派机制

    JVM 的生命周期 虚拟机的启动 Java 虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实 ...

  8. JVM加载类的过程,双亲委派机制中的方法

    JVM加载类的过程: 1)JVM中类的整个生命周期: 加载=>验证=>准备=>解析=>初始化=>使用=>卸载  1.1.加载 类的加载阶段,主要是获取定义此类的二进 ...

  9. (转)JVM类生命周期概述:加载时机与加载过程

    原文地址: http://blog.csdn.net/justloveyou_/article/details/72466105 JVM类加载机制主要包括两个问题:类加载的时机与步骤 和 类加载的方式 ...

随机推荐

  1. android 弹出软键盘将底部视图顶起问题

    今天要做一个搜索功能,搜索界面采用AutoCompleteTextView做搜索条,然后下面用listview来显示搜索结果,而我的主界面是在底 部用tab做了一个主界面导航,其中有一个搜索按钮,因为 ...

  2. Devexpress VCL Build v2014 vol 14.2.1 beta发布

    已经快到2015 年了. 14.2.1 beta 才出来了. 还好,有一些新东西. 官网地址 VCL Gauge Control Designed to clearly convey informat ...

  3. 使用eclipse创建android项目的时候为什么会生成两个项目

    使用eclipse创建android项目的时候为什么会生成两个项目 问题描述: 使用eclipse创建一个Android项目时,发现project列表中会多创建出一个appcompat_v7项目,再创 ...

  4. 使用reactjs做一个CRUD功能

    第一步:引入reactjs所依赖的js文件,本案例使用的是bootstrap前端框架,所以引入了相应的js和css文件 第二步:body里面添加两个div 第三步:开始编写reactjs脚本 < ...

  5. 2018.06.28 BZOJ1014 [JSOI2008]火星人prefix(非旋treap+hash)

    [JSOI2008]火星人prefix Time Limit: 10 Sec Memory Limit: 162 MB Submit: 8951 Solved: 2860 Description 火星 ...

  6. 2018.09.17 bzoj1260: [CQOI2007]涂色paint(区间dp)

    传送门 区间dp简单题啊. 很显然用f[l][r]f[l][r]f[l][r]表示把区间[l,r][l,r][l,r]按要求染好的代价. 这样可以O(n)O(n)O(n)枚举断点转移了啊. 显然如果断 ...

  7. arduino 驱动电调

    #include <TimerOne.h> #define PPMPIN 7 ; //0-9 ; void setup() { // put your setup code here, t ...

  8. KindEditor解决上传视频不能在手机端显示的问题

    KindEditor自带的上传视频生成的HTML代码为<embed>,在手机端并不支持.于是可以自己在控件里增加生成video标签相关代码. 参考https://www.jianshu.c ...

  9. UVa 1572 Self-Assembly (构造+拓扑排序。。。。。)

    题意:给定n个带标号的正方形,标号要么是一个大写字母加一个+或-,要么是00, 当且仅当大写字母相同并且符号相反时可以连接,问你给定的能不能拼成一个无限大的的东西. 析:说实话,真心没有看出来是拓扑排 ...

  10. 20155305乔磊2016-2017-2《Java程序设计》第七周学习总结

    教材学习内容总结 第十二章 Lambda 12.1 认识Lambda语法 - Lambda 教材的引入循序渐近.深入浅出 如果使用JDK8的话,可以使用Lambda特性去除重复的信息,例: Compa ...