Open Source, Open Future!
  menu
107 文章
ღゝ◡╹)ノ❤️

jvm 置顶!

image.png

源文件

package com.mncode.jvm.bytecode;

public class Test1 {
    private int a;

    public int inc() {
        return a + 1;
    }
}

编译

基本格式:javac <options> <source files>
例如:javac -g Test1.java

分析字节码工具

WinHex

image.png

HEX Editor Neo

javap

javap -verbose

public class com.mncode.jvm.bytecode.Test1

  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#18         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#19         // com/mncode/jvm/bytecode/Test1.a:I
   #3 = Class              #20            // com/mncode/jvm/bytecode/Test1
   #4 = Class              #21            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/mncode/jvm/bytecode/Test1;
  #14 = Utf8               inc
  #15 = Utf8               ()I
  #16 = Utf8               SourceFile
  #17 = Utf8               Test1.java
  #18 = NameAndType        #7:#8          // "<init>":()V
  #19 = NameAndType        #5:#6          // a:I
  #20 = Utf8               com/mncode/jvm/bytecode/Test1
  #21 = Utf8               java/lang/Object
{
  public com.mncode.jvm.bytecode.Test1();
    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/mncode/jvm/bytecode/Test1;

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/mncode/jvm/bytecode/Test1;
}
SourceFile: "Test1.java"

jclasslib

https://github.com/ingokegel/jclasslib

idea插件

jclasslib

image.png

分析字节码

整体结构

序号类型名称数量说明
1u4magic1魔数
2u2minjor_version1次版本号
3u2major_version1主版本号
4u2constant_pool_count1常量池数量
5cp_infoconstant_poolconstant_pool_count常量池
6u2access_flags1类访问权限
7u2this_class1类名
8u2super_class1父类名
9u2interfaces_count1接口数量
10u2interfacesinterfaces_count接口名
11u2fields_count1字段数量
12field_infofieldsfields_count字段表
13u2methods_count1方法数量
14method_infomethodsmethods_count方法表
15u2attributes_count1附加属性数量
16attribute_infoattributesattributes_count附加属性表

image.png

1、魔数:cafebabe
2、次版本号:0
3、主版本号:对应十进制为52,对应 JDK为1.8
4、常量池数量:22,注意常量池计数是从1开始的(0有特殊意义),所以常量池中的数量应为21个;
5、常量池:以上红框圈中的即是常量池;

   #1 = Methodref          #4.#18         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#19         // com/mncode/jvm/bytecode/Test1.a:I
   #3 = Class              #20            // com/mncode/jvm/bytecode/Test1
   #4 = Class              #21            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/mncode/jvm/bytecode/Test1;
  #14 = Utf8               inc
  #15 = Utf8               ()I
  #16 = Utf8               SourceFile
  #17 = Utf8               Test1.java
  #18 = NameAndType        #7:#8          // "<init>":()V
  #19 = NameAndType        #5:#6          // a:I
  #20 = Utf8               com/mncode/jvm/bytecode/Test1
  #21 = Utf8               java/lang/Object

6、类访问权限:0021,对应:ACC_PUBLICACC_SUPER
7、类名:对应3号常量,3号又引用了20号,所以为:com/mncode/jvm/bytecode/Test1
8、父类名:java/lang/Object
9、接口数量:0
10、接口名:无
11、字段数量:1

12、字段表:

字段表结构

序号类型名称数量说明
1u2access_flags1访问权限
2u2name_index1字段名
3u2descriptor_index1字段描述符
4u2attributes_count1属性数量
5attributes_infoattributesattributes_count属性表

0002 0005 0006 0000
访问权限:private
字段名:a
字段描述符:I(即int类型)
属性数量:0
13、方法数量:2

14、方法表:

方法表结构

序号类型名称数量说明
1u2access_flags1访问权限
2u2name_index1字段名
3u2descriptor_index1字段描述符
4u2attributes_count1属性数量
5attributes_infoattributesattributes_count属性表

对应以下两个方法:

  public com.mncode.jvm.bytecode.Test1();

    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/mncode/jvm/bytecode/Test1;

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/mncode/jvm/bytecode/Test1;

15、 附加属性数量:1

16、 附加属性表:

附加属性表结构

序号类型名称数量说明
1u2attribute_name_index1属性名
2u4attribute_length1属性值长度
3u1infoattribute_length属性值

