ClassLoader
将Class加载到内存
结构
- BootstrapClassLoader:加载Java核心库(JAVA_HOME/jre/lib),唯一一个使用本地代码编写的加载器
- ExtensionClassLoader:加载扩展库(JAVA_HOME/jre/lib/ext和系统参数java.ext.dirs指定的目录),它的父加载器是null(因为BootstrapClassLoader是使用C++实现的,没有对应的java类)
- SystemClassLoader:加载应用类的类文件(Classpath下的类文件)
- UserDefineClassLoader:加载用户定义的类文件,可以从网络或者数据库中加载类文件,实现类文件加密解密,动态地创建符合应用特殊需要的定制化类等
双亲委派机制
加载器在接收到加载类的请求时,首先检查自己的缓存,确认类是否已被加载,如果没有加载,则将请求委托给父加载器,依次递归,如果父加载器完成加载,则成功返回,否则才自己去加载
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
// 检查类是否已被加载
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 委托父类去加载
c = parent.loadClass(name, false);
} else {
// parent为空,代表父类是BootstrapClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 父类没有加载,才自己去加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
意义:避免重复加载,避免安全因素(如果不采用这种机制,那么系统核心的类就可以被随意替换)
** 不同类加载器加载的类不是相同类型:在Java中,一个类的全名(包名+类名)作为其标识,但在JVM中,一个类用其全名+类加载器作为唯一标识,不同类加载器加载的类置于不同的命名空间中**
线程上下文类加载器
双亲委派模式不能解决全部的加载问题。
Java提供了很多服务提供者接口(Service Provider Interface,SPI),常见的有JDBC、JNDI、JAXP等。这些SPI接口由Java核心库提供(通过BootstrapClassLoader加载),而它们的实现由第三方库提供(通过SystemClassLoader加载)。但很多时候,SPI接口需要加载具体的实现类,如 JAXP 中的javax.xml.parsers.DocumentBuilderFactory类中的newInstance()方法用来生成一个新的DocumentBuilderFactory的实例。这里的实例的真正的类是继承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的实现所提供的。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。问题是BootstrapClassLoader是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给SystemClassLoader,因为它是SystemClassLoader的祖先类。
而线程上下文类加载器就是解决这个问题,如果不做任何的设置,Java的线程的上下文类加载器默认就是SystemClassLoader。在SPI接口的代码中使用线程上下文类加载器,就可以成功的加载到SPI实现的类。
使用线程上下文加载器,要注意保证多个需要通信的线程间的类加载器应该是同一个,防止因为不同的类加载器导致类型转换异常(ClassCastException)。
运行时区域
Method Area
已加载的类信息(类结构、方法、字段、静态变量)
常量池:字符串、整形(-127-128)
一般称为Permanent Generation
线程执行
每个线程都有一个PC Register、JVM Stack、Native Method Stack
PC Register:下一条要执行的指令的地址
JVM Stack:包含一系列的Stack Frame,每次方法调用都会创建一个Frame并压栈,每个栈帧都对应一个被调用的方法(使用递归容易让栈溢出,通过-Xss设置栈大小)。同时那些方法内的局部变量,也是在这里创建
Heap
存放实例对象和数组
分成Young、Tenured、Permanent三个不同区域,其中Young又分成Eden和两个相同大小的Survivor:From、To
为什么要分代:不同对象的生命周期是不一样的,采用不同的收集算法,可以提高回收效率
例子
public class Test {
public static void main(String[] args) {
public Test2 t2 = new Test2();
//JVM将Test2类信息加载到方法区,new Test2()实例保存在堆区,Test2引用保存在栈区
}
}
参考
http://docs.oracle.com/javase/specs/jvms/se7/jvms7.pdf
http://blog.csdn.net/zhoudaxia/article/details/35897057
http://www.ibm.com/developerworks/cn/java/j-lo-classloader/