02、JVM实战:内存结构、类的加载过程

一、内存结构概述

*

类加载器子系统

*

  • class文件进入到虚拟机后,首先进行Loading(加载)然后Linking(链接)最后进行初始化。
  • 加载使用的加载器分为:Bootstrap ClassLoader(引导类加载器)、Extension ClassLoader(扩展类加载器)、Application ClassLoader(应用类加载器) 。
  • 连接分为:Verify(验证)、Prepare(准备)、Resolve(解析)。

内存区域

*

  • PC Registers(PC寄存器或程序计数器,线程独享一份)
  • Stack Area(栈区域,线程独享一份)
  • Native Method Stack(本地方法栈)主要作用域本地方法接口api调用
  • Heap Area(堆区域)主要存放对象
  • Method Area(方法区)主要存放常量、域、方法信息等。HotSpot虚拟机特有。

执行引擎

*

二、类加载器与类加载过程

*

  • 类加载器子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识。
  • . classLoader只负责class文件的加载,至于它是否可以运行,则由ExecutionEngine决定。
  • *加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是class文件中常量池部分的内存映射)

例如下图:

*

*

三、类的加载过程(Loading)

*

加载:

1、 通过一个类的全限定名获取定义此类的二进制字节流;
2、 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3、 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;

补充:加载.class文件的方式

  • 从本地系统中直接加载
  • 通过网络获取,典型场景: web Applet
  • 从zip压缩包中读取,成为日后jar、war格式的基础
  • 运行时计算生成,使用最多的是:动态代理技术
  • 由其他文件生成,典型场景:JSP应用
  • 从专有数据库中提取.class文件,比较少见
  • 从加密文件中获取,典型的防class文件被反编译的保护措施

四、类的加载过程(Linking)

*

验证(Verify)

在idea中安装二进制查看插件。

* 在class文件右键查看二进制会发现所有的java字节码文件起始位置都是CAFEBABE(魔数) ,这也就是校验的过程。

*

准备(Prepare)

public class HelloApp {
    private static int a = 1; // Prepare环节a=0 -> initial a赋值为1
}

五、类的加载过程(initialization)

初始化:

  • 初始化阶段就是执行类构造器方法<clinit>()的过程。
  • 此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
  • 构造器方法中指令按语句在源文件中出现的顺序执行。
  • <clinit>()不同于类的构造器。(关联:构造器是虚拟机视角下的<init>( ))
  • 若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕。
  • 虚拟机必须保证一个类的<clinit> ()方法在多线程下被同步加锁。

安装BytecodeViewer插件查看字节码文件。

*

例1:java代码:

public class HelloApp {
    private static int a = 1; 
    static {
        a = 2;
        b = 20;
    }
    private static int b = 10;
}

选中要查看的class文件找到下图选项

*

可以发现clinit对静态代码是按照自上而下的顺序初始化的。变量b在prepare阶段赋值为0,然后在初始化阶段先执行的静态代码块b=20,然后在执行的下面代码b=10。

// class version 55.0 (55)
// access flags 0x21
public class org/ywz/springbootdemo/HelloApp {

  // compiled from: HelloApp.java

  // access flags 0xA
  private static I a

  // access flags 0xA
  private static I b

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lorg/ywz/springbootdemo/HelloApp; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 4 L0
    ICONST_1
    PUTSTATIC org/ywz/springbootdemo/HelloApp.a : I
   L1
    LINENUMBER 7 L1
    ICONST_2
    PUTSTATIC org/ywz/springbootdemo/HelloApp.a : I
   L2
    LINENUMBER 8 L2
    BIPUSH 20
    PUTSTATIC org/ywz/springbootdemo/HelloApp.b : I
   L3
    LINENUMBER 10 L3
    BIPUSH 10
    PUTSTATIC org/ywz/springbootdemo/HelloApp.b : I
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 0
}

例2:java代码

public class HelloApp {
    private int a = 1; 

    public static void main(String[] args) {
        int b = 2;
    }
}

可以发现当没有静态的变量时,字节码中也就没有了clinit类构造器方法。init方法其实就是构造器。

// class version 55.0 (55)
// access flags 0x21
public class org/ywz/springbootdemo/HelloApp {

  // compiled from: HelloApp.java

  // access flags 0x2
  private I a

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 4 L1
    ALOAD 0
    ICONST_1
    PUTFIELD org/ywz/springbootdemo/HelloApp.a : I
    RETURN
   L2
    LOCALVARIABLE this Lorg/ywz/springbootdemo/HelloApp; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
    // parameter  args
   L0
    LINENUMBER 7 L0
    ICONST_2
    ISTORE 1
   L1
    LINENUMBER 8 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    LOCALVARIABLE b I L1 L2 1
    MAXSTACK = 1
    MAXLOCALS = 2
}

例3:java代码

public class HelloApp {
    static class Father {
        public static int a = 1;

        static {
            a = 2;
        }
    }

    static class Son extends Father {
        public static int b = a;
    }

    public static void main(String[] args) {
        System.out.println(Son.b);
    }
}

可以发现在构造器中先初始化的父类然后再初始化子类。

// class version 55.0 (55)
// access flags 0x20
class org/ywz/springbootdemo/HelloApp$Son extends org/ywz/springbootdemo/HelloApp$Father {

  // compiled from: HelloApp.java
  NESTHOST org/ywz/springbootdemo/HelloApp
  // access flags 0x8
  static INNERCLASS org/ywz/springbootdemo/HelloApp$Son org/ywz/springbootdemo/HelloApp Son
  // access flags 0x8
  static INNERCLASS org/ywz/springbootdemo/HelloApp$Father org/ywz/springbootdemo/HelloApp Father

  // access flags 0x9
  public static I b

  // access flags 0x0
  <init>()V
   L0
    LINENUMBER 12 L0
    ALOAD 0
    INVOKESPECIAL org/ywz/springbootdemo/HelloApp$Father.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lorg/ywz/springbootdemo/HelloApp$Son; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 13 L0
    GETSTATIC org/ywz/springbootdemo/HelloApp$Son.a : I
    PUTSTATIC org/ywz/springbootdemo/HelloApp$Son.b : I
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 0
}

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: