- 虚拟的虚拟机,虚拟虚拟机(笑)
-
java程序通过命令行进行实现,通过传入的参数对之后程序的运行状态进行判断,例如
指令 说明 -version 输出版本信息,然后退出 -?/-help 输出帮助信息,然后退出 -cp/-classpath 指定用户类路径 -Dproperty=value 设置java系统属性 -Xms 设置堆空间大小, -Xmx 设置最大堆空间大小 -Xss 设置线程栈空间大小
-
在运行用户类路径的用户代码之前,需要进行类加载,所以会按先后顺序搜索下列三个路径的class文件并加载。
-
启动类路径 bootstrap (默认在jre\lib中)
-
扩展类路径 extension (默认在jre\lib\ext)
-
用户类路径 user (默认为当前连接".",可以使用-cp类追踪更改)
-
-cp中使用分隔符加载多个用户类路径,Windows中使用";"分割,Unix中使用":"分割
-
实现方式 :
- Entry接口,定义实体接口用于传入的calss路径参数,并通过工厂方法根据参数类型返回需要用到的具体实例
- CompositeEntry:传入的类路径参数有多个被(;或:)进行分割,则返回该类
- WildcardEntry:传入的类路径为最后带有*号的泛指
- ZipEntry:传入的参数用于解析Zip或jar包文件
- DirEntry:传入的类路径参数为一个类的普通参数
- Classpath:用于使用-Xjre命令行参数解析启动类和扩展类路径,使用-cp来加载解析用户类路径
- class文件以字节码的形式进行解析,其中主要有uint1,uint2,uint4三种字节长度的数据类型,但需要注意的是Long和Double类型占有8个字节(两个Uint4类型数据),需要特别注意
- 魔数:文件中字节码的首部定义,无实际意义,作为标识 class字节码的魔数为0xcafebabe
- 版本号:分为次版本和主版本,次版本现在以无意义,主版本依照java8的规格使用
- 常量池:版本号之后为常量池数据,首部为常量池长度,之后为具体常量实现了13种常量,类class,double,field字段,float,interface,long,methodRef,methodType,nameAndType,utf8字符串,String常量,invoke,methodHandle
- 类访问标志位:之后为类访问标识符,用于标识该该class文件为类还是接口,访问级别为public还是其他等
- 本类名和父类名:之后两个u2类型分别代表本类类名和父类类名的常量池索引。
- 接口索引:接口索引类型首先为一个u2类型表示基础的接口数,这后为一个个的u2类型的常量池索引
- 字段和方法表:之后为字段表,方法表,两者基本相同,以一个索引数组存储数据,有访问表示符,名称的常量池索引,描述的常量池索引,属性表,其中属性表也为数组索引存储
- 属性表:和常量池类似,实现了15种具体属性,其中有为定义属性,SyntheticAttribute类未实现。
- 总结 :解析class文件时debug调试过很多次,解析其中的所有2进制数据,其类的所有信息都按照jvm虚拟机规范有序而繁复的排列,解析class文件的过程,就是将其存储在2进制文件中的内容一步步的拆分,并使用合理的存储结构进行存储和解析,以便之后为进行运算提供数据支持。
- 进程占有(线程共享):主要存放类数据和类实例(对象),对象数据存放在heap堆(gc堆)中,类数据存放在方法区中,其中类数据包括字段和方法信息,方法的字节码,运行时常量池。
- 线程占有:主要用于辅助java字节码的运行,主要包括PC寄存器和java虚拟机栈。栈主要由栈帧组成,其中主要存储方法执行的状态,局部变量表和操作数栈。若为native本地方法,则不在jvm虚拟机中执行
- 数据类型:基本类型和引用类型,基本类型包括布尔类型和数字类型,数字类型又有整型和浮点型。引用类型按引用对象分为,类类型,接口类型和数组类型。另外还有一个特殊的null类型。
- 线程中主要实现了栈信息,而栈信息主要包含有栈帧,栈帧由局部变量表和操作数栈组成
- 线程->虚拟机栈->栈帧->本地变量表+操作数栈
-
指令集占一个字节,最多有256个,java实现了205个,分为11个大类,常量(constants)指令,加载(loads)指令,存储(store)指令,操作数栈(stack)指令,数学(math)指令,转换(conversions)指令,比较(comparisons)指令,控制(control)指令,引用(references)指令,扩展(extended)指令,保留(reserved)指令这11类指令集 其中保留指令有三挑,为202(0xca)-breakPoint,和254,256,impdep1/impdep2用来进行虚拟机内部操作的指令,保留指令不允许出现在class文件中
-
constants:将常量数据直接推入到操作数栈中的操作,bipush和sipush分别将byte或short类型从操作数中获得数据并转为int存入到操作数栈中
-
loads:加载操作,根据特点索引从局部变量表中获得特点的数据并存入到操作数栈中
-
stores:存储操作,和load操作相反,从操作数栈的栈顶或得特点的元素并根据特点的索引放入到局部变量表中,long和double占用两个索引
-
stack:栈操作指令,直接对操作数栈进行操作,有add,sub,div,ml,shift,negate等对栈的直接操作
-
math:对操作数栈顶的元素进行数学运算的指令
-
conversion:强制转换指令,将基本数据类型之间进行强制转换
-
comparison:比较指令,多为跳转指令,若满足条件则执行或执行跳转指令的操作
-
extended:扩展指令,包括对局部变量表过大的读取位数扩展,goto_w语句和ifnull语句等
-
control:控制指令,gote和两个switch-case指令的实现
-
实现细节:从ClassFile类中的方法字段中,所包含的code属性字段中,找到main方法的字段,通过此来穿件一个线程和对应数目的栈帧,通过指令对栈帧的pc值,局部变量表和操作数栈(栈顶)进行操作,并一步步的完成运算操作。
- 堆也是存在于运行时数据区的,其中方法区也可以在逻辑上的分到堆上进行操作
- 方法区:在物理分区中,方法区是一个单独的空间,不过在逻辑上,方法区的功能基本都是提供给了堆。方法区中存储了关于类信息,将class文件中的读取到的数据进行类相关的存储,包括字段,方法,静态变量等,及类相关等等。
- 运行时常量池区域:属于方法区。使用运行时产量池类对传入的产量池数据金进一步的分析和解析,将其解析为运行时产量池数据,直接在运行时产量池中拿到数据,在需要直接从运行时常量池拿到数据时提供了便捷。ldc,get_static等字节指令,需要从运行时常量池中拿到数据。在需要用到该类时i,进行动态加载,需要使用才会将类信息从方法区加载到运行时常量池,同时也会多方法区进行垃圾回收,以便保持程序内存大小。
- 在使用时进行方法属性的加载,需要在继承关系中寻找,若找不到,则再去接口关系中去寻找,非接口方法的调用类似,在接口及其相关父接口中去查找其中的方法
- 对于方法相关指令,需要先拿到方法相关的数据并将这个新的栈帧推入到线程的栈中进行计算。对于实例方法,会在参数传递的最后传入属于本实例的对象引用。静态方法则直接进行数值改变,并会反映到创建的该类上。
- 对方法的传入参数和返回类型进行封装,并将该数据在需要时进行动态加载
- 方法返回:有常用的5种类型返回值的返回,将当前栈元素的最上层元素推入到栈顶帧中,即将返回值给到调用者。空值的return指令直接将当前栈帧元素推出即可。
- 方法调用:invoke指令用于调用方法等数据,在进行调用前先判断该类是否已经被初始化(加载),先进行类加载操作,之后进行方法的调用。一个方法的使用就是一个栈帧,将方法中需要用到的参数传递入栈帧中的局部变量中,以便进行之后的操作。
- 进行方法调用,先将本地native方法跳过。
- 在Class通用类中创建数组相关信息,通过数组方法能够创建数组和寻找到相关类信息
- 编写创建数组的相关指令,数组在jvm中也以对象的形式存在,借助数组将数组对象创建并直接推入到操作数栈中