高性能编程学习(第一章第一节:Java基础) 一、JVM运行的核心逻辑详细剖析

文章目录

一、前言

二、class文件内容

三、JVM运行时数据区

a、线程共享:所有线程能访问这块内存数据,随虛拟机或者GC而创建和销毁

b、线程独占:每个线程都会有它独立的空间,随线程生命周期而创建和销毁

四、使用javap命令查看class文件的内容

1、新建Demo1类

2、使用javac命令将类文件编译成class文件

3、使用javap命令将解析结果说明放到Demo1.txt文件中

4、版本信息

5、Constant pool常量池

6、熟悉的构造函数

7、程序入口main方法

8、分析程序是怎么完整运行的

五、总结

一、前言

  • 重点
  1. 线程安全概念、线程通信的方式及应用、reactor线程模型、 关于线程数量的优化、JDK的常用命令、Netty框架的作用
  • 难点
  1. JAVA程序运行的原理、同步关键字的原理、AQS的抽象、JUC的源码、网络编程的概念、理解GC机制
  • 技巧
  1. 通过写一段申请内存C代码,理解JVM内存和运行程序的概念;
  2. 将所学内容前后串连起来,形成java的知识体系;
  3. 跟着操作,然后自己做笔记;
  4. 尝试将内容和其他人沟通;

 

二、class文件内容

class文件包含JAVA程序执行的字节码;数据严格按照格式紧凑排列在class文件中的二进制流,中间无任何分隔符;文件开头有一个0xcafebabe(16进制)特殊的一个标志。

下图展示为16进制

 

 

他包含了

  1. 版本
  2. 访问标志
  3. 常量池
  4. 当前类
  5. 超级类
  6. 接口
  7. 字段
  8. 方法
  9. 属性

这文件是有复杂格式,专门给JVM读里面的内容,人类阅读可以借助工具查看;

 

三、JVM运行时数据区

JVM运行时会为class字节码文件分配对应区域去存储信息,而这片区域又分很多的数据区域数据区域分为线程共享部分和线程独占部分

a、线程共享:所有线程能访问这块内存数据,随虛拟机或者GC而创建和销毁

1、方法区

方法区:JVM用来存储加载的类信息、常量、静态变量、编译后的代码等数据虚拟机规范中这是一一个逻辑区划(逻辑区划:就 是没有硬性去规定你怎么实现,具体实现根据不同虚拟机来实现)

如: oracle的HotSpot在java7中 方法区放在永久代,java8放在元数据空间,并且通过GC机制对这个区域进行管理

2、堆内存

堆内存:堆内存可以细分为:老年代 、新生代(Eden、 From Survivor、To Survivor),JVM启动时创建,存放对象的实例。垃圾回收器主要就是管理堆内存。如果满了,就会出现OutOfMemroyError,后续在内存模型中,详细讲解。

b、线程独占:每个线程都会有它独立的空间,随线程生命周期而创建和销毁

1、虚拟机栈

虚拟机栈:

  • 每个线程都在这个空间有一个私有的空间。线程栈由多个栈帧(Stack Frame)组成。

  • 一个线程会执行一个或多个方法,一个方法对应一个栈帧

  • 栈帧内容包含:局部变量表、操作数栈、动态链接、方法返回地址、附加信息等。

  • 栈内存默认最大是1M,超出则抛出StackOverflowError

2、本地方法栈

本地方法栈:

  • 和虚拟机栈功能类似,虚拟机栈是为虚拟机执行JAVA方法而准备的,本地方法栈是为虚拟机使用Native本地方法而准备的。

  • 虚拟机规范没有规定具体的实现,由不同的虚拟机厂商去实现。

  • HotSpot虚拟机中虚拟机栈和本地方法栈的实现式一-样的。同样,超出大小以后也会拋出StackOverflowError

3、程序计数器

  • 程序计数器:程序计数器(Program Counter Register)记录当前线程执行字节码的位置,存储的是字节码指令地址,如果执行Native方法,则计数器值为空。

  • 每个线程都在这个空间有一个私有的空间,占用内存空间很少。

  • CPU同- -时间,只会执行一条线程中的指令。 JVM多线程会轮流切换并分配CPU执行时间的方式。为了线程切换后,

  • 需要通过程序计数器,来恢复正确的执行位置。

 

四、使用javap命令查看class文件的内容

1、新建Demo1类

public class Demo1{
    public static void main(String[] args){
        int x = 500;
        int y = 100;
        int a = x / y;
        int b = 50;
        System.out.println(a + b);
    }
}

2、使用javac命令将类文件编译成class文件

