深入JVM内核(一)——初始JVM

由于之前看的容易忘记,因此特记录下来,以便学习总结与更好理解,该系列博文也是第一次记录,所有有好多不完善之处请见谅与留言指出,如果有幸大家看到该博文,希望报以参考目的看浏览,如有错误之处,谢谢大家指出与留言。

目录:

JVM的概念

JVM发展历史

JVM种类

Java语言规范

JVM规范

一、初识JVM – JVM概念

1、JVM是Java Virtual Machine的简称。意为Java虚拟机

2、虚拟机:指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统

扫描二维码关注公众号,回复: 2151146 查看本文章
3、有哪些虚拟机
    -VMWare
    -Visual Box
    -JVM

4、不同虚拟机比较

VMWare或者Visual Box与JVM不同之处他们都是使用软件模拟物理CPU的指令集,所模拟的对象都是真实存在的计算机,所模拟的cpu都是现实可找到的CPU案例,包括CPU,硬盘,内存等,JVM模拟的对象是现实找不到的,并没有真实计算机去运行执行字节码,单纯从软件去做的设计。在正常的CPU中,都有若干个寄存器,而jvm的cpu中除了PC寄存器外,其他的JVM本身对其他寄存器做了裁剪,因为他是纯粹软件去实现的,所以寄存器效果并没有多大作用,引入而且会对JVM设计有运行带来大量的麻烦,所以就去掉了。

5、JVM使用软件模拟Java 字节码的指令集,与平时说的CPU指令集并不一样

二、初识JVM —— Java和JVM的历史

1、1996年 SUN JDK 1.0 Classic VM

    — 纯解释运行,使用外挂进行JIT  (这个性能是非常低的,所以开始让人们对java感觉性能非常低)

2、1997年 JDK1.1 发布

    — AWT、内部类、JDBC、RMI、反射

3、1998年 JDK1.2 Solaris Exact VM(这个虚拟机寿命比较短,由于hsotspot的加入导致他淘汰,但JDK1.2开始 称为Java 2;J2SE J2EE J2ME 的出现加入Swing Collections)
     (1)、
JIT 解释器混合
     (2)、Accurate Memory Management 虽然淘汰但他却做到精确内存管理,数据类型敏感

     (3)、提升的GC性能

4、2000年 JDK 1.3 Hotspot 作为默认虚拟机发布  (也加入(关与声音一些API)JavaSound)

5、2002年 JDK 1.4   (最早的虚拟机)Classic VM退出历史舞台   同时JDK1.4也加入Assert 正则表达式  NIO  IPV6 日志API  加密类库   这时候java类库就相当完整了。

6、2004年发布 JDK1.5 即 JDK5 、J2SE 5 、Java 5  (这个版本对java至关重要的)

    加入了如下内容:

     — 泛型 — 注解 — 装箱  — 枚举   — 可变长的参数  — Foreach循环

7、JDK1.6 JDK6加入如下内容:

     — 脚本语言支持 — JDBC 4.0 — Java编译器 API

