Java虚拟机

一.Java虚拟机的生命周期

1.Java虚拟机的责任是负责运行Java程序,启动一个Java程序时,产生一个虚拟机实例,当程序关闭退出,虚拟机实例即随之消亡,每一个程序运行于自己的Java虚拟机实例中。

2.JVM通过调用某个初始类的main()方法运行Java程序

3.Java程序初始类中的main()方法,作为该程序初始线程的起点(Java程序的执行是由JVM负责完成的,而JVM是由一个个线程构成的),执行main()方法的线程成为主线程,其他线程均由主线程创建。

4.JVM中有两种线程:守护线程与非守护线程

  守护线程是JVM自己使用的线程,即后台线程,为前台线程提供服务支持

  非守护线程是执行Java程序的执行线程,在Java程序中可以指定创建的线程是守护线程,但是一个Java程序的主线程一定是非守护线程

  只要有任何非守护线程在执行,说明Java程序尚未执行完毕,JVM实例仍然存活,当Java程序中所有的非守护线程都终止时,JVM将自动退出。

5.每一个JVM都有一个类装载器子系统,根据给定的全限定名来装入类型,每一个JVM同样有一个执行引擎,负责执行包含在被装载类的方法中的指令。

6.JVM运行一个程序时,需要内存存储很多东西,如字节码,从装载的class文件中得到的其他信息(注意类型在方法区中的描述不是完全拷贝class文件中的内容,而是对class文件进行抽取整合分类,从而形成完整的描述),程序创建的对象,传递给方法的参数,返回值,局部变量,以及运算的中间结果;

7.某些运行时数据区是由程序中所有线程共享的(一个Java程序在一个JVM中运行,有一个内存运行时数据区,不同的Java程序有不同的运行时数据区,它们之间互不干扰),还有一些只能由一个线程拥有,每一JVM实例都有一个方法区和一个堆区,它们有该JVM实例的所有线程共享;

  方法区:JVM装载一个class文件时,从这个class文件中解析类型信息,然后把这些类型信息放到方法区中;

  堆区:程序运行时,JVM会将程序在运行时创建的对象放到堆中。

  以上两个区域是一个JVM中所有的线程所共享的

 

  以下两个区域是单独一个线程所独有的

  PC寄存器:当一个新的线程被创建时,将得到自己的PC寄存器以及一个Java栈

  PC寄存器指向下一个被执行的指令,Java栈存储该线程中Java方法调用的状态:包括传入参数,返回值,局部变量,运算的中间结果等

 Java栈的每一个存储单元称为栈帧,每一个调用的Java方法对应于一个栈帧,记录方法的参数等信息,当JVM新调用一个方法时,代表该方法的栈帧压栈,当方法执行结束后,代表该方法的栈帧从栈中弹出

  Java虚拟机没有寄存器,指令集采用Java栈来存储中间数据

  Java栈和PC寄存器是每一个线程私有的,任何一个线程不能访问其他线程的Java栈和PC寄存器

 

 二. JVM中的数据类型 

.Java虚拟机的数据类型分为两种:基本类型和引用类型,基本类型的变量持有原始值,原始值是真正的原始数据,而引用类型的变量持有引用值,引用值是某个对象的引用

  1  JVM中的基本类型

  Java语言中的基本类型也是JVM中的基本类型,boolean除外,JVM的指令集对boolean只有很有限的支持,当编译器将java源代码编译成字节码时,它会用int或者byte来表示boolean,在JVM中用int和byte表示boolean的值,并不对boolean特殊定义,在JVM中,false有整数零表示,所有的非零整数都表示true,设计boolean值的操作使用int处理,boolean数组当作byte数组访问;

 除了boolean之外,Java语言中的基本类型构成JVM中的数值类型,JVM中的数值类型有两种:整数类型(byte,short,int,long,char)和浮点类型(float,double),和java语言一样,JVM的基本类型的值域在任何地方都一样,无论底层的主机或者操作系统是什么,byte 8位,

char short 16位,  int 32位, long 64位

  2. JVM中的引用类型

   JVM中的引用类型包括类引用类型(对类实例的引用),数组引用类型(对数组对象的引用)和接口引用类型(对实现该接口的某个类的实例的引用)

   null表示引用变量没有引用任何对象

 

