java虚拟机
1、Java虚拟机是什么
“Java虚拟机“可以指三种不同的东西
抽象规范
一个具体的实现
一个运行中的虚拟机实例
当运行一个Java程序的同时,也就是在运行一个Java虚拟机实例
2、Java虚拟机的生命周期
当启动一个Java程序时,一个虚拟机实例也就诞生了,当该程序关闭退出时,这个虚拟机实例也就随之消亡。
在java虚拟机内部有两种线程:守护线程和非守护线程。当该程序中所有的非守护线程都终止时,虚拟机实例将自动退出。
3、Java虚拟机的体系结构
一个虚拟机实例的行为是分别按照子系统、内存区、数据类型以及指令这几个术语来描述的。
3.1、数据类型
Java语言中所有的基本类型同样也都是Java虚拟机中的基本类型。但boolean有点特别,指令集对boolean只有很有限的支持。当编译器把Java源码编译为字节码的时,它会用int或byte来表示boolean。Boolean数组是当byte数组来访问的。
returnAddress是Java虚拟机内部使用的基本类型,这个类型被用来实现Java程序中的finally子句。
3.2、类装载器子系统
负责查找并装载的那部分被称为类装载器子系统。
分为启动类装载器和用户自定义类装载器
由不同的类装载器装载的类将放在虚拟机内部的不同命名空间中。
用户自定义的类装载器以及Class类的实例都放在内存的堆区,而装载的类型信息则都位于方法区。
装载顺序:
1)装载——查找并装载类型的二进制数据
2)连接——执行验证(确保被导入类型的正确性),准备(为类变量分配内存,并将其初始化为默认值),以及解析(把类变量中的符号引用转换为正确的初始值)
3)初始化——把类变量初始化为正确的初始值
3.3方法区
在java虚拟机中,关于被装载类型的信息存储在一个逻辑上被称为方法区的内存中。
所有线程都共享方法区。
类型信息:
这个类型的全限定名
这个类型的直接超类的全限定名
这个类型是类类型还是接口类型
这个类型的访问修饰符
任何直接超接口的全限定名的有序列表
该类型的常量池
字段信息
方法信息
除了常量以外的所有类(静态)变量
一个到类ClassLoader的引用
一个到Class类的引用
其中字段信息包括
字段名
字段类型
字段的修饰符
方法信息包括
方法名
方法的返回类型
方法参数的数量和类型
方法的修饰符
如果方法不是抽象的和本地的还须有
方法的字节码
操作数栈和该方法的栈帧中的局部变量的大小
异常表
3.4 堆
Java程序在运行时所创建的所有类实例或数组都放在同一个堆中。
Java对象中包含的基本数据由它所属的类及其所有超类声明的实例变量组成。只要有一个对象引用,虚拟机就必须能快速的定位对象实例的数据,另外,它必须能通过该对象引用访问相应的类数据,因此对象中通常有一个指向方法区的指针。
一种可能的堆空间设计就是,把堆分为两部分:一个句柄池,一个对象池。
这种设计的好处是有利于堆碎片的整理,缺点是每次访问对象的实例变量都需要经过两次指针传递。
另一种设计方式是使对象直接指向一组数据,而数据包括对象实例数据以及指向方法区类数据的指针。这种设计方式的优点是只需要一个指针就可以访问对象的实例数据,但是移动对象就变得更加复杂。
堆中其他数据:
1、对象锁,用于协调多个线程访问一个对象时的同步。
2、等待集合
3、与垃圾收集器有关的数据。
4、方法表:加快了调用实例方法时的效率。
方法表指向的实例方法数据包括以下信息:
此方法的操作数栈和局部变量区的大小
此方法的字节码
异常表
这些信息足够虚拟机去调用一个方法了,方法表包含有方法指针——指向类活或超类声明的方法的数据
3.5程序计数器
对于一个运行中的Java程序而言,其中的每一个线程都有它自己的PC(程序计数器),在线程启动时创建。大小是一个字长。当线程执行某个Java方法时,PC的内容总是下一条将被指向指令的“地址”。如果该线程正在执行一个本地方法,那么此时PC的值为”undefined”。Java栈
3.6Java栈
每当启动一个线程时,Java虚拟机都会为它分配一个Java栈,Java栈也帧为单位保存线程的运行状态,虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈和出栈。
某个线程正在执行的方法被称为该线程的当前方法,当前方法使用的栈帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池,在线程执行一个方法时,它会跟踪当前类和当前常量池。
每当线程调用一个方法时,虚拟机都会在该线程的Java栈中压入一个新帧,而这个新栈自然就成为当前帧。在执行这个方法时,它使用这个帧来存储参数、局部变量、中间运算结果等等数据。
Java栈上的所有数据都是数据都是此线程私有的。
3.7栈帧
栈帧由三部分组成:局部变量区、操作数栈和帧数据区。局部变量区和操作数栈的大小要视对应的方法而定,编译器在编译器时就确定的确定了这些值并放在class文件中。帧数据区的大小依赖于具体的实现。
当虚拟机调用一个方法时,它从对应类的类型信息中得到此方法的局部变量区和操作数栈的大小,并据此分配栈帧内存,然后压入Java栈中。
局部变量区:Java栈帧的局部变量区被组织为以一个字长为单位、从0开始计数的数组。字节码指令通过从0开始的索引来使用其中的数据。
局部变量区对应方法的参数和局部变量。编译器首先按声明的顺序把这些参数放入局部变量数组。
在java中,所有的对象都按引用传递,并且都存储在堆中,永远都不会在局部变量区或操作数栈中发现对象的拷贝,只会有对象的引用。
操作数栈:操作数栈也是被组织为一个字长为单位的数组。但它不是通过索引来访问,而是通过标准的栈操作——压栈和出栈来访问的。
帧数据区:支持解析常量池解析、正常方法返回以及异常派发机制。每当虚拟机要执行某个需要用到常量池数据的指令时,它都会通过帧数据区中指向常量池的指针来访问它。常量池中对类型、字段和方法的引用在开始时都是符号。当虚拟机在常量池中搜索时,如果遇到类、接口、字段或者方法的入口,假若它们仍然是符号,虚拟机那时候才会进行解析。
3.8执行引擎
指令集:方法的字节码流是由Java虚拟机的指令序列构成的。每一条指令包含一个单字节的操作码,后面跟随0个或多个操作数。操作码本身就已经规定了它是否需要跟随操作数,以及如果有操作数它是什么形式的。当虚拟机执行一条指令的时候,可能使用当前常量池中的项、当前帧的局部变量中的值,或者当前帧操作数栈顶端的值。
执行技术:解释、即时编译、字适应优化、芯片级直接执行。
Hotspot虚拟机就采用了自适应优化。自适应优化虚拟机开始的时候对所有的代码都是解释执行,但是它会监视代码的执行情况。大多数程序花费80%-90%的时间来执行10%-20%的代码。虚拟机可以意识到那些方法是程序的热区——就是那10%-20%的代码,他们占整个执行时间的80%-90%。当自适应优化的虚拟机判断出某个特定的方法是瓶颈的时候,它启动一个后台线程,把字节码编译成本地代码,非常仔细的优化这些本地代码。