首先我们知道Java源码经过 javac 编译为 class文件 再经过加载器加载到 内存中(JVM运行时数据区)
经过加载的文件包括 Java Source文件 Java Class文件(二进制文件)
我们还要理解清楚Class 与Object 的区别:
Class 指的是类,在jdk1.8以后称为元数据(metadata),其包含有类的描述信息。
Object 指的是对象/实例
类加载器: 是指把Java类文件加载到内存中 (其中包含有一个重要机制/模型---双亲委派)
为了更好理解类加载的过程和相关机制,我们来根据jdk源码了解下类加载器ClassLoader和他最主要的方法loadClass()
public abstract class ClassLoader
ClassLoader是一个抽象类,在源码中sun公司是这么解释这个类的(截取一小段)
* A class loader is an object that is responsible for loading classes. The
* class <tt>ClassLoader</tt> is an abstract class. Given the <a
* href="#name">binary name</a> of a class, a class loader should attempt to
* locate or generate data that constitutes a definition for the class. A
* typical strategy is to transform the name into a file name and then read a
* "class file" of that name from a file system.
大致意思是说
class loader 是一个负责加载 classes 的对象,ClassLoader类是一个抽象类,需要给出类的二进制名称。class loader尝试定位或者产生一个 class 的数据,一个典型的策略是把二进制名字转换成文件名然后在文件系统中找到该文件。
在ClassLoader中,存在一个私有常量,指代父类加载器
private final ClassLoader parent;
源码中是这么解释这个变量的
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
大致意思是说:
这是委托的父类加载器,且VM对该字段移量进行了硬编码,所有的新字段必须在 之后 添加
接下来我们看loadClass()方法的实现
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
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;
}
}
源码中对这个方法是这么解释的:
使用指定的二进制名称来加载类,这个方法的默认实现按照以下顺序查找类:
使用 findLoadedClass(String) 的方法检查这个类是否被加载过
使用父类加载器调用loadClass(String)方法,如果父类加载器为null,类加载器装载虚拟机内置的加载器调用 findClass(String) 方法装载类
如果按照以上步骤成功找到对应的类,并且该方法接收的 resolve 参数的值为true,那么就调用 resolveClass(class) 方法来处理类
ClassLoader的子类最好覆盖 findClass(String) 而不是这个方法。
除非被重写,这个方法默认在整个装载过程中是同步的。(线程安全)
由源码可分析出一个类加载器的方法过程大致有以下步骤(这里主要是为了了解双亲委派机制,其它涉及具体底层方法略过)
1、同一时间只允许一个线程加载同一个类(通过全限定名)--可称为加锁的过程。而这个加锁的过程底部又涉及了其它一些操作,在这里不深究。
synchronized (getClassLoadingLock(name))
2、检查类是否已被加载过,如果存在就不再加载。
Class c = findLoadedClass(name);
3、当所要加载的类不存在时,才会执行加载的流程,而这里就涉及到了一个双亲委派机制
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
}
由执行代码可以知道,当父类加载器不为空时,就调用父类加载器的loadClass()方法对当前类进行加载,当父类加载器为空时,才调用虚拟机的加载器来加载类。简而言之就是父加载器能加载的绝不给子加载器加载。
4、当以上方法都没成功加载到类时,那么则调用自身的方法findClass(name)去加载。
c = findClass(name);
5、在最后我们已经得到了加载之后的类,那么根据 resolve 的值决定是否调用 resolveClass 方法。这个方法的作用是链接指定的类,用来给classloader链接一个类,如果这个类已经被链接过了,则做一个简单的返回。否则,按照 Java 规范中的 Execution 描述进行连接。
至此,ClassLoader
类以及loadClass
方法的源码我们已经分析完了。那么,我们来总结一下:
java中的类大致分为三种:
1、系统类 2、扩展类 3、由程序员自定义的类
类装载方式有两种:
1、隐式加载,程序在运行过程中当碰到通过new
等方式生成对象时,隐式调用类装载器加载对应的类到jvm
中。
2、显式装载, 通过class.forName()
等方法,显式加载需要的类
类加载的动态行体现:
一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现。
为什么需要先检查类是否加载过和采用双亲委派机制:
这里体现了一种安全机制,其保证了每个类只能被加载一次,保证了基类安全和java程序的安全运行。
java类加载器:
java中的类装载器实质上也是类,功能是把类载入jvm中,值得注意的是jvm的类装载器并不是一个,而是三个,层次结构如下:appclassloader--Extclassloader--BootstrapClassLoader(native方法)
为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型。
类加载器之间是如何协调工作的
前面说了,java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,即java是如何区分一个类该由哪个类加载器来完成呢。 在这里java采用了委托模型机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类”
类装载器ClassLoader
(一个抽象类)描述一下JVM
加载class
文件的原理机制:
类装载器就是寻找类或接口字节码文件进行解析并构造JVM内部对象表示的组件,在java
中类装载器把一个类装入JVM
,经过以下步骤:
1、装载:查找和导入Class文件
2、链接:其中解析步骤是可以选择的
(a)检查:检查载入的class文件数据的正确性
(b)准备:给类的静态变量分配存储空间
(c)解析:将符号引用转成直接引用
3、初始化:对静态变量,静态代码块执行初始化工作
类装载工作由ClassLoder
和其子类负责。JVM
在运行时会产生三个ClassLoader
:根装载器,ExtClassLoader
(扩展类装载器)和AppClassLoader
(应用类加载器)。
其中根装载器不是ClassLoader
的子类,由C++
编写,因此在java
中看不到他,负责装载JRE
的核心类库,如JRE
目录下的rt.jar,charsets.jar
等。
ExtClassLoader
是ClassLoder
的子类,负责装载JRE
扩展目录ext
下的jar
类包。
AppClassLoader
负责装载classpath
路径下的类包。
这三个类装载器存在父子层级关系,即根装载器是ExtClassLoader
的父装载器,ExtClassLoader
是AppClassLoader
的父装载器。默认情况下使用AppClassLoader
装载应用程序的类。
Java
装载类使用“全盘负责委托机制”。“全盘负责”是指当一个ClassLoder
装载一个类时,除非显示的使用另外一个ClassLoder
,该类所依赖及引用的类也由这个ClassLoder
载入;“委托机制”是指先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。这一点是从安全方面考虑的,试想如果一个人写了一个恶意的基础类(如java.lang.String
)并加载到JVM
将会引起严重的后果,但有了全盘负责制,java.lang.String
永远是由根装载器来装载,避免以上情况发生 除了JVM
默认的三个ClassLoder
以外,第三方可以编写自己的类装载器,以实现一些特殊的需求。类文件被装载解析后,在JVM
中都有一个对应的java.lang.Class
对象,提供了类结构信息的描述。数组,枚举及基本数据类型,甚至void
都拥有对应的Class
对象。Class
类没有public
的构造方法,Class
对象是在装载类时由JVM通过调用类装载器中的defineClass()
方法自动构造的。