JVM中如何唯一标识一个类?JVM体系结构【转】

源地址:https://www.jianshu.com/p/b4a0ad809c77

在Java中,一个类的全名(包名+类名)作为其标识,但在JVM中,一个类用其全名+类加载器作为唯一标识,不同类加载器加载的类置于不同的命名空间中,这叫做类加载器隔离。

 
JVM体系结构

ClassLoader

将Class加载到内存

结构

  • BootstrapClassLoader:加载Java核心库(JAVA_HOME/jre/lib),唯一一个使用本地代码编写的加载器
  • ExtensionClassLoader:加载扩展库(JAVA_HOME/jre/lib/ext和系统参数java.ext.dirs指定的目录),它的父加载器是null(因为BootstrapClassLoader是使用C++实现的,没有对应的java类)
  • SystemClassLoader:加载应用类的类文件(Classpath下的类文件)
  • UserDefineClassLoader:加载用户定义的类文件,可以从网络或者数据库中加载类文件,实现类文件加密解密,动态地创建符合应用特殊需要的定制化类等
 
ClassLoader.png

双亲委派机制

加载器在接收到加载类的请求时,首先检查自己的缓存,确认类是否已被加载,如果没有加载,则将请求委托给父加载器,依次递归,如果父加载器完成加载,则成功返回,否则才自己去加载

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设置栈大小)。同时那些方法内的局部变量,也是在这里创建


 
Stack Frame

Heap

 

 
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/



作者:PennyWong
链接:https://www.jianshu.com/p/b4a0ad809c77
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

 

posted @ 2018-09-20 11:42  daddy再出发  阅读(1460)  评论(0编辑  收藏  举报