zoukankan      html  css  js  c++  java
  • JVM概述

    1. JVM是一个规范来定义的抽象计算机,本质上就是运行在计算机上的软件。

      可以这样理解 :

        抽象规范;

        具体的实现;

        运行中的虚拟机实例。 

      JVM是基于栈来操作的虚拟就计算机,而不是基于寄存器的。

    2.JVM的生命周期

      一个java程序,一个JVM实例。

      随着Java程序的启动 而 启动一个JVM实例,随着Java程序的 退出/关闭 而 消亡一个JVM实例。   

      main()是JVM的入口方法:JVM通过调用main()来运行一个Java程序。--- public static void main (String arg[]){}

      main():是一个非守护线程。

      线程 分类 : 守护线程  ,非守护线程。

        守护线程 :

          守护线程通常是JVM自己使用的(例如:垃圾回收线程);JVM退出后守护线程就是自动消亡。

          可以理解:守护线程是非守护线程的附属,也就意味着一个JVM实例中的全部非守护线程结束而结束。

      只要还有任何非守护线程运行,Java程序就在继续运行,JVM也就继续存活。

    3.JVM的体系结构

        

      JVM的行为:类加载器子系统 ,内存区,数据类型,指令。

      运行时数据区 :存放管理数据(字节码,class文件信息,对象,方法参数,方法返回值,局部变量,中间结果等)

      每一个JVM实例都有一个方法区和一个堆,他们被该JVM实例所有线程共享。

      方法区 : 存放类信息。

      堆 : 存放对象。

        

      一个线程 一个PC寄存器(程序计数器)和一个栈。

      当执行非本地方法时PC寄存器(的值)总是指向下一条将被执行的指令,栈存储方法调用的状态(局部变量,参数,返回值,中间结果等)。

      当执行本地方法时,方法调用状态存储在本地方法栈中。

      栈帧 : 是栈的基本单元,一个栈帧是一个Java方法调用的状态。栈帧是后进先出的,有出栈/压栈操作。

      JVM没有寄存器,指令集使用Java栈来存储中间数据。

        

      在上图中,Java栈是向下生长的,栈顶显示在图的底部,正在执行的方法栈帧以浅色表示,PC计数器指向下一条指令。

      线程1 和线程2 (执行Java方法)的栈帧 :上图左边的示例;

      线程3(执行本地方法) 的栈帧 :上图右边的示例;

    3.1 JVM数据类型 

        

      基本类型:真正的数据 ; 应用类型 : 对象的引用。

      注意 : JVM对boolean看作是基本类型,但是指令集对boolean支持有限。

        编译器 把boolean 编译成 int/byte 类型。

          false: 整数0 表示;true :非0 整数表示;

          涉及boolean操作:转化为 int 进行操作

          boolean 数组 :当作 byte数组

      returnAddress :基本类型 ,只能JVM内部使用 ,用来实现Java程序中的 finally语句 。 

      在JVM中,数组是真正的对象 ;

          接口时对实现了该接口的某一个实例的引用

          类 是对类实例的引用。

         

      注意 : 在栈中的操作都是以字长为单位的。意味着所有的不足字长的数据类型将转化为字长来操作。

          例如 : byte  short  char  boolean 将转换为inte 后,然后再栈中操作。 

        方法区 和堆空间 不会有这样的转换。

    3.2 JVM的字长

      JVM最基本的数据操作单位:字(word)

      JVM设计者至少选择32位作为字长 。(通常根据底层主机的指针长度来选择字长)

      运行时数据区的大部分内容操作,都是基于 字 这个概念的。

    3.3 JVM 类装载器子系统

      负责查找,装载类的

      分类 : 启动(引导)类装载器 ,用户自定义类装载器。 每一个类装载器都有自己的命名空间,命名空间用来保护类安全的。

      装载类的步骤 :

        1) 装载----查找并装载类的二进制数据,最终形成该类的Class对象。

        2)连接 --执行验证,准备,解析

          验证 :确保类型的正确性

          准备 :为类变量分配内存,并初始化默认值

          解析 :把类型中的符号引用转换为直接引用。

        3)初始化 --把类变量初始化为正确初始值。

      

      启动类装载器 :是JVM实现的一部分,JVM必须有一个启动类装载器。只装载JDK安装目录下的类,且这些类不会被卸载。

      自定义类装载器 : 是Java程序的一部分。它是派生自java.lang.ClassLoader。

        例如  : 系统类装载器(属于自定义类装载器):装载Java程序classpath下的类,这些类可能会被卸载。

        ClassLoader的4个方法是通往JVM的通道 : 

          defineClass( ) :任何JVM实现必须保证defineClass( ) 能够把新类型导入到方法区中

          findSystemClass( ) :任何JVM实现必须保证findSystemClass( ) 能够调用系统类装载器,此类返回一个Class对象

          resolveClass( ) :任何JVM实现必须保证resolveClass( )能够让类装载器子系统执行连接动作。

        任何JVM实现都必须把这些方法连到内部的类装载器子系统中。

      命名空间 : 任何类装载器都有自己的命名空间,其中维护着有它装载的类。命名空间是解析过程的结果。

    3.4 方法区

      存放类型信息的内存。从类的二进制数据中,提取类型数据放到方法区中。

      当JVM运行Java程序时,就会查找使用存储在方法区中的类型信息。

      由于方法区是线程共享的,所以方法区数据的访问必须被设计成线程安全的。

      方法区的大小不必是固定的,可以动态扩展或收缩, 也不必是连续的。

      方法区可以被垃圾回收。

      方法区存放的类型信息 :

        类信息 : 全限定名  , 访问修饰符   , 是类 还是接口  , 直接父类的全限定名  , 直接接口全限定名的有序列表

        常量池  : JVM 必须为每一个被装载的类维护一个常量池,池中数据项通过索引访问

        字段信息 : 字段名 ,字段类型 ,字段修饰符

        方法信息 : 方法名 ,方法修饰符 ,方法参数,方法返回值

        类变量 : 作为类信息,存储在方法区。 编译时常量 :是存储在常量池中的

        到ClassLoader的引用 : 装载此类的ClassLoader

        到Class类的引用 : 此类的Class对象  

        方法表 : 是数组,它的元素是实例对象的实例方法的直接引用,包含从父类继承的实例方法。 运行时可以快速访问实例方法

          包含信息 : 方法的操作数栈,局部变量区,方法字节码,异常表

    3.5 堆 

      存放对象信息的。

      JVM 有一条在堆中分配内存的指令,但是没有释放内存的指令。

      垃圾回收器 :负责回收对象,释放内存。

      堆空间的大小不必是固定的,可以动态扩展或收缩, 也不必是连续的。

      3.5.1 对象的表示  :如何在堆中表示对象。无论如何表示,都必须有个指向方法区的指针。

        对象表示法一 : 一个语柄池,一个对象池。

          

          优点 : 利于碎片整理;缺点 :每次访问需要两次指针操作,效率低下

        对象表示法二 :维护一组数据表。

          

         优缺点和上面相反。

         不管JVM如何表示对象,很有可能每一个对象都有一个指向方法区方法表的指针,可以加快访问 实例方法 效率。

           

         对象中的锁对象 : 每一个对象都有一个对象锁。

          只有当第一次需要加锁时才给对象分配对应的锁数据,即对象内有一个指向锁的指针。

          但是对象在其生命周期内没有使用锁,就没必要与锁关联。

        等待集合(wait set) : 就是让多线程完成一个共同的目标而协调工作的。

          等待集合由等待方法和通知方法联合使用  wait() / notfy()。

          当某一个线程在一个对象上调用wait()时,JVM阻塞线程,并把线程放入等待集合中。

          当另一个线程在同一个对象上调用notify( ) 时,等待集合中的线程被唤醒,进入准备阶段。    

          只有当第一次需要 wait set 时才给对象分配对应的 wait set,即对象内有一个指向 wait set 的指针。

          但是对象在其生命周期内没有使用 wait set ,就没必要与锁关联。

        垃圾回收相关的数据 :垃圾回收器必须跟踪程序中引用的对象,这个任务不可避免的给对象增加了额外数据。

          此外对于不在引用的对象,还需要指明他的终结方法 -- finalizer( ) 是否运行过。

          垃圾回收器只能执行一次对象的  finalizer( ) ,但允许 finalizer( ) 复活对象,即在此被引用;当该对象再次被回收时,

          就不会执行 finalizer( ) 。

      3.5.2 数组的内部表示

        数组类的名称由两部分组成: 维度 :使用 “[” 表示,元素类型 :使用字符表示。

          int[]   : [I   ; int[][] : [[I  ;  Object[][] :[[Ljava/lang/Object

          

     3.6 PC 程序计数器

      每一个线程拥有一个PC计数器。线程启动时创建,长度时一个字长。

    3.7  栈

      每一个线程分配一个栈,栈上数据时线程私有的。

      两种操作 : 压栈 出栈(这两种操作均以栈帧为单位的)

      Java方法的两种方式返回 : 正常返回(return);异常返回。

      Java方法返回,栈帧被弹出。

      栈 不必时连续的。

    3.8 栈帧

      组成 :局部变量区 ,操作数栈, 帧数据区。他们的大小是以字长计算的。

       3.8.1 局部变量区

        是以字长为单位的组数,从0 开始计数,通过索引访问 ;包含了对应方法的参数和局部变量。

        

    class Example3a{
       public  static int runClassMethod ( int i ,long l,float f,double  d,Object o,byte b )  {
    
    
            return 0;
       }  
    
        public  int runInstanceMethod ( char c ,double  d,short s,boolean b )  {
    
    
            return 0;
       }  
         
         
    }

     

        在实例方法的栈帧中第一项是this引用,这是隐含加入的;类方法就存在此引用。

        byte ,short ,char , boolean 在局部变量区被转换为inte ,在操作数栈也是同样转换为int 。

        注意一点 :在方法区和堆空间都不会被转换。

        Java方法的参数(形参)会严格按照声明的顺序存放,而真正的局部变量可以任意放置。

      3.8.2  操作数栈

        是以字长为单位的数组,但不是通过索引访问,而是通过 压栈 和出栈来访问的。  

        操作数栈和局部变量区存储数据的方式是一样的,数据会转化为int类型来操作。

        JVM指令是从操作数栈中读取操作数 ,而不是从寄存器中读取操作数。因此,JVM是基于栈的。

        JVM 把操作数栈作为它的工作区----大多数指令都是从操作数栈弹出数据,执行运算;然后再把结果压回操作数栈中。

      3.8.3 帧数据区

        常量池的解析,正常方法返回,异常派发的一些数据。

        每当执行 要访问常量池中数据 的指令时,就需要帧数据区中指向常量池的指针,来找到数据。

        Java方法正常返回时 :就是Java方法结束时,JVM需要恢复 发起调用的方法的栈帧(上一个栈帧),

                  设置PC寄存器指向发起调用的方法的指令,

                  把方法返回值压入发起调用的方法的操作数栈中。 

        Java方法异常退出时:帧数据必须存放一个对此方法异常表的引用,JVM根据异常表来决定如何处理异常。 

        其他数据也可存放在帧数据区。例如 调试数据。

    3.9  本地方法栈

    3.10 执行引擎

      JVM规范中,执行引擎的行为使用指令集定义。

      运行中的Java程序的每一个线程都有一个独立的执行引擎实例。

      所有属于用户运行程序的线程,都是在实际工作的执行引擎。

      指令集 :

        方法的字节码流是由JVM指令序列构成的。

        每一条指令包含一个字节的操作码,后面跟随n个操作数。

        操作码 就是将要执行的动作,操作数 就是操作码需要的额外信息。

        指令集关注的重心时操作数栈。

        指令集实际的工作方式就是把局部变量当作寄存器,用索引来访问。

      执行技术 : 解释 ,即时编译 ,自适应优化,芯片级直接执行。

    3.11 本地方法接口

      

  • 相关阅读:
    C#实现ASE加密解密
    c# 深复制
    Jenkins + Git +IIS 部署
    c#模拟Http请求
    TCP/IP学习
    c# 字符串中包含 "" 时提示:无法识别的转义序列
    部署.net core项目到IIS后HTTP 错误 500.19
    .net core读取配置文件appsetting.json
    asp.net提示“未能加载文件或程序集“XXXXXXXX.dll”或它的某一个依赖项。找不到指定的模块。”
    WCF错误404.17 请求的内容似乎是脚本,因而无法由静态文件处理程序来处理
  • 原文地址:https://www.cnblogs.com/wdp1990/p/11236853.html
Copyright © 2011-2022 走看看