java虚拟机
JAVA虚拟机是什么?
1.抽象规范
2.一个具体实现
3.一个运行中的虚拟机实例
java虚拟机规范只是概念,具体的实现是来自于不同的提供商,并存于多个平台,用软件实现或者软件硬件结合实现。运行一个java程序同时,也就运行了一个java虚拟机实例。
java虚拟机的生命周期
运行一个java虚拟机,java程序启动,虚拟机启动。java程序关闭,虚拟机消亡。每个java程序都有单独的JAVA虚拟机实例。
java程序初始类中的main()方法,是程序初始线程的的起点,其他任何线程都是由初始线程启动的。
java虚拟机中有两种线程,守护线程和非守护线程;守护线程就是虚拟机自己使用(比如垃圾回收任务线程),当时也可以吧任何创建的线程标记为守护线程,初始线程是非守护线程
只要有非守护线程运行,java虚拟机仍然存活。
java虚拟机的体系结构
java虚拟机规范中:虚拟机实例行为按照子系统、内存区、数据类型、指令来描述。这几种组成一起展示了虚拟机内部抽象体系结构。规范只是定义外部特征。
类装载器子系统:根据给定的全限定名来装入类型(类或接口)
执行引擎:负责执行包含在被装载类中方法的指令
运行时数据区:存储字节码、从已装载的class文件中得到的其他信息、程序创建的对象、传递给方法的参数、返回值、局部变量、以及运算的中间结果等等。
1.运行时数据区是抽象概念,结构实现的细节由具体实现的设计者决定。使得java虚拟机可以很容易的在各种计算机上实现。
2.某些运行时数据区是由程序中所有线程共享,有些是独享。每个JAVA虚拟机实例都有一个方法区以及一个堆,他们是由该虚拟机实例所有线程共享。
3.java栈是由许多栈桢组成,一个栈桢包含一个java方法的调用状态。当一个线程调用一个java方法时,虚拟机压入一个行的栈帧到改栈中,当方法返回时,这个栈帧从java栈中弹出并抛弃。
4.java虚拟机没有寄存器,其指令集使用java栈来存储中间数据。
1.数据类型
1.用int或者byte来表示boolean。
2.boolean数组当做byte数组来访问。
3.内部使用的基本类型:returnAddress,程序员不能使用,是用来实现java程序中的finally子句。
2.字长
java虚拟机中最基本的数据单元就是字,虚拟机设计者决定大小,字长必须足够支持所有类型,两个字长可以支持long和double,至少需要32位
类装载器子系统
1. JVM三种预定义类型类加载器
1.启动(Bootstrap)类加载器:引导类装入器是用本地代码实现的类装入器,它负责将 /lib下面的核心类库或-Xbootclasspath选项指定的jar包加载到内存中
2.扩展(Extension)类加载器:扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
3.系统(System)类加载器:系统类加载器是由 Sun的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径java -classpath或-Djava.class.path变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器。
2.类加载双亲委派机制
JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
系统类加载器的父类加载器是标准扩展类加载器,标准扩展类加载器的父类加载器是启动类加载器
注意:编写自定义类加载器时不建议用户覆写loadClass(…)方法,而是覆写findClass(…)逻辑。
“`
//用户自定义类加载器WrongClassLoader.Java(覆写loadClass逻辑)
public class WrongClassLoader extends ClassLoader {
public Class<?> loadClass(String name) throws ClassNotFoundException {
return this.findClass(name);
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 假设此处只是到工程以外的特定目录D:\library下去加载类
// 具体实现代码省略
}
}
“`
方法区 堆 栈
堆区:所有对象的实例分配都在Java堆上分配内存,堆大小由-Xmx和-Xms来调节
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
栈区:栈是存放线程调用方法时存储局部变量表,操作,方法出口等与方法执行相关的信息,栈大小由Xss来调节,方法调用层次太多会撑爆这个区域
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
方法区:方法区是存放虚拟机加载类的相关信息,如类、静态变量和常量,大小由-XX:PermSize和-XX:MaxPermSize来调节,类太多有可能撑爆永久带
1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
3.存放了类而不是实例,虚拟机会在需要装载的时候,通过常量池中的引用读取方法区中的class类 然后在堆中开辟出实例额的空间。
public class AppMain
//运行时, jvm 把appmain的信息都放入方法区
{
public static void main(String[] args) //main 方法本身放入方法区。
{
Sample test1 = new Sample( " 测试1 " ); //test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面
Sample test2 = new Sample( " 测试2 " );
test1.printName();
test2.printName();
}
}
堆区
1.堆空间存储者类的实例,不必是连续的内存空间
2.JAVA虚拟机没有规定JAVA对象在堆中如何表示,由JAVA虚拟机实现者决定
3.可能的两种设计:
一个句柄池,一个对象池,一个指向对象实例的指针,一个指向方法区类型数据的指针,有利于堆碎片的整理。
对象指针指向一组数据,而该数据包括对象实例数据以及指向方法区中类数据的指针。一个指针就可以访问对象,但是移动对象变得更复杂。
方法表:
不管虚拟机实现什么样的对象表示法,很可能每个对象都有一个方法表,因为加快了调用实例方法时的效率,但是JAVA虚拟机规范并未要求必须使用方法表,所以一些严格内存资源限制的代码中并不会使用
程序计数器
JAVA中的PC寄存器,大小是一个字长,能够持有一个本地指针,也能够持有一个returnAddress。PC寄存器中的内容总是吓一跳将被执行命令的”地址”。这里的地址可以是方法字节码中相对于该方法起始指令的偏移量。如果执行的是本地方法PC寄存器的值是”undefined”
java栈
启动新线程时,java虚拟机都会为他分配一个JAVA栈,栈以帧为单位保存线程的运行状态。虚拟机只会对java栈执行两种操作:已帧为单位压栈或出栈。
某个线程正在执行的方法被称为该线程的当前方法,当前方法使用的帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池。
每当线程调用一个java方法视,虚拟机都会在该线程的java栈中压入一个新帧,这个新帧就会成为当前帧,当执行这个方法时,他使用这个帧来存储参数,局部变量,中间运算结果。
方法可以用两种方式完成。一种通过return返回的,一种通过抛出异常终止。都会将该当前帧弹出java栈释放掉。
java栈上的所有数据都是私有的
和方法区一样内存不必连续。
栈帧
局部变量区,操作数栈,帧数据区。
局部变量区
JAVA栈帧的局部变量区被组织为一个以字长为单位、从0开始计数的数组,字节码指令通过从0开始的索引来使用其中的数据。类型为int、float、reference和returnAddress的值在数组中只占据一项,而类型为byte、short和char的值在存入数组前都将被转化未int值。long和double的值占据连续的两项。
局部变量区包含对应方法的参数和局部变量
操作数栈
不是通过索引来访问,通过标准的栈操作 —压栈和出栈 来访问,如果某个指令八一个值压入栈操作数栈中,稍后另一个指令就可以弹出这个值来使用。
java虚拟机没有寄存器 JAVA中的指令是从栈操作数栈中回去操作数的。
帧数据区
支持常量池解析,正常返回以及异常的派发机制
本地方法栈
java线程调用本地方法时,会保持java栈不变,不再在线程的JAVA栈中压入新的帧,虚拟机只是简单的动态连接并直接调用指定的本地方法