0010 00000002 0011
attribute_name_index:16号常量为SourceFile
attribute_length:2(即后面的两个字节存储属性值)
info:17号常量为Test1.java
表示这个class文件是从Test1.java编译来的。

类加载器

作用

加载class文件字节码,在运行时数据区中生成一个代表这个类的Class对象。

类的生命周期

image.png

种类

启动类加载器:负责加载<JAVA_HOME>\lib目录下或者参数-Xbootclasspath指定路径下的类库(如rt.jar);
扩展类加载器:负责加载<JAVA_HOME>\lib\ext目录下或者系统变量java.ext.dirs指定路径下的类库;
应用类加载器:负责加载用户类路径(ClassPath)下的类库,默认的类加载器;
自定义类加载器:可以通过继承ClassLoader来实现;

双亲委派模型

image.png

引入时间:JDK1.2

说明:
当一个类加载器收到类加载请求时,它不会先自己去加载类,而是把请求委托给父类加载器,请求依次向上传递;当父类加载器无法加载时(在负责的范围内没有找到指定类),子加载器才会自顶向下依次尝试加载类。

好处:

  • 避免同一个类被多次加载;
  • 每个加载器只能加载自己范围内的类;

源码(ClassLoader类的loadClass方法):

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 检查类是否已经被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        // 委托给父加载器加载
                        c = parent.loadClass(name, false);
                    } else {
                        // 父加载器为空,调用启动类加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    long t1 = System.nanoTime();
                    // 若还没找到,则调用findClass方法查找
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

ClassLoader

主要方法:loadClass,findClassdefineClass
loadClass:通过类的全限定名完成类的加载,返回Class对象,内部实现了双亲委派的逻辑,若要破坏双亲委派机制,可以重写这个方法;
findClass:自定义类加载器时,一般只重写这个方法即可;loadClass内部会调用这个方法;
源码:

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

defineClass:将class文件字节数组转换为Class对象;

    protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(name, b, off, len, null);
    }

自定义类加载器

代码 1:

package com.mncode.jvm.classloader;

public class MyClassLoader extends ClassLoader {

