java虚拟机1

JVM基础知识

从编译到执行

  • HelloWorld.java通过javac编译成HelloWorld.class

  • JVM中的ClassLoader类加载器把HelloWorld.class加载到JVM中

  • 再通过字节码解释器的执行引擎运行在操作系统上

  • 在JDK底下有一个JRE(java运行时环境),里面有很多java类库

  • 在JRE底下有一个JVM,也就是java虚拟机

  • 任何一个class要运行,除了走字节码解释器执行,还可以通过JIT编译器来执行

  • jvm把class这个字节码文件翻译成操作系统底层能够识别的机器码

JVM的跨平台与语言无关性

  • 写一个类,编译成class
  • 这个class文件可以跑在windows、linux、mac等不同操作系统上,但是前提是要在官网上下载相应操作系统的JDK
  • JVM并不识别java语言,识别的是class字节码,除了java,还有scala、kotlin、groovy都有字节码,这称为语言无关性
  • 奠定了很强大的java生态圈,比如kafka

常见的JVM实现

  • Hotspot

    • oracle
    • 在jdk1.8融合了Jrocket
  • Jrocket

    • 原先属于BEA,后被oracle收购
  • J9

    • IBM
    • 只能用在IBM自己的产品上
  • TaobaoVM

    • 淘宝定制版
    • 是hotspot的深度定制版本
    • 考虑了电商场景
    • 进行垃圾回收时可以定制化,垃圾回收普通虚拟机是根可达,除此之外,还可以根据业务可达、seesion可达
  • LiquidVM

    • BEA
    • 底下没有操作系统
  • zing

    • 同名公司zing
    • 很贵
    • 垃圾回收效率非常高,可以控制在1ms
    • hotspot吸收了里面的一个垃圾回收算法,hotspot又推出了zgc
  • 虚拟机本身有一套规范,唯一没按规范的是微软

JVM知识体系

  • 核心:内存结构,以下的7项都跟内存结构相关
  • 性能调优
  • 垃圾回收
  • JVM自身优化技术
  • 执行引擎
  • 监控工具
  • 类文件结构
  • 类加载

JVM的内存区域

  • class其实就是jvm的指令,同时也需要模拟内存

运行时数据区域

  • Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域
结构
  • JVM运行时数据区

    • 线程私有区(每个线程都有一个),如果启动了多个线程,就会有多个线程私有区,就会有多个虚拟机栈

      (1)虚拟机栈

      (2)本地方法栈

      (3)程序计数器

      $1.虚拟机栈和程序计数器配合使用执行java方法

      $2.native方法是在c/c++中实现的,比如操作系统中有dll的动态连接库,native方法也是通过类似的手段调用了操作系统底层的某个方法,像这些native方法是在本地方法栈中执行的

      ##为什么一定要有本地方法栈?

      虚拟机规范,只要有这个概念,并没有规定实现一定要按什么样来实现

    • 线程共享区

      (1)方法区

      $1.方法区除了方法区,还包括运行时常量池

      (2)堆

  • 直接内存

  • 假如电脑操作系统有8g内存,JVM虚拟化了3g,则直接内存为5g

虚拟机栈

  • 存储当前线程运行java方法所需的数据,指令、返回地址
模拟
  • package ex1;
    /**
     * @author King老师
     * JAVA方法的运行与虚拟机栈
     */
    public class MethodAndStack {
        public static void main(String[] args) {
            A();
        }
        public static void A(){
            B();
        }
        public static void B(){
            C();
        }
        public static void C(){
    
        }
    }
    
    
  • 假设我们启动一个线程1来运行main方法

  • 1.JVM要启动一个虚拟机栈,栈的特点是先进后出

  • 2.会往这个虚拟机栈压入栈帧1-main()

  • 3.继续往这个虚拟机栈压入栈帧2-A()

  • 4.继续往虚拟机栈压入栈帧3-B()

  • 5.最后往虚拟机栈压入栈帧4-C()

  • 6.C方法执行完毕,栈帧4-C()出栈,然后就被销毁了

  • 7.B方法执行完,栈帧3-B()出栈

  • 8.A方法执行完,栈帧2-A()出栈

  • 9.main方法执行完,栈帧1-main()出栈

