JVM的组成
JVM 是 Java 虚拟机 Java Virtual Machine 的缩写,JVM 是一种用于计算设备的规范。引入 Java 语言虚拟机后,Java 语言在不同平台上运行时不需要重新编译。Java 语言使用 Java 虚拟机屏蔽了与具体平台相关的信息,使得 Java 语言编译程序只需生成在 Java 虚拟机上运行的目标代码(字节码),就可以在不同机器上进行运行。
1 类加载器
1、JVM三种预定义类型类加载器
当JVM启动的时候,Java开始使用如下三种类型的类加载器:
启动(Bootstrap)类加载器:启动类加载器是用本地代码实现的类加载器,它负责将JAVA_HOME/lib下面的核心类库或-Xbootclasspath选项指定的jar包等虚拟机识别的类库加载到内存中。由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用。具体可由启动类加载器加载到的路径可通过System.getProperty(“sun.boot.class.path”)查看。
扩展(Extension)类加载器:扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,它负责将JAVA_HOME /lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器,具体可由扩展类加载器加载到的路径可通过System.getProperty(“java.ext.dirs”)查看。
系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,它负责将用户类路径(java -classpath或-Djava.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路径,如第四节中的问题6所述)下的类库加载到内存中。开发者可以直接使用系统类加载器,具体可由系统类加载器加载到的路径可通过System.getProperty(“java.class.path”)查看。
2 运行时数据区
2.1方法区(元数据区)
方法区保存在着被加载过的每一个类的信息;这些信息由类加载器在加载类的时候,从类的源文件中抽取出来;static变量信息也保存在方法区中;可以看做是将类(Class)的元数据,保存在方法区里;方法区是线程共享的;方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。jvm也可以允许用户和程序指定方法区的初始大小,最小和最大限制;方法区同样存在垃圾收集,因为通过用户定义的类加载器可以动态扩展Java程序,这样可能会导致一些类,不再被使用,变为垃圾。这时候需要进行垃圾清理。
2.2 堆
堆区 Heap 是 JVM 中最大的一块内存区域,基本上所有的对象实例都是在堆上分配空间。堆区细分为年轻代和老年代,其中年轻代又分为 Eden、S0、S1 三个部分,他们默认的比例是 8:1:1 的大小。对象一般是在新生代创建,经过引用计数法和可达性分析算法进行垃圾的标记回收,最终存活的对象在老年代中被调用,当然有些比较大的对象会直接在老年代中被创建。
2.3 虚拟机栈
2.3.1概念
由于跨平台性的设计, Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。
栈是运行时的单位,而堆是存储的单位。即: 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
2.3.2Java虚拟机栈是什么?
Java虚拟机栈(Java virtual Machine stack) ,早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(stack Frame) ,对应着一次次的Java方法调用。
线程私有
生命周期生命周期和线程一致
主管Java程序的运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。
栈的特点:
栈是一种快速有效的分配存储方式,访问速度仪次于程序计数器
JVM直接对Java栈的操作只有两个:每个方法执行伴随着进栈(入栈、压栈) 和 执行结束后的出栈工作
对于栈来说不存在垃圾回收问题GC,但存在内存移出问题OOM
2.4程序计数器
概念
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
程序计数器的特点
1.线程隔离性,每个线程工作时都有属于自己的独立计数器。
2.执行java方法时,程序计数器是有值的,且记录的是正在执行的字节码指令的地址(参考上一小节的描述)。
3.执行native本地方法时,程序计数器的值为空(Undefined)。因为native方法是java通过JNI直接调用本地C/C++库,可以近似的认为native方法相当于C/C++暴露给java的一个接口,java通过调用这个接口从而调用到C/C++方法。由于该方法是通过C/C++而不是java进行实现。那么自然无法产生相应的字节码,并且C/C++执行时的内存分配是由自己语言决定的,而不是由JVM决定的。
4.程序计数器占用内存很小,在进行JVM内存计算时,可以忽略不计。
5.程序计数器,是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域。
2.5本地方法栈
特点
本地方法栈(Native Method Stacks)与 Java 虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。
3 执行引擎
概念
执行引擎是Java虚拟机核心的组成部分,它是用于负责装载字节码到其内部,但是字节码并不能直接在操作系统上运行,那么执行引擎就是将字节码指令解释/编译为对应平台上的本地机器指令。简单来说,JVM执行引擎充当了将高级语言翻译为机器语言的翻译者。
执行引擎的工作过程
1、执行引擎在执行的过程中究竟需要执行什么样的字节码指令安全依赖于PC寄存器。
2、每当执行完一项指令操作后,PC寄存器就会更新下一条需要被执行的指令地址。
3、当然方法在执行的过程中,执行引擎有可能通过存储在局部变量表中的对象引用准确定位到存储在Java堆区中的对象实例信息,以及通过对象头中的元数据指针定位到目标对象的类型信息。