8、2011年 JDK7发布(由于在版本规划中加入太多东西,一度难以发布,所以好多原本计划的内容推到jdk1.8中

     — 延误项目推出到JDK8  — G1  — 动态语言增强  — 64位系统中的压缩指针  — NIO 2.0

9、2014年 JDK8发布 

     — Lambda表达式  — 语法增强  Java类型注解

10、2016年JDK9

     — 模块化

三、初识JVM—Java和JVM的历史 —— 发展中的大事件

1、使用最为广泛的JVM为HotSpot

2、HotSpot 为Longview Technologies开发 被SUN收购

3、2006年 Java开源 并建立OpenJDK

    — HotSpot  成为Sun JDK和OpenJDK中所带的虚拟机

4、2008 年 Oracle收购BEA

    — 得到JRockit VM

5、2010年Oracle 收购 Sun

   — 得到Hotspot        从此,Oracle拥有了两个主流的虚拟机JRockitHotspot

6、Oracle宣布在JDK8时整合JRockitHotspot,优势互补

   — 在Hotspot基础上,移植JRockit优秀特性  (在JDK1.8把他俩整合,因为Oracle不可能维护两个虚拟机,所以把他们整合)

四、初识JVM —— 各式JVM

1、KVM

     -- SUN发布

     -- IOS Android前,广泛用于手机系统(以前java也是应用于手机)

2、CDC/CLDC HotSpot

    -- 手机、电子书、PDA等设备上建立统一的Java编程接口

    -- J2ME的重要组成部分

3、JRockit

    -- BEA 

4、IBM 的J9 VM

    -- 用于IBM内部程序,自己用。

5、Apache Harmony

    -- 兼容于JDK 1.5和JDK 1.6的Java程序运行平台

    -- 与Oracle关系恶劣 阿帕奇退出JCP ,Java社区的分裂

    -- OpenJDK出现后,受到挑战 2011年 退役

    -- 没有大规模商用经历

    -- 对Android的发展有积极作用

五、初识JVM – 规范

(一)java规范

     -- 语法

     -- 变量

     -- 类型

     -- 文法

1、语法定义

    -- If ThenStatement:

             if ( Expression ) Statement

    -- ArgumentList:支持多个参数列表
        Argument
        ArgumentList , Argument

2、词法结构

     -- \u + 4个16进制数字 表示UTF-16

     -- 行终结符: CR, or LF, or CR LF.

     -- 空白符

     -- 空格 tab \t 换页 \f  行终结符

     -- 注释

     -- 标识符

     -- 关键字

标识符:标识字符但不是关键字或布尔值文字或零文字

IdentifierChars:    JavaLetter    IdentifierChars JavaLetterOrDigit

JavaLetter:    any Unicode character that is a Java letter (see below)

JavaLetterOrDigit:    any Unicode character that is a Java letter-or-digit (see below)

例子:

public static void 打印(){
System.out.println("中文方法哦");
}
public  static void main(String[] args) {
打印();

}

 —  Int
        0 2 0372 0xDada_Cafe 1996 0x00_FF__00_FF
 — Long
        0l 0777L 0x100000000L 2_147_483_648L 0xC0B0L
 — Float
        1e1f 2.f .3f 0f 3.14f 6.022137e+23f
 — Double
        1e1 2. .3 0.0 3.14 1e-9d 1e137
 — 操作
        +=  -=  *=  /=  &=  |=  ^=  %=  <<=  >>=  >>>=

 4、类型和变量

—  元类型 
         byte short int long float char

— 变量初始值
        boolean false
        char \u0000

— 泛型

举例:

class Value { int val; }

class Test {
    public static void main(String[] args) {
        int i1 = 3;
        int i2 = i1;
        i2 = 4;
        System.out.print("i1==" + i1);
        System.out.println(" but i2==" + i2);
        Value v1 = new Value();
        v1.val = 5;
        Value v2 = v1;
        v2.val = 6;
        System.out.print("v1.val==" + v1.val);
        System.out.println(" and v2.val==" + v2.val);
    }
}

  结果:i1==3 but i2==4

    v1.val==6 and v2.val==6

    i1 i2为不同的变量
    v1 v2为引用同一个实例

5、还有其他一些规范

    — Java内存模型

    — 类加载链接的过程

    — public static final abstract的定义

    — 异常

    — 数组的使用

    — …….

Java语言规范定义了什么是Java语言,则

JVM规范定义了什么是JVM,所以java语言与jvm是相对独立的,其他语言也可以运行在JVM中,只要准守jvm规范

(二)JVM规范

     -- Class文件类型

     -- 运行时数据

     -- 帧栈

     -- 虚拟机的启动

     -- 虚拟机的指令集

1、Java语言和JVM相对独立。其他语言也可以运行在JVM比如:
    (1)、 Groovy (2)、 Clojure (3)、Scala

2、JVM主要定义二进制class文件和JVM指令集等

3、Class 文件格式

4、数字的内部表示和存储

     Byte  -128 to 127 (-27 to 27 - 1)

5、returnAddress 数据类型定义

    — 指向操作码的指针。不对应Java数据类型,不能在运行时修改。Finally实现需要/

6、定义PC

7、

8、栈

9、方法区

10、JVM中整数的表达
    — 原码:第一位为符号位(0为正数,1为负数)
    — 反码:符号位不动,原码取反
    — 负数补码:符号位不动,反码加1
    — 正数补码:和原码相同
    — 打印整数的二进制表示
        int a=-6;
        for(int i=0;i<32;i++){  //因为整数有32位,所以按位取时,做32次循环,每一次取一位
            int t=(a & 0x80000000>>>i)>>>(31-i);      //0x80000000最高位为1,a & 0x80000000 就是把a的第一位取出来,a做为无符号的右移>>>(31-i)   这个多位,就打印出这个数字的2进制表示。
               System.out.print(t);  }


为什么要用补码?好处(1)因为0无正负,难以表示,所以用补码表示计算才是正确的,(2)补码能够很好的参与计算机的二进制计算。

(1)计算0的表示:


(2)举例:


11、Float的表示与定义
    — 支持 IEEE 754

       > s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm

     以单精度为例:

     

              举例:

                

       > e全0 尾数附加位为0  否则尾数附加位为1  位数虽为23其实指24位,隐藏在指数当中,可以通过e推导。
       > s*m*2^(e-127)  
12、一些特殊的方法
          <clinit>  类的初始化方法

          <init>    实例测初始化方法

在虚拟机中构造,比如类如果我们没有初始化构造函数,他会默认帮那个我们初始化

13、JVM指令集

    — 类型转化

        l2i 

    — 出栈入栈操作

        aload  astore

    — 运算

        iadd  isub

    — 流程控制

        ifeq ifne

    — 函数调用

        invokevirtual invokeinterface  invokespecial  invokestatic 

14、JVM需要对Java (库)Library 提供以下支持:

     — 反射 java.lang.reflect

     — ClassLoader   类装载器。java中是有加载器,但最终启动是这个,在虚拟机中,而且java是无法操作它的。

     — 初始化class和interface

     — 安全相关 java.security

     — 多线程

     — 弱引用

15、JVM的编译

    — 源码到JVM指令的对应格式

    — Javap

    — JVM反汇编的格式

        <index> <opcode> [ <operand1> [ <operand2>... ]] [<comment>]

举例:

void spin() {
  int i; 
  for (i = 0; i < 100; i++) { ;
     // Loop body is empty
   }

 }

虚拟机中直接执行的事下面这些指令代码:

0   iconst_0       // Push int constant 0  首先操作0,所以把零入栈
1   istore_1       // Store into local variable 1 (i=0)  存储局部变量1
2   goto 8         // First time through don't increment 第一次通过不增加
5   iinc 1 1       // Increment local variable 1 by 1 (i++)  增量局部变量1
8   iload_1        // Push local variable 1 (i)   推局部变量1入栈
9   bipush 100     // Push int constant 100  再把常量100入栈
11  if_icmplt 5    // Compare and loop if less than (i < 100)   比较和循环如果小于

14  return         // Return void when done  完成时返回

在例如:

     4.public static int add(int a, int b) {

     5.int c = 0;

     6. c = a + b;

     7. return c;

     8.}

查看字节码的命令:javap -verbose ByteCode.class

add方法的字节码如下:

 public static int add(int, int);
   descriptor: (II)I                    //描述方法参数为两个int类型的变量和方法的返回类型是int的
   flags: ACC_PUBLIC, ACC_STATIC        //修饰方法public和static
   Code:
     stack=2, locals=3, args_size=2     //操作数栈深度为2,本地变量表容量为3,参数个数为2
        0: iconst_0    //将int值0压栈
        1: istore_2    //将int值0出栈,存储到第三个局部变量(slot)中
        2: iload_0     //将局部变量表中第一个变量10压栈
        3: iload_1     //将局部变量表中第一个变量20压栈
        4: iadd        //将操作数栈顶两个int数弹出,相加后再压入栈中
        5: istore_2    //将栈顶的int数(30)弹出,存储到第三个局部变量(slot)中
        6: iload_2     //将局部变量表中第三个变量压栈
        7: ireturn     //返回栈中数字30
     LineNumberTable:
       line 5: 0       //代码第5行对应字节码第0行
       line 6: 2       //代码第6行对应字节码第2行
       line 7: 6       //代码第7行对应字节码第6行
     LocalVariableTable:
       Start  Length  Slot  Name   Si
           0       8     0     a   I    //a占用第1个solt
           0       8     1     b   I    //b占用第2个solt
           2       6     2     c   I    //c占用第3个solt

局部变量表:存放的一组变量的存储空间。存放方法参数和方法内部定义的局部变量表。

在java编译成class的时候,已经确定了局部变量表所需分配的最大容量。

局部变量表的最小单位是一个Slot。

虚拟机规范没有明确规定一个Slot占多少大小。只是规定,它可以放下boolean,byte,...reference &return address.

reference 是指一个对象实例的引用。关于reference的大小,目前没有明确的指定大小。但是我们可以理解为它就是类似C++中的指针。

局部变量表的读取方式是索引,从0开始。所以局部变量表可以简单理解为就是一个表.

局部变量表的分配顺序如下:

this 引用。可以认为是隐式参数。

方法的参数表。

根据局部变量顺序,分配Solt。

一个变量一个solt,64为的占2个solt。java中明确64位的是long & double

为了尽可能的节约局部变量表,Solt可以重用。

注意:局部变量只给予分配的内存,没有class对象的准备阶段,所以局部变量在使用前,必须先赋值。

局部变量表可参考:https://www.tuicool.com/articles/URZrMnb

根据上面字节码画出下面局部变量表和操作数栈之间的操作关系。 

图中调用add(10,20)传入的参数是a=10;b=20。

  1. 指令0执行后:局部变量表中有两个数字10、和20,操作数栈一个值0,程序计数器指向第0行字节码指令 
    0: iconst_0 //将int值0压栈
  2. 指令1执行后:局部变量表中有三个数字10、20和0,操作数栈没有值,程序计数器指向第1行字节码指令 
    1: istore_2 //将int值0出栈,存储到第三个局部变量(slot)中
  3. 指令2执行后:局部变量表中有三个数字10、20和0,操作数栈一个值10,程序计数器指向第2行字节码指令 
    2: iload_0 //将局部变量表中第一个变量10压栈
  4. 指令3执行后:局部变量表中有三个数字10、20和0,操作数栈两个值10和20,程序计数器指向第3行字节码指令 
    3: iload_1 //将局部变量表中第一个变量20压栈
  5. 指令4执行后:局部变量表中有三个数字10、20和0,操作数栈一个值30,程序计数器指向第4行字节码指令 
    4: iadd //将操作数栈顶两个int数弹出10和20,相加后再压入栈中
  6. 指令5执行后:局部变量表中有三个数字10、20和30,操作数栈没有值,程序计数器指向第5行字节码指令 
    5: istore_2 //将栈顶的int数(30)弹出,存储到第三个局部变量(slot)中
  7. 指令6执行后:局部变量表中有三个数字10、20和30,操作数栈一个值30,程序计数器指向第6行字节码指令 
    6: iload_2 //将局部变量表中第三个变量压栈
  8. 指令7执行后:将栈中的数字返回给调用方法,并销毁此栈帧 
    7: ireturn //返回栈中数字30
简单总结介绍堆和栈:
堆是先进先出,而栈是先进后处 
1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,
程序员不能直接地设置栈或堆。 
2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的
数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势
是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些
不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢
3. Java把内存划分成两种:一种是栈内存,一种是堆内存。在函数中定义的一些基本类型的变量
和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为
这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,
该内存空间可以立即被另作他用。堆内存用来存放由new创建的对象和数组。在堆中分配的内存,
由Java虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以在栈中定义一
个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就
成了数组或对象的引用变量。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程
序中使用栈中的引用变量来访问堆中的数组或对象
heap堆:用来存放new出来的东西
stack栈:局部变量。
data segment:静态变量,字符串常量。
code segment:存放代码
堆栈详细可参考:https://blog.csdn.net/qq_24531461/article/details/70224449

此篇只是介绍认识JVM以及规范,后续再单独继续深入。

猜你喜欢

转载自blog.csdn.net/gududedabai/article/details/80978204