java

JVM--类加载与反射

2021-10-04  本文已影响0人  aruba

对JVM的内存有了一定理解后,再来看JVM是如何加载类,以及Java的反射机制

一、类加载过程

有了前面的了解,我们知道Java文件先要编译成class文件,再由JVM加载class到方法区成为类元信息,最后实例化class对象,加载类的过程又可以细分为:加载、连接、初始化、使用、卸载

类加载生命周期
1.加载(Loading)

Java编译为class文件后,在使用类时,JVM如果没有加载过class,则会先加载class文件,加载可以用读文件操作来理解,就是将文件内容加载到内存中,转化为类元信息,作为方法区这个类的各种数据访问入口,并实例化Class对象,存放在堆中

2.连接(Linking)
2.1 验证(Verifivation)

class文件为字节码,根据字节码对应表进行验证,如:对该class文件进行标志头校验,class文件的前4个字节都是 “0xCAFEBABE”

2.2 准备(Preparation)

根据字节码,如果有静态成员变量,那么在方法区为它们分配内存,并将内存清零(类似c语言memset函数),如果是静态常量(final修饰),那么此时就会赋值,字符串比较特殊,它会分配在字符串常量池中

2.3 解析(Resolution)

根据字节码对照表把Constant Pool Table中的符号转换成直接引用
每个符号为一个指针,解析时,将符号指向对应的内存首地址(变量、函数、函数类型结构体等)
栈帧中的动态链接也是使用这种机制,一个方法对应一个指针,指向了常量池中的符号,符号指向一个方法,来执行方法中的代码

下面class文件反编译的内容,可以作为参考:

public class Hello {
    public String name = "aaa";
    private final static int nameConst = 123;
    private static int nameStatic = 1234;

    private int test() {
        int a = 3;
        int b = 4;

        return a + b;
    }

    public int test(int a) {
        int b = 4;

        return a + b;
    }

}
Classfile /C:/Users/tyqhc/Documents/javaworkspace/myJava/out/production/myJava/Hello.class
  Last modified 2021-10-13; size 651 bytes
  MD5 checksum 57a1c2ff200580304191ccda4feaea70
  Compiled from "Hello.java"
public class Hello
  SourceFile: "Hello.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#29         //  java/lang/Object."<init>":()V
   #2 = String             #30            //  aaa
   #3 = Fieldref           #5.#31         //  Hello.name:Ljava/lang/String;
   #4 = Fieldref           #5.#32         //  Hello.nameStatic:I
   #5 = Class              #33            //  Hello
   #6 = Class              #34            //  java/lang/Object
   #7 = Utf8               name
   #8 = Utf8               Ljava/lang/String;
   #9 = Utf8               nameConst
  #10 = Utf8               I
  #11 = Utf8               ConstantValue
  #12 = Integer            123
  #13 = Utf8               nameStatic
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               LHello;
  #21 = Utf8               test
  #22 = Utf8               ()I
  #23 = Utf8               a
  #24 = Utf8               b
  #25 = Utf8               (I)I
  #26 = Utf8               <clinit>
  #27 = Utf8               SourceFile
  #28 = Utf8               Hello.java
  #29 = NameAndType        #14:#15        //  "<init>":()V
  #30 = Utf8               aaa
  #31 = NameAndType        #7:#8          //  name:Ljava/lang/String;
  #32 = NameAndType        #13:#10        //  nameStatic:I
  #33 = Utf8               Hello
  #34 = Utf8               java/lang/Object
{
  public java.lang.String name;
    flags: ACC_PUBLIC

  public Hello();
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #2                  // String aaa
         7: putfield      #3                  // Field name:Ljava/lang/String;
        10: return
      LineNumberTable:
        line 6: 0
        line 7: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      11     0  this   LHello;

  public int test(int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=2
         0: iconst_4
         1: istore_2
         2: iload_1
         3: iload_2
         4: iadd
         5: ireturn
      LineNumberTable:
        line 19: 0
        line 21: 2
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       6     0  this   LHello;
               0       6     1     a   I
               2       4     2     b   I

  static {};
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: sipush        1234
         3: putstatic     #4                  // Field nameStatic:I
         6: return
      LineNumberTable:
        line 9: 0
}

3.初始化(Initialization)

类初始化阶段是类加载过程的最后一步。在前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的java程序代码(字节码)。
在准备阶段,只是对静态成员变量进行了内存分配和内存初始化。初始化阶段才会对成员变量进行赋值,相当于执行构造函数中的内容,以及执行static代码块中的内容

4.为对象分配内存的两种方式

二、类加载时机

我们了解了类加载的流程后,试想下,什么时候会触发类的加载呢?


类加载时机

三、类加载器-双亲委派机制

类加载时,如果以前加载过,那么就不需要加载该类,实现这个机制的,就是双亲委派
子加载器不断往上询问是否加载过,再有顶至下加载该类,可以加载就直接加载,否则往下委派加载

双亲委派机制

四、反射

反射是Java中一种机制,它能够帮助我们动态的使用一个类,其本质就是获取类元信息,并通过符号引用来操作内存或调用方法
例子使用的类如下:

public class Hello {
    public String name;
    private String namePrivate;

    public int test() {
        int a = 3;
        int b = 4;

        return a + b;
    }

    public int test(int a) {
        int b = 4;

        return a + b;
    }

}
1.获取Class对象

JVM加载类后,会在方法区存在一份类元信息,我们可以通过以下方法获取它:

2.获取方法和调用方法

获取到Class后,我们就可以通过以下两种方式,获取方法对象Method,并通过Method的invoke方法反向调用方法

    //getMethod只能获取公有方法,但是也可以获取父类公有方法
    Method method = helloClass.getMethod("test", int.class);
    method.invoke(h, 5);
    //getDeclaredMethod可以获取该类中的所有方法,但是不可以获取父类方法
    Method method = helloClass.getDeclaredMethod("test");
    method.setAccessible(true);
    method.invoke(h);
3.获取构造方法和通过Class实例化对象

Class对象还可以获取构造方法:

    Constructor<Hello> constructor = helloClass.getConstructor();

同样的,私有构造方法可以通过getDeclaredConstructor方法获取,setAccessible(true)后才可以调用

我们可以通过构造方法来实例化对象:

    Hello h2 = constructor.newInstance();

还可以通过Class直接实例化,该方式只能是无参构造

    Hello h3 = helloClass.newInstance();
4.获取属性和设置属性

通过以下两种方式,获取属性Field对象,并通过Filed的set方法,来为对象设置新值

    Field name = helloClass.getField("name");
    name.set(h, "hello");
    Field name = helloClass.getDeclaredField("namePrivate");
    name.setAccessible(true);
    name.set(h, "helloPrivate");
上一篇下一篇

猜你喜欢

热点阅读