1.jvm基础知识

  1.什么是 jvm ?  

    JVM全称Java Virtual Machine,翻译过来也就是Java虚拟机。
JVM的实际功能也是和翻译一样,类似于一台虚拟操作系统,是通过在实际的计算机上模拟各种计算功能来实现的。JVM有着自己完善的硬件架构,例如处理器、堆栈、寄存器等,还具有相应的指令系统。
    JVM是JRE(Java Runtime Environment)的一部分,安装了JRE其实也就是相当于安装了JVM,可以运行Java程序了。

 

  

  2.常见的 jvm

HotSpot JVM
  

  HotSpot VM是绝对的主流。大家用它的时候很可能就没想过还有别的选择,或者是为了迁就依赖了Oracle/Sun JDK某些具体实现的烂代码而选择用HotSpot VM省点心。
Oracle / Sun JDK、OpenJDK的各种变种(例如IcedTea、Zulu),用的都是相同核心的HotSpot VM。当大家说起“Java性能如何如何”、“Java有多少种GC”、“JVM如何调优”云云,经常默认说的就是特指HotSpot VM。可见其“主流性”。
  JDK8的HotSpot VM已经是以前的HotSpot VM与JRockit VM的合并版,也就是传说中的“HotRockit”,只是产品里名字还是叫HotSpot VM。这个合并并不是要把JRockit的部分代码插进HotSpot里,而是把前者一些有价值的功能在后者里重新实现一遍。移除PermGen、Java Flight Recorder、jcmd等都属于合并项目的一部分
  不过要留意的是,这里的HotSpot VM特指“正常配置”版,而不包括“Zero / Shark”版。Wikipedia那个页面上把后者称为“Zero Port”。用这个版本的人应该相当少,很多时候它的release版都build不成功

 
J9 JVM

  J9是IBM开发的一个高度模块化的JVM。在许多平台上,IBM J9 VM都只能跟IBM产品一起使用。这不是技术限制,而是许可证限制。例如说在Windows上IBM JDK不是免费公开的,而是要跟IBM其它产品一起捆绑发布的;
  使用IBM Rational、IBM WebSphere的话都有机会用到J9 VM(也可以自己选择配置使用别的Java SE JVM)。根据许可证,这种捆绑在产品里的J9 VM不应该用于运行别的Java程序…大家有没有自己“偷偷的”拿来跑别的程序IBM也没力气管
  咳咳而在一些IBM的硬件平台上,很少客户是只买硬件不买配套软件的,IBM给一整套解决方案,里面可能就包括了IBM JDK。这样自然而然就用上了J9 VM。所以J9 VM得算在主流里,虽然很少是大家主动选择的首选。
  J9 VM的性能水平大致跟HotSpot VM是一个档次的。有时HotSpot快些,有时J9快些。
不过J9 VM有一些HotSpot VM在JDK8还不支持的功能,最显著的一个就是J9支持AOT编译和更强大的class data sharing,JRockitJRockit以前Java SE的主流JVM中还有JRockit,跟HotSpot与J9一起并称三大主流JVM。这三家的性能水平基本都在一个水平上,竞争很激烈。
  自从Oracle把BEA和Sun都收购了之后,Java SE JVM只能二选一,JRockit就炮灰了。JRockit最后发布的大版本是R28,只到JDK6;原本在开发中的R29及JDK7的对应功能都没来得及完成项目就被终止了。

 

2.ClassFileFormat

  

3.类加载器-类的初始化

 

 

  1.加载过程

 

 

  2.类的初始化: 

在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。

  在程序中,静态变量的初始化有两种途径:

  1.在静态变量的声明处进行初始化;

  2.在静态代码块中进行初始化。

  没有经过显式初始化的静态变量将原有的值。

package demo01day;


public class Initialization {

public static void main(String[] args) {
System.out.println(T.count);
}

}
class T {
public static int count = 2;//0
public static T t = new T();//null

private T(){
count++;
}
}

可见将生成对象的语句放在两个位置,输出是不一样的(相应位置的输出已在程序注释中标明)。

  这是因为初始化语句是按照顺序来执行的。

  静态变量的声明语句,以及静态代码块都被看做类的初始化语句,Java虚拟机会按照初始化语句在类文件中的先后顺序来依次执行它们。

 

 3.类的初始化步骤

    1.loading读取加载

        1.双亲委派机制,主要是处于安全角度考虑

        2.lazyLoading五种情况

·          -new getstatic putstatic invokestatic指令,访问final变量除外

           -Java.lang.reflect对类进行反射调用时

           -初始化子类的时候,父类首先初始化

           -虚拟机启动时,被执行的主类必须初始化

           -动态语言支持java.lang.invoke.MethodHandle解析的结果为REF getstatic REF putstatic

          REF inv okestatic的方法句柄时,该类必须初始化

        3.ClassLoader源码

          1. findInCAche -> parent.loaderClass - > findClass()

        4.自定义类加载器

          1.extends ClassLoader

          2.overwrite findClass() -> defineClass(byte[] -> Class clazz)

          3.加密

        5.混合执行  编译执行 解释执行

          1.监测特点代码: -XX:CompileThreshold = 10000

     2.Linking初始化

        1.Verification验证

          验证文件是否符合JVM规定:

  1. 文件格式的验证
  2. 元数据的验证
  3. 字节码的验证
  4. 符号引用的验证

        2.preparation准备 

    ·      为类的静态变量分配内存,并将其初始化为默认值

        3.Resolution解析

          把类中的符号引用转换为直接引用

     3.initializing

        执行 <clinit> ,对类的静态代码块,静态变量执行初始化操作,给静态成员变量赋初始值

