首页 > 代码库 > Java虚拟机体系结构

Java虚拟机体系结构

在了解jvm的结构之前,我们有必要先来了解一下操作系统的内存基本结构:
在此输入图片描述
操作系统中的jvm

在此输入图片描述
为什么jvm的内存是分布在操作系统的堆中呢??因为操作系统的栈是操作系统管理的,它随时会被回收,所以如果jvm放在栈中,那java的一个null对象就很难确定会被谁回收了,那gc的存在就一点意义都没有了,而要对栈做到自动释放也是jvm需要考虑的,所以放在堆中就最合适不过了。
操作系统+jvm的内存简单布局
在此输入图片描述
从上图中,你可以看到,jvm的内存结构居然和操作系统的结构惊人的一致,再来看一个图。看我下面红色的标注
在此输入图片描述

你应该不难发现,原来jvm的设计的模型其实就是操作系统的模型,基于操作系统的角度,jvm也就是一个应用(java.exe/javaw.exe),而基于class文件来说,jvm就是个操作系统,而jvm的方法区,也就相当于操作系统的硬盘区,所以方法区也被叫做permanent区,因为这个单词是永久的意思,也就是永久区。而java栈和操作系统栈是一致的,无论是生长方向还是管理的方式,至于堆嘛,虽然概念上一致目标也一致,分配内存的方式也一直(new,或者malloc等等),但是由于他们的管理方式不同,jvm是gc回收,而操作系统是程序员手动释放,所以在算法上有很多的差异.
看下面的图。
在此输入图片描述
将这个图和上面的图对比多了什么?没错,多了一个pc寄存器,所谓pc寄存器,无论是在虚拟机中还是在我们虚拟机所寄宿的操作系统中功能目的是一致的,计算机上的pc寄存器是计算机上的硬件,本来就是属于计算机,计算机用pc寄存器来存放“伪指令”或地址,而相对于虚拟机,pc寄存器它表现为一块内存(一个字长,虚拟机要求字长最小为32位),虚拟机的pc寄存器的功能也是存放伪指令,更确切的说存放的是将要执行指令的地址,它甚至可以是操作系统指令的本地地址,当虚拟机正在执行的方法是一个本地方法的时候,jvm的pc寄存器存储的值是undefined,所以你现在应该很明确的知道,虚拟机的pc寄存器是用于存放下一条将要执行的指令的地址(字节码流)。
在此输入图片描述
这个图是要告诉你,当一个classLoder启动的时候,classLoader的生存地点在jvm中的堆,然后它会去主机硬盘上将A.class装载到jvm的方法区,方法区中的这个字节文件会被虚拟机拿来new A字节码(),然后在堆内存生成了一个A字节码的对象,然后A字节码这个内存文件有两个引用一个指向A的class对象,一个指向加载自己的classLoader

如下图。
在此输入图片描述

下面你看我贴出的一个类的基本结构。

import java.io.Serializable;
public final class ClassStruct extends Object implements Serializable{//1.类信息
    //2.对象字段信息
    private String name;
    private int id;
    //4.常量池
    public final int CONST_INT=0;
    public final String CONST_STR="CONST_STR";
    //5.类变量区
     public static String static_str="static_str";
    //3.方法信息
    public static final String getStatic_str ()throws Exception{
        return ClassStruct.static_str;
    }
}

你将上面的代码注解和上面的那个字节码码内存块按标号对应一下,就会发现,其实内存的字节码块就是完整的把你整个类装到了内存而已。
所以各个信息段记录的信息可以从我们的类结构中得到。

1.类型信息:也就是public final class ClassStruct extends Object implements Serializable这段描述的信息提取
修饰符(public final)
是类还是接口(class,interface)
类的全限定名(Test/ClassStruct.class)
直接父类的全限定名(java/lang/Object.class)
直接父接口的权限定名数组(java/io/Serializable)

2.字段信息:也就是类似private String name;这段描述信息的提取
修饰符(pirvate)
字段类型(java/lang/String.class)
字段名(name)

3.方法信息:也就是对方法public static final String getStatic_str ()throws Exception的字节码的提取
修饰符(public static final)
方法返回值(java/lang/String.class)
方法名(getStatic_str)
参数需要用到的局部变量的大小还有操作数栈大小
方法体的字节码
异常表(throws Exception)

4.常量池:
4.1.直接常量:
4.1.1CONSTANT_INGETER_INFO整型直接常量池 public final int CONST_INT=0;
4.1.2CONSTANT_String_info字符串直接常量池 public final String CONST_STR=“CONST_STR”;
4.1.3CONSTANT_DOUBLE_INFO浮点型直接常量池
等等各种基本数据类型基础常量池

4.2.方法名、方法描述符、类名、字段名,字段描述符的符号引用
也就是所有编译器能够被确定,能够被快速查找的内容都存放在这里,它像数组一样通过索引访问,就是专门用来做查找的。
编译时就能确定数值的常量类型都会复制它的所有常量到自己的常量池中,或者嵌入到它的字节码流中。作为常量池或者字节码流的一部分,编译时常量保存在方法区中,就和一般的类变量一样。但是当一般的类变量作为他们的类型的一部分数据而保存的时候,编译时常量作为使用它们的类型的一部分而保存

5.类变量:
就是静态字段( public static String static_str=“static_str”;)
虚拟机在使用某个类之前,必须在方法区为这些类变量分配空间。

6.一个到classLoader的引用,通过this.getClass().getClassLoader()来取得为什么要先经过class呢?思考一下,然后看第七点的解释,再回来思考

7.一个到class对象的引用,这个对象存储了所有这个字节码内存块的相关信息。所以你能够看到的区域,比如:类信息,你可以通过this.getClass().getName()取得所有的方法信息,可以通过this.getClass().getDeclaredMethods(),字段信息可以通过this.getClass().getDeclaredFields(),等等,所以在字节码中你想得到的,调用的,通过class这个引用基本都能够帮你完成。因为他就是字节码在内存块在堆中的一个对象

8.方法表,如果学习c++的人应该都知道c++的对象内存模型有一个叫虚表的东西,java本来的名字就叫c++- -,它的方法表其实说白了就是c++的虚表,它的内容就是这个类的所有实例可能被调用的所有实例方法的直接引用。也是为了动态绑定的快速定位而做的一个类似缓存的查找表,它以数组的形式存在于内存中。不过这个表不是必须存在的,取决于虚拟机的设计者,以及运行虚拟机的机器是否有足够的内存

Java虚拟机体系结构