JVM 四大模块一之类加载器

JVM 类加载与类加载器
类加载

类的生命周期是由7个阶段组成,如图
 

首先类的加载阶段
1.通过类的全限命名获取储存该类的class文件(获取方式)

2.解析成运行时数据,即instanceKlass 实例,存放在方法区

3.在堆区生成该类的class对象,即instanceMirrorKlass实例

何时加载

-主动使用时

1、new、getstatic、putstatic、invokestatic

2、反射

3、初始化一个类的子类会去加载其父类

4、启动类(main函数所在类)

5、当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化

-预加载:包装类,String,Thread

获取方式 比如

1、从压缩包中读取,如jar、war

2、从网络中获取,如Web Applet

3、动态生成,如动态代理、CGLIB

4、由其他文件生成,如JSP

5、从数据库读取

6、从加密文件中读取

验证阶段

1、文件格式验证

2、元数据验证

3、字节码验证

4、符号引用验证

准备阶段

为静态变量分配内存并赋初值:比如 int = 0;long = 0L;

实例变量时在创建对象的时候完成赋值的,并没有赋初值的说法

如果变量被final修饰了,那么在编译的时候会给属性添加 constantValue 属性,准备阶段(编译完)直接完成赋值

    public static int a = 20;
    public static final int b = 30;
    public static void main(String[] args) {
        //不运行直接编译
    }

解析阶段

通俗点就是说 间接引用->直接引用

间接引用:指向运行时常量池的引用

直接引用:内存地址

还是用这个Test对象,我们分别来看看编译后和运行中的常量池信息

    public static int a = 20;
    public static final int b = 30;
    public static void main(String[] args) {
        for (;;);//运行中
    }

1.编译后的 切换到classes目录下使用javap -verbose 或者 jclasslib

比如我的是 D:\spring-boot\boot\target\classes>javap -verbose com.waf.boot.wk.Test 得到的结果是常量池的引用
javap -verbose

D:\spring-boot\boot\target\classes>javap -verbose com.waf.boot.wk.Test
Classfile /D:/spring-boot/boot/target/classes/com/waf/boot/wk/Test.class
  Last modified 2021-3-24; size 566 bytes
  MD5 checksum d7655afea6f7cf4d8b5ab6005cd334ab
  Compiled from "Test.java"
