jvm高级特性

一. class类文件

  1. 常量池:字面量,符号引用 -> 访问标志:这个标志用于识别一些类或者接口层次的访问信息 -> 类索引、父类索引和接口索引集合都按顺序排列在访问标志之后
  2.  描述符描述方法,字段,入参

例:int indexOf(char[]source,intsourceOffset,int sourceCount,char[]target,int targetOffset,int targetCount,intfromIndex)的描述符为“([CII[CIII)I”

3. 属性表 

  • Java程序方法体里面的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性内。在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量,局部变量表中也会预留出第一个变量槽位来存放对象实例的引用,所以实例方法参数值从1开始计算

4. 字节码

  • Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需的参数(称为操作数,Operand)构成
  • 指令:[类型]load:将变量从局部变量表加载到操作数栈,[类型]store:将变量从操作数栈加载到局部变量表。(_<助记符操作数>)
  • 方法调用指令
  • 同步指令:(1). 方法级同步:方法级的同步是隐式的,无须通过字节码指令来控制,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否被声明为同步方法。当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的管程将在异常抛到同步方法边界之外时自动释放。(2). 同步块monitorenter,monitorexit方法出异常无法处理执行monitorexit释放锁,异常表处理记录异常。

二. 类加载

  1. 初始化的情况:(1). 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段(2). 反射调用(3). 虚拟机执行指定主类(main)(4). jdk7如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化(5). 接口中加入了default修饰的方法,实现类初始化之前先初始化接口。(常量不会引发初始化)(子类初始化之前父类需要初始化,接口用到才初始化)
  2. 加载

获取类文件二进制字节流的过程不可控,用户可自定义类加载器,二进制字节流被虚拟机存储在方法区,实例化class对象作为类型数据的访问入口

     3. 验证:二进制流存入方法区之前进行文件格式验证,验证完存入方法区,之后的验证都在方法区中。元数据验证进行类语义验证。字节码验证类的方法体验证。符号引用验证。

     4. 准备:类中定义的变量分配内存(static零值,常量赋值)

     5. 解析;符号引用->直接引用

        类D要把类C的符号引用转化为直接引用,类D的类加载器通过C的全限定类名加载,进行加载到方法区需要元数据,字节码验证引出了其他类的加载。

    6. 初始化:初始化阶段是执行类构造器<clinit>的过程,编译器自动收集所有类变量的赋值动作和static块中语句合并产生的。静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。父类的<clinit>要先执行,所以父类的静态语句块要优于子类的变量赋值。(接口则不同使用时才会执行clinit初始化)而且clinit只执行一次,并发阻塞的其他线程即使被唤醒也不会执行<clinit>.

三.类加载器及双亲委派

  1. 启动类加载器c++实现是虚拟机的一部分,加载加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机的内存中。

四. 字节码执行引擎

  1. 栈桢:操作数栈,局部变量表,返回地址,动态链接(class字节码方法表的code属性中写入)
  2. 局部变量表:64位可能占两个变量槽,作为GCRoot变量槽被占用不会被回收
  3. 动态链接:字节码指令调用方法的入参就是常量池中方法的符号引用。
  4. 解析:class文件中存的是方法的符号引用,因此一部分在类加载解析过程中转化直接引用,一部分运行时动态转化,类加载过程中能够确定的是不可变的能确定版本的方法(主要有静态方法和私有方法两大类invokestatic,invokespecial,final)
  5. 分派:(1). 静态分派是重载的本质,编译期间静态类型可确定 (2).动态分派是重写的本质运行期间执行invokevirtual指令。

1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。

2)如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;不通过则返回java.lang.IllegalAccessError异常。

3)否则,按照继承关系从下往上依次对C的各个父类进行第二步的搜索和验证过程。

4)如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

Human human = new Man();这个过程首先执行new指令创建实例,实例存入局部变量表,然后压倒栈顶,执行invokeVirtual指令。栈顶第一个元素因为是子类对象所以重写会调用子类方法。new指令之后是dup指令把对象引用存入局部变量表,之后才是invokevirtual。

优化动态分派,类中会有虚方法表保存虚方法的实际入口地址,子类中包含父类所有的方法。连接阶段初始化虚方法表。

五. 解释器与编译器

解释执行逐行翻译字节码为机器码,编译的优势在于以方法为单位进行优化其中包含方法内联,逃逸分析等等优化操作。

  1. 当一个方法被调用虚拟机会先检查该方法有无本地编译完成的版本有则执行,无则统计方法调用计数器与循环回边之和是否超过方法调用计数器的阈值。超过阈值向即时编译器提交编译请求。

2. 方法内联

只有使用invokespecial指令调用的私有方法、实例构造器、父类方法和使用invokestatic指令调用的静态方法才会在编译期进行解析。编译器可确定进行方法内联即把被调用的方法代码直接放置调用方法内。虚方法内联则提供类型继承分析,用于确定在目前已加载的类中,某个接口是否有多于一种的实现、某个类是否存在子类、某个子类是否覆盖了父类的某个虚方法等信息。这样,编译器在进行内联时就会分不同情况采取不同的处理:如果是非虚方法,那么直接进行内联就可以了,这种的内联是有百分百安全保障的;如果遇到虚方法,则会向CHA查询此方法在当前程序状态下是否真的有多个目标版本可供选择,如果查询到只有一个版本,那就可以假设“应用程序的全貌就是现在运行的这个样子”来进行内联,这种内联被称为守护内联(Guarded Inlining)

3. 逃逸分析

分析对象动态作用域,当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,这种称为方法逃逸;甚至还有可能被外部线程访问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;从不逃逸、方法逃逸到线程逃逸,称为对象由低到高的不同逃逸程度。

  • 栈上分配:对于不会被外部方法或线程访问到的对象则会在栈上分配随方法结束销毁。
  • 标量替换:对象创建时确定其不被外部访问则拆散对象将其成员变量在栈帧中创建。
  • 同步消除:确定不会被外部线程访问,同步也可消除。

4.公共子表达式消除

5.数组边界检查消除

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章