javac Demo1.java

3、使用javap命令将解析结果说明放到Demo1.txt文件中

javap -v Demo1.class>Demo1.txt

下面开始查看Demo1编译成的class文件包含了什么内容

Demo1.txt内容如下:

Classfile /ideaWorkspace/git/subject-1/subject-1-docs/Demo1.class
  Last modified 2019-8-30; size 414 bytes
  MD5 checksum ae6fa820973681b35609c75631cb255b
  Compiled from "Demo1.java"
public class Demo1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
   #4 = Class              #19            // Demo1
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               Demo1.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #21            // java/lang/System
  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #17 = Class              #24            // java/io/PrintStream
  #18 = NameAndType        #25:#26        // println:(I)V
  #19 = Utf8               Demo1
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V
{
  public Demo1();
    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 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=5, args_size=1
         0: sipush        500
         3: istore_1
         4: bipush        100
         6: istore_2
         7: iload_1
         8: iload_2
         9: idiv
        10: istore_3
        11: bipush        50
        13: istore        4
        15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        18: iload_3
        19: iload         4
        21: iadd
        22: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        25: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 7
        line 6: 11
        line 7: 15
        line 8: 25
}
SourceFile: "Demo1.java"

4、版本信息

public class Demo1 minor version: 0 //次版本号 
major version: 52 //主版本号 
flags: ACC_PUBLIC, ACC_SUPER //访问标志

版本号的规则:JDK5、6、7、8分别对应49、50、51、52 如上面52对应的就是JDK8

flags 访问标志:类的修饰符

标志的名称对应含义如下图

 

5、Constant pool常量池

Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
   #4 = Class              #19            // Demo1
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               Demo1.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #21            // java/lang/System
  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #17 = Class              #24            // java/io/PrintStream
  #18 = NameAndType        #25:#26        // println:(I)V
  #19 = Utf8               Demo1
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V

这里面存的是类信息包含的静态常量,编译之后就能确认

常量名称对应的含义如下图

6、熟悉的构造函数

我并没有写构造函数,由此可见,没有定义构造函数,会有隐式的无参构造函数

public Demo1();
    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 1: 0

7、程序入口main方法

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC //描述了方法的访问控制 ACC_PUBLIC 公共的,ACC_STATIC 静态的
    Code:
      stack=3, locals=5, args_size=1//描述了locals本地变量的数量、args_size参数数量、stack方法对应栈帧中操作数栈的深度
         0: sipush        500  
         3: istore_1
         4: bipush        100
         6: istore_2
         7: iload_1
         8: iload_2
         9: idiv
        10: istore_3
        11: bipush        50
        13: istore        4
        15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        18: iload_3
        19: iload         4
        21: iadd
        22: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        25: return
flags:ACC_PUBLIC, ACC_STATIC

描述了方法的访问控制

Code:stack=3, locals=5, args_size=1

描述了locals本地变量的数量(args、x、y、a、b)、args_size参数数量(args)、stack方法对应栈帧中操作数栈的深度

而后面的这些都是JVM执行弓|擎去执行这些源码编译过后的指令码。javap翻译 出来是操作符,class文件内存储的是指令码。前面的数字,是偏移量(字节),jvm根据这个去,区分不同的指令。详情看资料包"JVM指令码表"

8、分析程序是怎么完整运行的

a、第一步的话就就是编译,编译之后会把类信息加载到方法区

b、第二步的话就是要运行,就是JVM会创建线程来执行代码,在虚拟机栈、程序计数器内存区域中创建线程独占的空间,这里的话不涉及带本地方法栈,因为代码都是Java代码

 

 

 

c、第三步分析字节码指令这里配合查找JVM指令码表理解就行了

这里要注意的是方法之间的调用指令是有绑定关系的,如下:

invokevirtual(运行时方法绑定调用方法)#3 指令剖析:

新方法调用,jvm会根据这个方法的描述,创建一个新的栈帧,方法的参数从操作数栈中弹出来(可以理解成把调用操作数栈的值当做变量给传进了被调用的操作数栈中),然后程序技术器归零重新开始计算,执行完println方法后又回到main方法栈帧中

d、最后执行return指令,void函数返回

 

 

五、总结

这一章,将JVM运行的核心逻辑进行了详细剖析。

注: JVM运行原理中更底层实现,针对不同的操作系统或者处理器,会有不同的实现。

这也是JAVA能够实现“一处编写,处处运行”的原因。

我们开发人员理解到这个层次,就足够学习掌握后面的多线程课程了

如果有问题,也可以留言给我

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值