对于我们开发人员,我认为应该具体了解一下初始化阶段什么时候在开始。JVM规范对此做了严格规范,有且只有以下5种情况必须对类进行初始化:

  1. 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类没有被初始化过,就需要先进行初始化。对于字节码指令不了解的同学,可能就是一脸蒙圈了。我们来说人话,就是:使用new关键字实例化对象的时候、读取和设置一个类的静态字段(不被final修饰的)和调用一个类的静态方法的时候。这样说更容易被理解一些。

  2. 使用java.lang.reflect包中的方法对类进行反射调用的时候,如果类没有被初始化过,就需要先进行初始化。

  3. 当初始化一个类的时候,如果发现它的父类还没有被初始化过,就需要先初始化它的父类。

  4. JVM会先初始化要执行的主类,也是包含main()方法的那个类。

  5. 当使用JDK 1.7的动态语言支持时,如果java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic(使用MethodHandle读取类的静态字段)、REF_putStatic(使用MethodHandle设置类的静态字段)、REF_invokeStatic(使用MethodHandle调用类的静态方法)的方法句柄时,如果这个方法句柄没有被初始化过,就需要先进行初始化。

 

    注意事项:

      

接口也有初始化过程,和类是一致的。不过接口中不能使用“static{}”语句块,但编译器仍然会为接口生成“clinit()”类构造器,用于初始化接口中所定义的成员变量。

接口初始化的时机,基本和之前提到的类的5种情况基本一致,唯一不一样的是第3种情况:在一个类被初始化时,它的父类也必须被初始化,但是一个接口被初始化时,它的父接口并不要求被初始化。只有在真正使用到父接口时才会被初始化,比如:引用父接口中定义的常量。

 

4.对象创建过程:

      1.class loading

   2. class linking(verification,preparation,resolution)                                                                                                                                                                                                                  

   3.class initializing

   4.申请对象内存

   5.成员变量赋默认值

   6.调用构造方法<init>

    (1)成员变量顺序赋初始值

    (2)执行构造方法语句  VB

对象在内存中的存储布局

  普通对象

      1. 对象头:markword  8

      2.ClassPoint指针:-XX:+UserCompressedOops为4字节  不开启为8字节

      3.实例数据

        1.引用类型:-XX+UseCompressedOops为 4 字节  不开启 8 字节 

        2.Okps Ordinary Object Promters

      4.Padding对齐,8 的倍数

    数组对象

      数组对象只是比普通对象多一个数组的长度罢了,多了 一个数组长度:4字节

 

  对象头:

jvm对象头信息是与对象自身定义的数据无关的额外存储的信息,由于它存在于对象中,jvm规范中安装对象类型,分两种类型:

  • 普通对象包含:Mark Word、元数据指针(Klass Pointer)
  • 数组对象包含:Mark Word、元数据指针(Klass Pointer)、Array Length

    

32位JVM-->Mark Word

mark word里存放的是对象运行时的信息,不同状态的对象里mark word 存放的信息是不同的,如下:

存储内容(30bit) 锁状态

(2bit)identify_hashcode:25|age:4|biased_lock:1 (01)无锁

threadId:23 | age:4 | epoch:2 | biased_lock:1 (01)偏向锁

ptr_to_lock_record:30 (00) (00)轻量级锁

ptr_to_heavyweight_monitor:30 (10)重量级锁

gc_info:30 (11)GC标记

64位JVM-->Mark Word

存储内容(62bit) 锁状态(2bit)

unused:25 | identify_hashcode:25 | unused:1 | age:4 | biased_lock:1 (01)无锁

unused:25 | identify_hashcode:25 | unused:1 | age:4 | biased_lock:1 (01)无锁锁

record:62(00)轻量级锁 ptr_to_heavyweight_monitor:62 (10)重量级锁

gc_info:62 (11)GC标记

说明:

1、名词:

    • age: GC分代年龄
    • identify_hashcode: 对象的hashcode值
    • threadId: 偏向线程的Id
    • biased_lock: 是否是偏向锁,因为只占一个bit,所以只有0和1
    • epoch: 偏向时间戳
    • ptr_to_lock_record: 指向栈中轻量级锁记录的指针
    • ptr_to_heavyweight_monitor:指向栈中重量级锁的指针
    • GC标记: 用于GC算法对对象的标记
    • gc_info: GC算法给不同状态的标记信息

元数据指针(Klass Pointer):类型指针存放的是该对象对应的类的指针。即该指针应该指向方法区的内存区域。

Array Length:数组长度只在数组类型的对象中存在。用于记录数组的长度。避免获取数组长度时,动态计算。以空间换时间的做法

 

5.JVM(四)-Runtime Data Area

 

 

  解释:运行时数据区     -就是Java虚拟机定义的在程序运行期间使用各种运行数据区域。其中一部分数据区就是Java虚拟机上创建的,只有当Java虚拟机启动和退出的时,该数据区域才会创建和销毁。另外一部分数据区域为每个线程创建。这部分运行时数据区域在线程运行时创建,线程退出时销毁。  

   

 

  

 

posted on 2022-07-20 12:52  成为一代王者的星辰  阅读(96)  评论(0编辑  收藏  举报