三. JVM中的类装载器

    1. JVM中的类装载器分为两种:启动类装载器和用户自定义类装载器,前者是JVM实现的一部分,后者是JAVA程序的一部分

    2.不同的类装载器装载的类放在JVM内部的不同命名空间中(内存运行时数据区的方法区中的不同的命名空间中)

    3.用户自定义的类装载器只是普通的Java对象,它的类必须派生自java.lang.ClassLoader类,ClassLoader中定义的方法为程序提供访问类装载器机制的接口

      对于每一个被装载的类型,JVM会为它创建一个java.lang.Class类的实例代表该类型,

      用户自定义的类装载器以及Class类的实例都放在内存的堆区中(即Java程序运行过程中动态创建的对象,用户创建的类加载器不过是一个对象而已)而装载的类型信息位于方法区中

    4.每一个JVM都要有一个启动类装载器,它知道怎么装载受信任的类,比如Java API中的class文件

    5.尽管用户自定义的类装载器是Java程序的一部分,但是类ClassLoader中的四个方法是通往java虚拟机的通道(所有的用户自定义的类装载器都继承ClassLoader类),用户自定义的类装载器也是类装载器的一种,她们需要获得类装载器的机制,而ClassLoader类提供这些机制,因此用户自定义的类装载器要继承ClassLoader类

   defineClass    findSystemClass   resolveClass

  JVM的实现需要将上述方法连接到内部的类装载器子系统中

  6.每一个类装载器都有自己的命名空间,由相同类装载器装载的类放入同一个命名空间中,有不同的类装载器装载的类在不同的命名空间中;

  7.类装载的过程:被装载的类型信息放在内存运行时数据区的方法区内存中,当虚拟机装载某个类型时,

    (1)用类装载器定位相应的class文件

    (2)读入这个class文件--》线性的二进制数据流

    (3)提取该数据流中的类型信息,并将这些信息存储到方法区中

 

  7.1关于方法区:

     a. 所有线程共享,因此对方法区的数据访问必须是线程安全的,同一时刻只能由一个线程加载某个类,不能多个线程同时加载同一个类;

     b.方法区可以被垃圾收集,因为可以自定义类装载器,使得Java程序具有动态扩展性,用户自定义的类装载器装载的类可能会成为程序中“不再引用的”类,当某个类不再被引用时,JVM可以卸载这个类,进行垃圾回收,调用GC线程,从而使方法区占据的内存保持最小(对象不再被引用:没有变量引用对象  类不再被引用:类的Class对象不再被引用)

    c.方法区保存的内容:

       类型的全限定名+类型的直接超类的全限定名+类型的直接接口的权限定名列表+类的修饰符+类型是类类型还是接口类型

       以上是基本信息

       还有以下信息:

      a. 类型的常量池:该类型所用常量的有序集合,包括直接常量和对其他类型,字段和方法的符号引用(直接常量+符号引用),常量池存储了相应类型所用到的所有类型,字段和方法的符号引用,因此在Java程序的动态连接中起核心的作用

      b.字段信息  字段名 类型 修饰符

      c.方法信息  方法名 参数列表 返回值  操作数   修饰符  方法的字节码  栈帧信息  异常信息

      d.类变量 静态变量  所有实例共享

         编译时类变量(使用final修饰的类变量),保存在常量池或者方法的字节码流中

      e.指向ClassLoader类的引用  如果类型是由用户自定义的类加载器装载的,虚拟机需要保存对该装载器的引用

         当某个类型引用另一个类型时,虚拟机会请求发起引用类型的类装载器(因此要保存对类加载器的引用)来装载被引用的类型,这个动态链接的过程,对于虚拟机分离命名空间的方式至关重要,为了正确执行动态链接和维护命名空间,需要保存对类加载器的引用

      f.指向Class类的引用  每一个被装载的类型(无论是类类型还是接口类型)虚拟机会为它创建一个java.lang.Class类的实例,放在运行时数据区的堆区中,而且虚拟机需要把这个实例和存储在方法区中的类型数据关联起来(Java程序不能够直接得到方法区中的类型描述信息,只能够通过Class对象得到),因此JVM需要提供一种机制将Class对象和方法区中的类型描述信息关联起来

     g.Class对象和方法区的类型描述信息的关联方式

          java.lang.Class类提供的方法:public static Class forName(String className)

         参数是一个类型的全限定名,得到该类型的Class对象的引用,这是通过类型得到类型的Class对象的引用

         还可以通过对象获得类型的Class对象的引用

         可以调用任何对象的getClass()方法,获得该对象类型的Class对象的引用,例如调用Integer对象的getClass()方法,就可以得到表示java.lang.Integer类的Class对象

         Class类的作用是提供对方法区类型的描述信息,它提供的描述信息在方法区中的类型描述中均能够找到

         例如:

          public Strng getName()  public Class getSuperClass()  public boolean isInterface()  public Class[] getInterfaces

          public ClassLoader getClassLoader()

       h. 方法表

          类型的所有实例可能被调用的实例方法的直接引用,包括从超类继承过来的实例方法

 

          虚拟机如何使用方法区中的信息??

         

(1).JVM找到初始类Volcano.class,然后将class文件以二进制流的形式读入到JVM中,并从中提取出相关的描述信息保存在方法区中

(2) 通过执行方法区中的字节码,JVM开始执行main()方法,在执行时,JVM一直持有当前类型的常量池(保存所有的常量以及符号引用)的指针

(3)JVM不会等到所有的类都加载到内存中之后才开始执行程序,因为Java具有动态连接的特性,因此只有在需要某个类型的时候,才将该类型加载进内存数据区的方法区中,因此,JVM在执行Java程序时,并不是所有的类型都已经加载到内存中了。

 (4)main()中的第一条指令告知虚拟机为类型的常量池第一项的类分配足够的内存,JVM使用指向Volcano常量池的指针找到第一项,发现它是一个对Lava类的符号引用,然后它检查方法区,看Lava类是否已经被装载了。   

  (5)当JVM发现Lava类没有被装载的时候,它会去找Lava.class文件并将二进制代码读入JVM中,提取出相关信息保存到内存运行时数据区的方法区中,并保存Lava类型的常量池的指针

  (6)接着,JVM用一个直接指向方法区Lava类数据的指针来替换常量池中的第一项(就是原来的符号引用),以后可以通过这个指针快速地访问Lava类,即是将常量池中的符号引用替换为直接引用,这样就可以很方便地获得类型的各种信息

  (7)JVM为一个新的Lava对象分配内存,为Lava对象分配内存(所以一定要先声明《声明时用实际指针替换符号引用》再初始化《初始化类的对象》),因为新建一个类型的实例时需要获得实例类型的描述信息,因此要有指向内存方法区类型描述的指针,才能够进一步进行初始化对象的操作,通过常量池中的指针找到一个Lava对象需要分配多少堆空间;

 (8)确定堆对象的大小之后,在对上分配空间,并将对象的实例变量赋值为默认初始值0,如果Lava类的超类Object也有实例变量,此时也会赋值为默认值

  (9)将新生成的Lava对象的引用压到栈(main方法的栈帧,因为JVM没有寄存器)中,接下来可以通过这个引用调用Java代码,初始化实例变量,调用Lava对象的flow()方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值