    private final String POSTFIX = ".class";
    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        byte[] classBytes = getClassBytes(className);
        if (classBytes == null) {
            throw new ClassNotFoundException(className);
        }
        return defineClass(className, classBytes, 0, classBytes.length);
    }

    /**
     * 获取class字节数组(可以通过文件、网络等方式获取)
     *
     * @param className 类全限定名
     * @return class字节数组
     */
    private byte[] getClassBytes(String className) {
        Path path = Paths.get(classPath, className.replace('.', File.separatorChar) + POSTFIX);
        try {
            return Files.readAllBytes(path);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

代码 2:

public class Hello {
}

测试代码:

public class MyClassLoaderTest {

    public static void main(String[] args) throws Exception {
        // 自定义类加载器
        MyClassLoader classLoader = new MyClassLoader("F:\\classes");
        // 根据全限定名加载指定类
        Class c = classLoader.loadClass("com.mncode.jvm.classloader.Hello");
        System.out.println(c.getClassLoader());
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}

测试结果:

image.png

备注:
如果开发工具自动做了编译,如下图所示:

image.png

则测试结果为:

image.png

因为根据双亲委派机制可知,类加载请求会传递给父加载器,而应用类加载器会在类路径下找到文件,所以最终由应用类加载器加载成功;把这个文件删掉,才会是自定义的类加载器去加载。

运行时数据区

基本结构

image.png

简要说明

程序计数器:当前线程所执行的字节码的行号指示器(线程私有);
虚拟机栈:java方法执行的内存模型(线程私有);
本地方法栈:类似虚拟机栈,不同的是为native方法服务(线程私有);
堆:存储对象实例的区域(线程共享);
方法区:存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据(线程共享)。

垃圾回收

回收什么?

回收已经死掉的对象(没有任何引用指向的对象)。

怎么知道哪些对象已死?

方法有以下两种:

  • 引用计数算法
简介对象中添加一个引用计数器;对象每被引用一次,计数器加 1;引用每取消一次,计数器减 1 ;当计数器为 0 时,表示对象死掉
优点实现简单,判定效率高
缺点当对象之间互相引用时,即使这些对象已经没用了,计数器不为 0,所以不能被回收
  • 可达性分析算法
简介以一系列的 "GC Roots"对象为起点,开始向下搜索,节点走过的路径称为引用链,当一个对象与 "GC Roots" 之间没有任何引用链相连时,表示对象死掉
优点可以解决对象互相引用的问题

可作为GC Roots的对象包括下面几种:

  • 虚拟机栈中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNINative方法)引用的对象。

回收方法区

主要回收两方面的内容:
一. 废弃常量
      判定条件:没有地方引用这个常量。

二. 无用的类
      判定条件:
        1.该类的所有实例都被回收;
        2.加载该类的ClassLoader已被回收;
        3.该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

垃圾收集算法

  • 标记 - 清除算法(Mark-Sweep
最基础的收集算法,分为标记和清除两个阶段。

流程:
  1.先标记出所有需要回收的对象;
  2.清除被标记的对象。

缺点:
  1.效率问题,标记和清除的效率都不高;
  2.空间问题,会产生大量不连续的内存碎片。
  • 复制算法(Copying
流程:
  1.将可用内存划分为大小相等的两块,每次只使用其中的一块;
  2.当其中一块内存用完后,就将还活着的对象复制到另外一块上;
  3.把刚才的那块内存清空。

优点:
  1.不会产生内存碎片;
  2.对象存活率较低时需要的复制操作少,效率高。

缺点:
  1.可用内存只有一半;
  2.对象存活率较高时需要的复制操作多,效率低。

说明:
  1.新生代回收一般用这种算法。
  2.因为新生代中的对象98%是朝生夕死的,所以并不需要按照1:1的
    比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块
    较小的Survivor空间,每次使用Eden和其中一块Survivor。
  3.当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外
    一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。
  4.HotSpot虚拟机默认Eden和Survivor的大小比例是 8:1
  • 标记 - 整理算法(Mark-Compact
流程:
  1.先标记出所有需要回收的对象;
  2.所有存活的对象都向一端移动;
  3.清掉剩下的内存。

优点:
  1.不会产生内存碎片;
  2.不需要像复制算法那样浪费一半的空间。

缺点:
  1.整理阶段的效率比较低。

说明:
  1.老年代回收一般用这种算法。
  • 分代收集算法
1.当前商业虚拟机的垃圾收集都采用分代收集算法。
2.这种算法没什么新的思想,只是根据对象存活周期的不同将内存划分为几块。
3.一般把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的算法。
4.新生代中,每次回收只有少量对象存活,采用复制算法,效率高。
5.老年代中,对象存活率高、没有额外空间对它进行分配担保,
  就要用标记—清除或者标记—整理算法来进行回收。

垃圾收集器

垃圾收集器

执行引擎

简介

物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统上的,而虚拟机的执行引擎则是由自己实现的,所以可以自行制定指令集与执行引擎的结构体系。

栈帧

栈帧存储了方法的局部变量、操作数栈、动态连接和方法返回地址等信息,每一个方法从调用到执行完成的过程,都对应者一个栈帧在虚拟机栈中从入栈到出栈的过程。每个栈帧需要分配多少内存,代码编译后就已经确定,不受运行期的影响。

结构如下:

image.png

说明:

  • 局部变量表:存储方法参数和内部定义的局部变量,单位为:Slot,最大容量由Code属性中的locals可知;
  • 操作数栈:在方法执行的过程中,会有各种字节码指令往栈中写入和读取数据,栈最大深度由Code属性中的stack可知;

执行过程

开头处的源码 inc:

public int inc() { 
   return a + 1; 
}

编译后对应内容:

  public int inc();

    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/mncode/jvm/bytecode/Test1;
}

说明:

  1. 操作数栈最大深度2,局部变量表中变量数量为1
  2. aload_0:将局部变量表第0个slot中的引用类型变量复制到栈顶;
  3. getfield:获取指定类的实例域,并将其值压入栈顶;
  4. iconst_1: 将int型1推送至栈顶;
  5. iadd: 将栈顶两个数相加,并将结果推送至栈顶;
  6. ireturn:方法执行结束,并将栈顶结果返回;

测试代码:

  Test1 obj = new Test1();
  obj.inc();

inc方法执行流程如下(内部各个组件的变化情况):

image.png

参数

HotSpot虚拟机主要参数表

相关链接