`
464772913
  • 浏览: 24412 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

jvm原理

 
阅读更多

JVM 原理解释



 

  JVM 全称是 Java Virtual Machine ,Java 虚拟机,这个 JVM 你是看不到的,它存在内存中。我们知道计算机的基本构成是:运算器、控制器、存储器、输入和输出设备,那这个 JVM 也是有这成套的元素,运算器是当然是交给硬件 CPU 还处理了,只是为了适应“一次编译,随处运行”的情况,需要做一个翻译动作,于是就用了JVM 自己的命令集,JVM 的命令集则是可以到处运行的,因为 JVM 做了翻译,根据不同的CPU ,翻译成不同的机器语言。

  JVM 是一个内存中的虚拟机,那它的存储就是内存了,我们写的所有类、常量、变量、方法都在内存中。

  JVM 的组成部分



 

  Class Loader 类加载器

  类加载器的作用是加载类文件(.class)到内存,Class Loader 加载的 class 文件是有格式要求的。

  类加载的最终产品是位于运行时数据区的堆区的Class对象。

  Class对象封装了类在方法区内部的数据结构。 

  并且向JAVA程序提供了访问类在方法区内的数据结构。



 

  JVM加载class文件的原理机制

  1. Java 中的所有类,必须被装载到 JMV 中才能运行,这个装载工作是由 JVM 中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中。

  2. Java中的类大致分为三种:

  a) 系统类

  b) 扩展类

  c) 由程序员自定义的类

  3. 类装载方式,有两种:

  a) 隐式装载,程序在运行过程中当碰到通过 new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中。

  b) 显式装载,通过 class.forname() 等方法,显式加载需要的类。  

  4. 类加载的动态性体现

  一个应用程序总是由n多个类组 成,Java 程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到 JVM 中,其它类等到 JVM 用到的时候再加载,这样的好处是节省了内存的开销。因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是 Java 动态性的一种体现。

  5.Java 类装载器

  Java 中的类装载器实质上也是类,功能是把类载入 JVM 中,值得注意的是 JVM 的类装载器并不是一个,而是三个,层次结构如下:

      Bootstrap Loader  - 负责加载系统类
            |
          - - ExtClassLoader  - 负责加载扩展类
                          |
                      - - AppClassLoader  - 负责加载应用类

  为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型。

  6. 类加载器之间是如何协调工作的

  前面说了,Java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,即 Java 是如何区分一个类该由哪个类加载器来完成呢。

  在这里Java采用了委托模型机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果 Parent 找不到,那么才由自己依照自己的搜索路径搜索类”,注意喔,这句话具有递归性。

  

 

 JVM加载器机制

 

Java中,在调用类的静态成员,或新建该类的对象等之前,类一定要先装入Java虚拟机中,这是勿庸置疑的。但虚拟机怎样把类装载进来的呢?要经过三 步:装载(Load),链接(Link),初始化(Initializ)。其中链接又可分为校验(Verify),准备(Prepare),解析 (Resolve)三步。

JVM类加载机制

一、装载(Load)
ClassLoader就是用来装载的。通过指定的className,找到二进制码,生成Class实例,放到JVM中。
ClassLoader从顶向下分为 Bootstrap ClassLoader、Extension ClassLoader、System ClassLoader以及User-Defined ClassLoader(分叉,可以多个)。如下图。
JVM类加载机制

这是Tomcat装载器的例子:
JVM类加载机制


装载过程从源码清析可见:
protected synchronized Class<?> loadClass(String name, boolean resolve)
 

   throws ClassNotFoundException
{
    // 先检查是否已被当前ClassLoader装载。
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
        if (parent != null) {
            // 如果没被当前装载,则递归的到父中装载。
            c = parent.loadClass(name, false);
        } else {
           // 装载器树已到顶,还没找到的话就到Bootstrap装载器中找。注意:虽然Bootstrap是所有加载器的根,但它是C++实现的,不可能放到子的"parent"中,因此,第二层装载器是所有的根了。
            c = findBootstrapClass0(name);
        }
        } catch (ClassNotFoundException e) {
            // 如果祖先都无法装载,则用当前的装载。子类可在findClass方法中调用defineClass,把从自定义位置获得的字节码转换成Class。
            c = findClass(name);
        }
    }
    if (resolve) {
        // Links the specified class.
        resolveClass(c);  // 注
    }
    return c;
}

注:
1. resolveClass(c)方法的注释是链接类,而不只是解析,从该即可看出。
调用resolveClass时语义上是去链接,是否真的链接了我不是很清楚,但可以肯定的是没有初始化。当A类中有static B b=new B()时,最晚会在初始化时去装载B。
如果改成static B b=null,那么把B.class删掉后,即使A已经链接,初始化过了,但也不会报错,也就是A所引用的B类没有被加载过。解析时难道没有真的去装入它所引用的B类?还是链接时,没有执行解析的步骤? 问题的关键就是 1.对“解析”的理解,解析时是否会去装载B类?2. JVM在链接时是否执行了解析?(毕竟有资料说,解析是可选步骤)

二、链接
链接就是把load进来的class合并到JVM的运行时状态中

链接 是三个阶段中最复杂的一个。可以把它分成三个主要阶段:

  • 校验。 对二进制字节码的格式进行校验,以确保格式正确、行为正确。
  • 准备。 准备类中定义的字段、方法和实现接口所必需的数据结构。比如会为类中的静态变量赋默认值(int等:0, reference:null, char:'\u0000')。
  • 解析。 装入类所引用的其他所有类。可以用许多方式引用类:
    • 超类
    • 接口
    • 字段
    • 方法签名
    • 方法中使用的本地变量
三、初始化
Initialization of a class consists of executing its static initializers and the initializers for static fields (class variables) declared in the class. Initialization of an interface consists of executing the initializers for fields (constants) declared there.
类的初始化包括:执行静态区块和静态方法的初始化。比如下面这两种代码都会被执行,包括new B()。
static{
  ...
}
static B b=new B();

接口中不允许有static initializer(也就是static{...}),所以对于接口,只会执行静态字段的初始化。

初始化前,装载,链接一定已经执行过!

类初始化前,它的直接父类一定要先初始化(递归),但它实现的接口不需要先被初始化。类似的,接口在初始化前,父接口不需要先初始化。

什么情况下,类的初始化会被触发?

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

 

  • T is a class and an instance of T is created.
  • T is a class and a static method declared by T is invoked.
  • A static field declared by T is assigned.
  • A static field declared by T is used and the field is not a constant variable (§4.12.4).
  • T is a top-level class, and an assert statement (§14.10) lexically nested within T is executed.

当使用类的字段时,即便可以通过子类或子接口访问该字段,但只有真正定义该字段的类会被触发初始化。如下例。

class Super { static int taxi = 1729; }

class Sub extends Super { static { System.out.print("Sub "); } }

class Test {

    public static void main(String[] args) {

               System.out.println(Sub.taxi);

    }

}

只会输出“1729”,不会输出"Sub",也就是说,Sub其实没有被初始化。

四、Class.forName()与ClassLoader.loadClass()
这两方法都可以通过一个给定的类名去定位和加载这个类名对应的 java.long.Class 类对象,区别如下:
1. 初始化
Class.forName()会对类初始化,而loadClass()只会装载或链接。可见的效果就是类中静态初始化段及字节码中对所有静态成员的初始工作的执行(这个过程在类的所有父类中递归地调用). 这点就与ClassLoader.loadClass()不同. ClassLoader.loadClass()加载的类对象是在第一次被调用时才进行初始化的。
你可以利用上述的差异. 比如,要加载一个静态初始化开销很大的类, 你就可以选择提前加载该类(以确保它在classpath下), 但不进行初始化, 直到第一次使用该类的域或方法时才进行初始化

2. 类加载器可能不同
Class.forName(String) 方法(只有一个参数), 使用调用者的类加载器来加载, 也就是用加载了调用forName方法的代码的那个类加载器。当然,它也有个重载的方法,可以指定加载器。 相应的, ClassLoader.loadClass()方法是一个实例方法(非静态方法), 调用时需要自己指定类加载器, 那么这个类加载器就可能是也可能不是加载调用代码的类加载器(调用代用代码类加载器通getClassLoader0()获得)
  • 大小: 13.4 KB
  • 大小: 19.8 KB
  • 大小: 27 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics