理解Java虚拟机(2)之.class文件加载过程

来源:互联网 发布:北bi数据分析 编辑:程序博客网 时间:2024/05/16 15:43

理解Java虚拟机(2)之.class文件加载过程

读《深入理解Java虚拟机》-周志明 读书笔记

虚拟机只能执行.class文件,在.class文件加载过程中,生命周期包括:加载,验证,准备,解析,初始化使用卸载

image

  • 加载将.clss文件加载进虚拟机,加载来源有

    • 1.常见jar包;
    • 2.网络获取(dubbo的RPC是典型,底层通过java的RMI方法,通过固定协 议,将远端生成者接口,对象序列化,消费者调用);
    • 3.运行时动态生成,典型代表反射技术;
    • 4.从其他文件,如jsp;
    • 5.通过数据库读取,(没见到过,书里说中间件服务器)

    加载完成后,虚拟机就会按照.class文件里面的内容相应的存储到内存的方法区(方法区是虚拟机加载类信息,常量,静态变量,即使编译后的代码,是线程共享的,简单讲就是加载的东西放在这里面),加载是通过ClassLoader这个加载,这个加载机制是通过(双亲委派模型,后面详细讲双亲委派模型,简单讲就是JVM有一个系列的继承加载,自己也可以自定义ClassLoader,先通过自己的ClassLoader,在通过JVM的ClassLoader),平常使用Tomcat,Jetty,这些容器都是有自己的ClassLoader,用过maven配置jetty启动时,都会在pom.xml配置标签,这个就是去配置jetty的ClassLoader。

  • 验证
    验证是连接阶段的第一步,并不是加载完了才验证(这样的太傻瓜式了,要是很多.class文件,在最开始加载就有错误,等加载完了再验证那就不用工作了),验证和加载是相辅相成的。在.java编译成.class文件里讲过.class打头就是个”cafebabe”的魔数,首先验证这个,不是这个打头的必然报错,接着看看版本号,版本号是不是java没有的(比方搞个java6.0,必然错了),然后看常量类型啊,验证相当于看这个.class文件是否符合JVM规范,用过Eclipse的就知道,每次”ctrl+s”就能看到自己写的代码有没有错误,因为eclipse就是有自己的CLassLoader,即时编译,你一保存就给你编译了.class,打开文件夹就能看到class目录的文件夹,然后通过去验证编译后的.class是不是符合JVM规范,然后错了就各种红叉叉,iead就没有即时编译,”ctrl+s”就找不到对应的class文件夹,eclipse的验证不是虚拟机的验证,这只是eclipse自己根据jVM规范验证的,编译后的.class文件JVM在加载的过程中,还是要自己验证的,不过虚拟机有个参数可以设置不验证(-Xverify:none),这样可以加快加载速度,当然前提是你相信.class符合规范,验证是很重要的一件事,不然怎么承受恶意的攻击,使得代码更安全,当然验证也会使得加载时间长,在安全和时间上,还是选择更安全些,毕竟java大都都是用来做企业级应用的,安全是最重要的。

  • 准备
    准备阶段是正式给类变量(static修饰的变量)分配内存并设置类变量的初始值,实例变量(不是static修饰的类层面的变量)会在对象实例化时随着对象一起分配在java堆中,要注意的是初始值的含义,和final修饰的实例变量,初始值指数据类型的0值,下面a在这个时候初始值为0;final修饰的变量这个时候会给值,c此时的值就是为3;(感觉final修饰符有些破坏了面向对象的设计,也不知道这个设计的初衷)

public Class Test(){    private static int a = 1;//类变量    private int b = 2 ;//实例变量    private final int c = 3 ;}
  • 解析
    解析是虚拟机将常量池(常量池位于方法区)的符号引用替换成直接引用的过程,先解释下符号引用直接引用
    符号引用:
   public class Test {    public static  int sa = new Random().nextInt();    }    public class TestClass {    //这里就是符号引用    public static  int sa = Test.sa;    public static  int sb = 1;}

编译下上上面的,再反编译过来如下(javap -verbose TestClass.class):

D:\learn\myLearn\readBook\src\main\java\JVM>javap -verbose TestClass.classClassfile /D:/learn/myLearn/readBook/src/main/java/JVM/TestClass.class  Last modified 2016-8-12; size 330 bytes  MD5 checksum 96ed1ae561b16a0cfecbdf7518c947cb  Compiled from "TestClass.java"public class JVM.TestClass  SourceFile: "TestClass.java"  minor version: 0  major version: 51  flags: ACC_PUBLIC, ACC_SUPERConstant pool:   #1 = Methodref          #6.#17         //  java/lang/Object."<init>":()   #2 = Fieldref           #18.#19        //  JVM/Test.sa:I   #3 = Fieldref           #5.#19         //  JVM/TestClass.sa:I   #4 = Fieldref           #5.#20         //  JVM/TestClass.sb:I   #5 = Class              #21            //  JVM/TestClass   #6 = Class              #22            //  java/lang/Object   #7 = Utf8               sa   #8 = Utf8               I   #9 = Utf8               sb  #10 = Utf8               <init>  #11 = Utf8               ()V  #12 = Utf8               Code  #13 = Utf8               LineNumberTable  #14 = Utf8               <clinit>  #15 = Utf8               SourceFile  #16 = Utf8               TestClass.java  #17 = NameAndType        #10:#11        //  "<init>":()V  #18 = Class              #23            //  JVM/Test  #19 = NameAndType        #7:#8          //  sa:I  #20 = NameAndType        #9:#8          //  sb:I  #21 = Utf8               JVM/TestClass  #22 = Utf8               java/lang/Object  #23 = Utf8               JVM/Test{  public static int sa;    flags: ACC_PUBLIC, ACC_STATIC  public static int sb;    flags: ACC_PUBLIC, ACC_STATIC  public JVM.TestClass();    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."":()V         4: return      LineNumberTable:        line 8: 0  static {};    flags: ACC_STATIC    Code:      stack=1, locals=0, args_size=0         0: getstatic     #2                  // Field JVM/Test.sa:I         3: putstatic     #3                  // Field sa:I         6: bipush        114         8: putstatic     #4                  // Field sb:I        11: return      LineNumberTable:        line 9: 0        line 11: 6}

截部分看看

java/lang/Object."<init>":()   #2 = Fieldref           #18.#19        //  JVM/Test.sa:I   #3 = Fieldref           #5.#19         //  JVM/TestClass.sa:I   static {};    flags: ACC_STATIC    Code:      stack=1, locals=0, args_size=0         0: getstatic     #2                  // Field JVM/Test.sa:I         3: putstatic     #3                  // Field sa:I          6: bipush        114         8: putstatic     #4                  // Field sb:I  
最后satatic{}可以看到Test.java中sa(#3 = Fieldref          #5.#19 //  JVM/TestClass.sa:I)变量指向的TestClass(JVM/Test.sa:I 3: putstatic     #3   // Field sa:I ),初始化就是将这种指向变成指向new Random().nextInt(),的具体内存地址,这样才能真正的调用直接引用就是直接new 对象了,将句柄指向对象的指针地址;在解析的过程中,遇到引用其他的类,就去触发对应类的加载,最后都是直接引用
  • 初始化

初始化是类加载的最后一步,这个阶段才是真正执行类中定义的代码,在准备阶段,变量已经赋值过系统要求的值,初始化阶段根据我们写代码的主观习惯去初始化变量和其他资源。相当于程序真正运行起来。

0 0