@Auther: http://www.jianshu.com/u/a21455ce3dba
类的生命周期
一个类从 class 文件到一个可以被程序利用的对象需要经历一系列过程:
![](https://img.haomeiwen.com/i673200/4af3ef5471c3cad3.png)
- 要对JVM有大概理解,推荐一下这位哥们的JVM系列文章
http://blog.csdn.net/column/details/java-vm.html
不过据我做知,他不过是将《深入理解JAVA虚拟机》——周志明的内容直接简略搬到了博客上而已,不过对于大概理解JVM是挺不错的。
而《深入理解JAVA虚拟机》很多讲解其实是在官方的 javadoc 里面有了的,作者做了比较好的翻译加上了自己的理解。
类的加载阶段
其中 加载
需要完成一下三件事情
- 通过一个类的全限定名来获取此类的二进制字节流
- 将这个字节流锁代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的入口
其中,java虚拟机规范没有详细规定通过一个类的全限定名来获取此类的二进制字节流
这点,所以可以从不同的地方加载类的二进制流,例如:
- 各种zip压缩包的包,war,jar,ear等等
- 从 jsp 文件加载 Class 类
- 运行时生成的类的二进制字节:主要是动态代理技术
数组不是通过类加载器加载的,是JVM直接创建的;但是数组包含的类是类加载器加载的
另外,JVM规范没有说明 在内存中生成一个代表这个类的 Class 对象
中的内存指的是哪里,但是在我们常用的 HotSpot 虚拟机来说,是指方法区(永久代)
类加载器
类加载器具有热部署的特性,是 java 流行的重要原因之一。 据说当初是为了使得在浏览器能运行 applet 而搞出来的。(当年javascript 还无法提供非常便捷的交互)
有了类加载器以后,本地不需要提前下载好相关的 class 文件,而是通过网络连接获取字节流,通过类加载器进行加载类,在没有丰富的交互手段的互联网年代,这样的程序岂不美滋滋?
类的唯一性
在JVM里面,一个类是通过类加载器和类本身一同确定的。也就是说,即使加载的类都来自同一个 class 文件,只要加载这个class文件的类加载器不一样,那这两个类就不是同一个类,则Class类 equals() 返回 false。
package com.jvm.classloading;
import java.io.IOException;
import java.io.InputStream;
/** * 类加载器在类相等判断中的影响 * * instanceof关键字 * */
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
// 自定义类加载器
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(fileName);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
};
// 使用ClassLoaderTest的类加载器加载本类
Object obj1 = ClassLoaderTest.class.getClassLoader().loadClass("com.jvm.classloading.ClassLoaderTest").newInstance();
System.out.println(obj1.getClass());
System.out.println(obj1 instanceof com.jvm.classloading.ClassLoaderTest);
// 使用自定义类加载器加载本类
Object obj2 = myLoader.loadClass("com.jvm.classloading.ClassLoaderTest").newInstance();
System.out.println(obj2.getClass());
System.out.println(obj2 instanceof com.jvm.classloading.ClassLoaderTest);
}
}
输出结果:
class com.jvm.classloading.ClassLoaderTest
true
class com.jvm.classloading.ClassLoaderTest
false
双亲委派模型
-
Bootstrap ClassLoader 启动类加载器
负责加载<JAVA_HOME>\lib 目录,或者 -XBootclasspath 指定目录下的 jar 包对应的类 -
Extension ClassLoader 扩展类加载器
负责加载<JAVA_HOME>\lib\ext 目录,或者 java.ext.dirs系统变量指定目录下的 jar 包对应的类 -
Application ClassLoader 应用程序类加载器
负责加载用户路径 classpath上指定的类,一般我们写程序默认用的这个类加载器
![](https://img.haomeiwen.com/i673200/9d009515a6e3030b.png)
什么是委派?
双亲委派模型的工作流程是这样的:如果一个类加载器接到了类加载的请求,他不会首先自己进行加载,而是委派(委托)给父加载器加载,父加载器就委派自己的父加载器,一直到启动类加载器为止。如果父加载器无法加载(自己的搜索目录下没有该类),那么子加载器就会自己加载。
这样一个委派机制就形成了一个优先级的机制,所以即使你自己实现了一个 java.lang.Object 类,也不能代替启动类加载器加载的 Object 类,从而有效保证 JVM 安全性。
启动类加载器是用C++实现的,因为JVM启动的时候需要加载很多类,这个时候由启动类加载器负责加载(包括加载扩展类加载器和应用程序类加载器),所以此时只能用C++实现了。
JAVA 应用层面无法引用启动类加载器,引用了也会返回 null,如下:
public class ClassLoaderTest {
public static void main(String[] args) {
ClassLoaderTest myClass = new ClassLoaderTest();
ClassLoader classLoader_app = myClass.getClass().getClassLoader();
ClassLoader classLoader_ext = classLoader_app.getParent();
ClassLoader classLoader_boot = classLoader_ext.getParent();
System.out.println(classLoader_app.toString());
System.out.println(classLoader_ext.toString());
System.out.println(classLoader_boot);
}
}
sun.misc.Launcher $ AppClassLoader @14dad5dc
sun.misc.Launcher $ ExtClassLoader @1b6d3586
null
$ 代表内部类
什么是双亲?
双亲委派模型不是强制的,但是是JVM内部极力推行的,因为官方违背这个模型的事情时有发生,例如 JDNI ,还有有点古老的 OSGI,这里就不说了。
那么所谓双亲指的是啥?
我认为指的是 启动类加载器 和 扩展类加载器对应的加载目录,因为上面已经说明了
Application ClassLoader 应用程序类加载器
负责加载用户路径 classpath上指定的类,一般我们写程序默认用的这个类加载器
而启动类加载器和扩展类加载器目录下加载的基本都是 jre 包内的库,可以认为是比较稳定的库,所有用户类路径都会经过 启动类加载器和扩展类加载器 指定的加载路径才会加载用户目录下定义的类(classpath下的类)
而对于自定义的类加载器,实际委派的父加载器是 Application ClassLoader。详情请看我的另一篇文章:http://www.jianshu.com/p/1307196d70f5
!!
这里的父子关系是通过组合(composition)来体现的,而不是通过继承(Inheritance)。
怎么理解?
直接上代码啊。以sun公司的 HotSpot JVM为例。
扩展类加载器和应用程序类加载器其实都是 sun.misc.Launcher 类的内部类
![](https://img.haomeiwen.com/i673200/2e81a5417a75f08a.png)
继承关系其实是这样的
ClassLoader <- SecureClassLoader <- URLClassLoader <- AppClassLoader/ExtClassLoader
再看下 ClassLoader 就真相大白了,类加载器们是通过 parent 字段来指示父加载器的,也就是组合的方法,不是继承的方式。
![](https://img.haomeiwen.com/i673200/98563a9147d3cfa8.png)
所谓双亲委派其实就是
按照优先级,加载特定目录下的类
的意思而已。
小结
加载
需要完成一下三件事情
- 通过一个类的全限定名来获取此类的二进制字节流
- 将这个字节流锁代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的入口
双亲委派模型
-
Bootstrap ClassLoader 启动类加载器
负责加载<JAVA_HOME>\lib 目录,或者 -XBootclasspath 指定目录下的 jar 包对应的类 -
Extension ClassLoader 扩展类加载器
负责加载<JAVA_HOME>\lib\ext 目录,或者 java.ext.dirs系统变量指定目录下的 jar 包对应的类 -
Application ClassLoader 应用程序类加载器
负责加载用户路径 classpath上指定的类,一般我们写程序默认用的这个类加载器
双亲委派模型的工作流程是这样的:如果一个类加载器接到了类加载的请求,他不会首先自己进行加载,而是委派(委托)给父加载器加载,父加载器就委派自己的父加载器,一直到启动类加载器为止。如果父加载器无法加载(自己的搜索目录下没有该类),那么子加载器就会自己加载。
这里的父子关系是通过组合(composition)来体现的,而不是通过继承(Inheritance)。
@Auther: http://www.jianshu.com/u/a21455ce3dba
参考
《深入理解java虚拟机》——周志明
http://blog.csdn.net/ns_code/article/details/17881581
网友评论