JVM中如何唯一标识一个类?JVM体系结构【转】
源地址:https://www.jianshu.com/p/b4a0ad809c77
在Java中,一个类的全名(包名+类名)作为其标识,但在JVM中,一个类用其全名+类加载器作为唯一标识,不同类加载器加载的类置于不同的命名空间中,这叫做类加载器隔离。
![](http://upload-images.jianshu.io/upload_images/1389146-4394687a2cf402c5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/560/format/webp)
ClassLoader
将Class加载到内存
结构
- BootstrapClassLoader:加载Java核心库(JAVA_HOME/jre/lib),唯一一个使用本地代码编写的加载器
- ExtensionClassLoader:加载扩展库(JAVA_HOME/jre/lib/ext和系统参数java.ext.dirs指定的目录),它的父加载器是null(因为BootstrapClassLoader是使用C++实现的,没有对应的java类)
- SystemClassLoader:加载应用类的类文件(Classpath下的类文件)
- UserDefineClassLoader:加载用户定义的类文件,可以从网络或者数据库中加载类文件,实现类文件加密解密,动态地创建符合应用特殊需要的定制化类等
![](http://upload-images.jianshu.io/upload_images/1389146-148542501031fdc8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/238/format/webp)
双亲委派机制
加载器在接收到加载类的请求时,首先检查自己的缓存,确认类是否已被加载,如果没有加载,则将请求委托给父加载器,依次递归,如果父加载器完成加载,则成功返回,否则才自己去加载
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)。
运行时区域
![](http://upload-images.jianshu.io/upload_images/1389146-92a630dff91d8650.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/366/format/webp)
Method Area
已加载的类信息(类结构、方法、字段、静态变量)
常量池:字符串、整形(-127-128)
一般称为Permanent Generation
线程执行
每个线程都有一个PC Register、JVM Stack、Native Method Stack
PC Register:下一条要执行的指令的地址
JVM Stack:包含一系列的Stack Frame,每次方法调用都会创建一个Frame并压栈,每个栈帧都对应一个被调用的方法(使用递归容易让栈溢出,通过-Xss设置栈大小)。同时那些方法内的局部变量,也是在这里创建
![](http://upload-images.jianshu.io/upload_images/1389146-5df78b4f3d9ef9d7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/378/format/webp)
Heap
![](http://upload-images.jianshu.io/upload_images/1389146-dc1b2035cdaadb0b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/574/format/webp)
存放实例对象和数组
分成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/
作者:PennyWong
链接:https://www.jianshu.com/p/b4a0ad809c77
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。