大小限制
  • –Xss
    • 具体的VM命令设置见—https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
    • 设置线程栈大小,linux64位下默认是1M,不同操作系统是不一样的
栈异常
  • 栈溢出—StackOverflowError
    • 递归方法没有出口
  • 国外社区StackOverflow,查很多异常信息是十分方便的,能查到很多百度查不到的,把日志贴上去很快就能找到相应问题的解决方法

栈帧

  • 虚拟机栈最核心的方法是栈帧
结构
  • 1.局部变量表,方法中的变量,不但包括8大基础类型变量,还包括对象的引用
    • 局部变量表从第0行开始,其中存储的是this
  • 2.栈帧最核心的部分-----操作数栈
    • 也是一个栈
    • 操作数栈是执行引擎的一个工作区
    • 如果要自己造操作系统,一般是由CPU+缓存+主内存,这些东西不能是文件,主内存就是放数据的地方,CPU运算的时候先把数据从主内存放到缓存中,然后再利用缓存中的数据去运算
    • 既然JVM是一个模拟版本的操作系统,在操作系统之上再架一个操作系统,对应上面的结构可以对照为 JVM执行引擎+操作数栈+(栈、堆),所以可以把操作数栈看成操作系统中的缓存
  • 3.动态连接
    • 是java多态动态语言的特性,必须结合class文件和执行引擎
  • 4.完成出口
    • 每一个栈帧有完成出口,也可以称为返回地址
    • 字节码文件中iconst_1左边的数据就是字节码地址或对应的行号
    • 方法出栈后,执行引擎会返回程序计数器记录的行号
模拟栈帧
java文件
  • package ex1;
    
    /**
     * @author King老师
     * 栈帧执行对内存区域的影响
     */
    public class Person {
        public  int work()throws Exception{
            int x =1;
            int y =2;
            int z =(x+y)*10;
            return  z;
        }
        public static void main(String[] args) throws Exception{
            Person person = new Person();//person 栈中--、  new  Person  对象是在堆
            person.work();
    
            person.hashCode();
    
        }
    }
    
    
class文件
  • 不能直接用notepad打开,打开是乱码,直接打开class文件所在目录的命令行,并输入javap -c Person.class,对class字节码进行了反汇编

  • Compiled from "Person.java"
    public class ex1.Person {
      public ex1.Person();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public int work() throws java.lang.Exception;
        Code:
           0: iconst_1
           1: istore_1
           2: iconst_2
           3: istore_2
           4: iload_1
           5: iload_2
           6: iadd
           7: bipush        10
           9: imul
          10: istore_3
          11: iload_3
          12: ireturn
    
      public static void main(java.lang.String[]) throws java.lang.Exception;
        Code:
           0: new           #2                  // class ex1/Person
           3: dup
           4: invokespecial #3                  // Method "<init>":()V
           7: astore_1
           8: aload_1
           9: invokevirtual #4                  // Method work:()I
          12: pop
          13: return
    }
    
  • 为什么work()中没有第8行?

    这只是简单的近似位,因为JVM是通过c/c++编写的,c/c++中有一个偏移量的概念,这里的数字代表字节码相对于work方法的偏移量

  • java虚拟机字节码指令集—https://cloud.tencent.com/developer/article/1333540

  • iconst表示将1个常量加入到操作数栈中