public class com.waf.boot.wk.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#26         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#27         // com/waf/boot/wk/Test.a:I
   #3 = Class              #28            // com/waf/boot/wk/Test
   #4 = Class              #29            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               b
   #8 = Utf8               ConstantValue
   #9 = Integer            30
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               LocalVariableTable
  #15 = Utf8               this
  #16 = Utf8               Lcom/waf/boot/wk/Test;
  #17 = Utf8               main
  #18 = Utf8               ([Ljava/lang/String;)V
  #19 = Utf8               args
  #20 = Utf8               [Ljava/lang/String;
  #21 = Utf8               StackMapTable
  #22 = Utf8               MethodParameters
  #23 = Utf8               <clinit>
  #24 = Utf8               SourceFile
  #25 = Utf8               Test.java
  #26 = NameAndType        #10:#11        // "<init>":()V
  #27 = NameAndType        #5:#6          // a:I
  #28 = Utf8               com/waf/boot/wk/Test
  #29 = Utf8               java/lang/Object
{
  public static int a;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC

  public static final int b;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 30

  public com.waf.boot.wk.Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/waf/boot/wk/Test;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: goto          0
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       3     0  args   [Ljava/lang/String;
      StackMapTable: number_of_entries = 1
        frame_type = 0 /* same */
    MethodParameters:
      Name                           Flags
      args

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        20
         2: putstatic     #2                  // Field a:I
         5: return
      LineNumberTable:
        line 5: 0
}
SourceFile: "Test.java"

主要看 Constant pool:

#3 = Class              #28            // com/waf/boot/wk/Test
jclasslib 得到的也是常量池的引用

2.运行后的通过进程号在HSDB里面看 得到的是真正的内存地址

 初始化阶段

执行静态代码块,完成静态变量的赋值

静态字段、静态代码段,字节码层面会生成clinit方法

方法中语句的先后顺序与代码的编写顺序相关

我们可以看看static的几个例子

public class Test_1 {
    public static void main(String[] args) {
        System.out.println(Test_1_B.str);
    }
}

class Test_1_A{
    static {
        System.out.println("a static");
    }
}

class Test_1_B extends Test_1_A{
    public static String str = "a str";
    static {
        System.out.println("b static");
    }
}
//output
a static
b static
a str
public class Test_1 {
    public static void main(String[] args) {
        System.out.println(Test_1_B.str);
    }
}

class Test_1_A{
    public static String str = "a str";
    static {
        System.out.println("a static");
    }
}

class Test_1_B extends Test_1_A{
    static {
        System.out.println("b static");
    }
}
//output
a static
a str

这个静态字段在klass中是怎么存储的呢--通过HSDB工具看第二个例子中Test_1_A & Test_1_B

存储在 instanceMirrorKlass(Oop) 中 而且 Test_1_B 是没有 str 的属性(只存在Java语法的继承关系)那底层是怎么找到的呢(没搞懂....)

public class Test_1 {
    public static void main(String[] args) {
        System.out.println(new Test_1_B().str);
    }
}

class Test_1_A{
    public  String str = "a str";
    static {
        System.out.println("a static");
    }
}

class Test_1_B extends Test_1_A{
    static {
        System.out.println("b static");
    }
}
//output
a static
b static
a str

JVM读取静态属性的方式(例子二)(那底层是怎么找到的呢(没搞懂....))

str是类Test_1_A的静态属性,可以看到不会存储到子类Test_1_B的镜像类中

可以猜得到,通过子类Test_1_B访问父类Test_1_A的静态字段有两种实现方式:

1、先去Test_1_B的镜像类中去取,如果有直接返回;如果没有,会沿着继承链将请求往上抛。很明显,这种算法的性能随继承链的death而上升,算法复杂度为O(n)

2、借助另外的数据结构实现,使用K-V的格式存储,查询性能为O(1)

Hotspot就是使用的第二种方式,借助另外的数据结构ConstantPoolCache,常量池类ConstantPool中有个属性_cache指向了这个结构。每一条数据对应一个类ConstantPoolCacheEntry。

ConstantPoolCacheEntry在哪呢?在ConstantPoolCache对象后面,看代码C++

\openjdk\hotspot\src\share\vm\oops\cpCache.hpp

ConstantPoolCacheEntry* base() const           { 
  return (ConstantPoolCacheEntry*)((address)this + in_bytes(base_offset()));
}

这个公式的意思是ConstantPoolCache对象的地址加上ConstantPoolCache对象的内存大小

ConstantPoolCache

常量池缓存是为常量池预留的运行时数据结构。保存所有字段访问和调用字节码的解释器运行时信息。缓存是在类被积极使用之前创建和初始化的。每个缓存项在解析时被填充

如何读取

\openjdk\hotspot\src\share\vm\interpreter\bytecodeInterpreter.cpp

CASE(_getstatic):
        {
          u2 index;
          ConstantPoolCacheEntry* cache;
          index = Bytes::get_native_u2(pc+1);

          // QQQ Need to make this as inlined as possible. Probably need to
          // split all the bytecode cases out so c++ compiler has a chance
          // for constant prop to fold everything possible away.

          cache = cp->entry_at(index);
          if (!cache->is_resolved((Bytecodes::Code)opcode)) {
            CALL_VM(InterpreterRuntime::resolve_get_put(THREAD, (Bytecodes::Code)opcode),
                    handle_exception);
            cache = cp->entry_at(index);
          }
……

从代码中可以看出,是直接去获取ConstantPoolCacheEntry

类加载器

猜你喜欢

转载自blog.csdn.net/qq_38108719/article/details/115167676
今日推荐