Java虚拟机简单介绍
阅读原文时间:2021年04月20日阅读:1

安装jdk之后一般都会安装jre,在jre中就包含有Java虚拟机(jvm).
jvm是虚拟出来的,类似于简单的系统,有自己的内存管理,寄存器,程序计数器cp,环境指针和栈指针等。
1.先说Java虚拟机的内存管理  (1)寄存器
   JVM只设置了4个最为常用的寄存器。它们是:
   pc程序计数器,用于记录程序的执行。
   optop操作数栈顶指针 ,记录指向Java栈区的指针。
   frame当前执行环境指针, 记录指向Java栈区的指针。
   vars指向当前执行环境中第一个局部变量的指针,记录指向Java栈区的指针。
   所有寄存器均为32位。
(2)jvm的栈结构
  这个极为重要,对理解代码底层。
  Java栈是JVM存储信息的主要方法。Java代码中一个任何类的每一个方法调用都会创建一个栈框架,以保存该方法的状态信息。注意:这里面的栈框架是指不是一个栈结构,   而 是很多的栈结构。一般只要有{},就代表这一个栈结构。“{” 进栈,“}”出栈。
  是由一个一个的栈帧组成的后进先出的栈式结构,栈桢中存放方法运行时产生的局部变量、方法出口等信息。
  当调用一个方法时,虚拟机栈中就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,
  则继续在栈顶创建新的栈桢。
栈框架包括以下三类信息:   局部变量
  局部变量用于存储一个类的方法中所用到的局部变量。vars寄存器指向该变量表中的第一个局部变量。
  每个Java方法使用一个固定大小的局部变量集。
执行环境
  执行环境用于保存解释器对Java字节码进行解释过程中所需的信息:上次调用的方法、局部变量指针和操作数栈的栈顶和栈底指针。
  执行环境是一个执行一个方法的控制中心。例如:如果解释器要执行iadd(整数加法),首先要从frame寄存器中找到当前执行环境,而后便从执行环境中找到操作数栈,从栈   顶弹出两个整数进行加法运算,最后将结果压入栈顶。
操作数栈
  用于存储运算所需操作数及运算的结果
(3)jvm 堆
  Java类的实例所需的存储空间是在堆上分配的。解释器负责分配,记录,回收。
  用于存放类的对象实例。
  (4)jvm存储区,这个是最重要的,是jvm的躯干。任何数据的加载和方法的加载,都是从这个区域复制过去的。
   JVM有两类存储区:常量缓冲池和方法区。
   常量缓冲池用于存储类名称、方法和字段名称以及串常量。
   方法区。在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域
   方法区则用于存储Java方法的字节码。
   方法区提供加载的类的存储,也是生命周期最长的,所以这里面也包含静态变量的存储,常量存储。但是jvm存储区还存在一个缓存区,这个缓存区就是加快一些数据和信息      的读取工作,如字段,串常量(把字符串常量放到这里面,可以加快读取工作)和类名称。类名称是必然要放到缓存区的,这样可以很好的遍历寻找类。方法也是如此。就相   当于你有一个很大的数据表保存你的信息,但是你还需要一个索引记录信息的名称,关键字等信息,这样可以提供你尽快从你的较大的数据表中读取你要的数据。

 

2.jvm结构图

3.jvm的类加载器

BootStrap ClassLoader:称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等,可通过如下程序获得该类加载器从哪些地方加载了相关的jar或class文件:
 

public class BootStrapTest
{
  public static void main(String[] args)
  {
    URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
&nbsp; &nbsp; for (int i = 0; i < urls.length; i++) {
&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(urls[i].toExternalForm());
&nbsp; &nbsp; &nbsp;}
&nbsp; }
}

这是获得jdk环境

System.out.println(System.getProperty("sun.boot.class.path"));这是获取 根加载器路径

Extension ClassLoader:称为扩展类加载器,负责加载Java的扩展类库,Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。

默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。

App ClassLoader:称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

除了系统提供的类加载器以外,开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

除了引导类加载器之外,所有的类加载器都有一个父类加载器。 给出的 getParent()方法可以得到。对于 
系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。

4.程序运行

PC计数器: 每一个线程都拥有一个PC计数器,当线程启动(start)时,PC计数器被创建,这个计数器存放当前正在被执行的字节码指令(JVM指令)的地址。 Java栈: 同样的,Java栈也是每个线程单独拥有,线程启动时创建。这个栈中存放着一系列的栈帧(Stack Frame),JVM只能进行压入(Push)和弹出(Pop)栈帧这两种操作。每当调用一个方法时,JVM就往栈里压入一个栈帧,方法结束返回时弹出栈帧。如果方法执行时出现异常,可以调用printStackTrace等方法来查看栈的情况

5.jvm 底层实现的数据结构

对象在堆中的存储结构是hashtable, 每一个对象的hashcode 就是在hash表中查询的key值。

在常量区中,所有的类字节码文件中的字符信息存储到方法区中的常量池中,常量池也是一个hash表。

在方法区中类对象的存储结构也是hash表,reference堆上面的句柄池也是hash表。