jvm中执行过程
  • 1.首先启动一个线程,创建一个虚拟机栈
  • 2.main()-栈帧入栈
  • 3.work()-栈帧入栈
  • 4.执行work方法
    • 1.iconst_1— 将1压入操作数栈中
    • 2.istore_1 — 从操作数栈把1取出来,并将1放入局部变量表第1行
    • 3.iconst_2 — 将2压入操作数栈中
    • 4.istore_2— 从操作数栈把2取出来,并将2放入局部变量表第2行
    • 5.iload_1 — 将局部变量表下标为1的变量加载到操作数栈中
    • 6.iload_2 — 将局部变量表下标为2的变量加载到操作数栈中
    • 7.iadd — 取出操作数栈栈顶的两个元素2和1,放入执行引擎相加,得到结果3,并将结果重新放入操作数栈中
    • 8.bipush 10 — 把10的常量加到操作数栈中
    • 9.imul — 取出操作数栈栈顶的两个元素10和3,放入执行引擎相乘,得到结果30,并将结果重新放入操作数栈中
    • 10.istore_3 — 把30存放到局部变量表下标为3的位置
    • 11.iload_3 — 将局部变量表下标为3的变量加载到操作数栈中
    • 12.ireturn — 方法返回指令,ireturn说明是int类型,执行引擎只能操作操作数栈,将30这个返回值返回
程序计数器
  • 指向当前线程正在执行的字节码指令的地址

  • 这是一块单独的区域,是一块很小很小的内存

  • 比如方法转跳、进行恢复、异常处理都需要程序计数器

  • 为什么需要程序计数器?

    • 操作系统:CPU时间片轮转机制,1秒钟对于CPU太慢了,所以CPU对1秒钟进行切片,假设按1ms切片,切成了1000片,之前的work方法需要占用两个时间片,但是操作系统层面并没有规定一定是连续的,假设work方法执行到第3行,事件片用完了,必须挂起或阻塞,来等下一个时间片,所以必须记录当前执行到第3行,确保jvm在时间片轮转机制下正常运行
    • 本质上是对操作系统的程序计数器的映射
  • 比如work方法,程序执行到第7行,程序计数器里面count=7

  • 程序计数器是在内存里面唯一不会内存溢出(OOM)的区域

方法区

  • 类加载时是把某一个类加载进虚拟机,而方法区就是去加载相关的类信息

  • 方法区是虚拟机规范中规定的,是一个逻辑划分,任何版本的虚拟机都有一块区域进行逻辑划分,叫做方法区

永久代和元空间
  • jdk1.7方法区的实现叫做永久代
  • jdk1.8之后方法区的实现叫做元空间
