文章目录
a、线程共享:所有线程能访问这块内存数据,随虛拟机或者GC而创建和销毁
b、线程独占:每个线程都会有它独立的空间,随线程生命周期而创建和销毁
3、使用javap命令将解析结果说明放到Demo1.txt文件中
一、前言
- 重点
- 线程安全概念、线程通信的方式及应用、reactor线程模型、 关于线程数量的优化、JDK的常用命令、Netty框架的作用
- 难点
- JAVA程序运行的原理、同步关键字的原理、AQS的抽象、JUC的源码、网络编程的概念、理解GC机制
- 技巧
- 通过写一段申请内存C代码,理解JVM内存和运行程序的概念;
- 将所学内容前后串连起来,形成java的知识体系;
- 跟着操作,然后自己做笔记;
- 尝试将内容和其他人沟通;
二、class文件内容
class文件包含JAVA程序执行的字节码;数据严格按照格式紧凑排列在class文件中的二进制流,中间无任何分隔符;文件开头有一个0xcafebabe(16进制)特殊的一个标志。
下图展示为16进制
他包含了
- 版本
- 访问标志
- 常量池
- 当前类
- 超级类
- 接口
- 字段
- 方法
- 属性
这文件是有复杂格式,专门给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能够实现“一处编写,处处运行”的原因。
我们开发人员理解到这个层次,就足够学习掌握后面的多线程课程了
如果有问题,也可以留言给我