运行时常量池
  • 常量池分为运行时常量池和静态常量池

  • 静态常量池

    • 之前反汇编是通过javap -c Person.class指令,如果换成javap -v

      Person.class

    • Classfile /C:/Users/liusiping/AppData/Local/Temp/WeiyunDisk/tencent_weiyun_open_file_temp/{D9D05872-9A22-40B9-A327-39893EAC0A5A}/ref-jvm3/ref-jvm3/out/production/ref-jvm3/ex1/Person.class
        Last modified 2020-7-16; size 622 bytes
        MD5 checksum ddf1a0c6629c54ff1cc57a76de3a42af
        Compiled from "Person.java"
      public class ex1.Person
        minor version: 0
        major version: 52
        flags: ACC_PUBLIC, ACC_SUPER
      Constant pool:
         #1 = Methodref          #5.#28         // java/lang/Object."<init>":()V
         #2 = Class              #29            // ex1/Person
         #3 = Methodref          #2.#28         // ex1/Person."<init>":()V
         #4 = Methodref          #2.#30         // ex1/Person.work:()I
         #5 = Class              #31            // java/lang/Object
         #6 = Utf8               <init>
         #7 = Utf8               ()V
         #8 = Utf8               Code
         #9 = Utf8               LineNumberTable
        #10 = Utf8               LocalVariableTable
        #11 = Utf8               this
        #12 = Utf8               Lex1/Person;
        #13 = Utf8               work
        #14 = Utf8               ()I
        #15 = Utf8               x
        #16 = Utf8               I
        #17 = Utf8               y
        #18 = Utf8               z
        #19 = Utf8               Exceptions
        #20 = Class              #32            // java/lang/Exception
        #21 = Utf8               main
        #22 = Utf8               ([Ljava/lang/String;)V
        #23 = Utf8               args
        #24 = Utf8               [Ljava/lang/String;
        #25 = Utf8               person
        #26 = Utf8               SourceFile
        #27 = Utf8               Person.java
        #28 = NameAndType        #6:#7          // "<init>":()V
        #29 = Utf8               ex1/Person
        #30 = NameAndType        #13:#14        // work:()I
        #31 = Utf8               java/lang/Object
        #32 = Utf8               java/lang/Exception
      {
        public ex1.Person();
          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 7: 0
            LocalVariableTable:
              Start  Length  Slot  Name   Signature
                  0       5     0  this   Lex1/Person;
      
        public int work() throws java.lang.Exception;
          descriptor: ()I
          flags: ACC_PUBLIC
          Code:
            stack=2, locals=4, args_size=1
               0: iconst_1
               1: istore_1
               2: iconst_2
               3: istore_2
               4: iload_1
               5: iload_2
               6: iadd
               7: bipush        10
               9: imul
              10: istore_3
              11: iload_3
              12: ireturn
            LineNumberTable:
              line 9: 0
              line 10: 2
              line 11: 4
              line 12: 11
            LocalVariableTable:
              Start  Length  Slot  Name   Signature
                  0      13     0  this   Lex1/Person;
                  2      11     1     x   I
                  4       9     2     y   I
                 11       2     3     z   I
          Exceptions:
            throws java.lang.Exception
      
        public static void main(java.lang.String[]) throws java.lang.Exception;
          descriptor: ([Ljava/lang/String;)V
          flags: ACC_PUBLIC, ACC_STATIC
          Code:
            stack=2, locals=2, args_size=1
               0: new           #2                  // class ex1/Person
               3: dup
               4: invokespecial #3                  // Method "<init>":()V
               7: astore_1
               8: aload_1
               9: invokevirtual #4                  // Method work:()I
              12: pop
              13: return
            LineNumberTable:
              line 15: 0
              line 16: 8
              line 17: 13
            LocalVariableTable:
              Start  Length  Slot  Name   Signature
                  0      14     0  args   [Ljava/lang/String;
                  8       6     1 person   Lex1/Person;
          Exceptions:
            throws java.lang.Exception
      }
      SourceFile: "Person.java"
      
    • 新方法反汇编出来的文件里面就有常量池,在这个class文件里面就有一大块内容来表示常量池,包括定义的常量、方法的很多东西

  • 运行时常量池

    • 当我们把class文件加载到常量池时,它会把符号引用替换成直接引用

      $符号引用

      比如Tool.hashcode(),这就是一个符号引用

      $直接引用

字面量
  • Stirng a = “b”;
  • b就是字面量
方法区运行步骤
  • 首先进行类加载,放class文件,class文件之后要进行解析,在解析的过程把一些东西放到常量池中间来

  • 几乎所有的对象都在堆中分配

直接内存

  • 堆外内存,JVM在运行时除了自己虚拟化的内存,还有操作系统本身剩余的内存,比如Nio中的DirectByteBuffer
  • sun.misc.Unsafe类就是可以操作内存的
  • EHcache、中间件大量使用了直接内存

JVM的整体内存结构

  • 机器内存
    • 运行时数据区
      • 堆:共享的,要回收的
      • 方法区:共享的,静态的,要经常使用的
      • 栈区
    • 直接内存/堆外内存

总结

  • 1.一个线程只有一个程序计数器
  • 2.iload_3入栈后,这个数值仍然存在于局部变量表中,局部变量表的数据不会删,但是栈帧出站了,局部变量表、操作数栈就都没有了
  • 3.时间片轮转,反汇编的行号为什么不连续?因为有的指令需要多个字节的操作数。
  • 4.运行时常量池中符号引用变为直接引用是什么意思?
  • 5.栈溢出的主要原因是不断的压栈帧
  • 6.栈帧出栈后不需要回收,这块区域就没有了,跟随线程销毁
  • 7.ireturn到哪里?属于一个指令,根据返回值类型进行区分返回
  • 8.不是操作数栈溢出,指令集设计有问题,是虚拟机栈溢出
  • 9.符号引用,不指代地址,jvm运行后当变成直接引用,就代表了具体的地址。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值