JVM学习笔记一:JVM体系结构概览

一、jvm总览:

jvm的位置:在运行操作系统上,与硬件没有直接的交互。
jvm与操作系统,底层硬件三者的关系如下:
在这里插入图片描述

下面是JVM虚拟机的结构图:

在这里插入图片描述
方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序计数器是运行是线程私有的内存区域。
下面几个部分逐个对上图的各个部分进行剖析:

二、类加载器Class Loader

类加载器负责加载class文件,class文件在文件开头有特定的文件标示,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构。
类加载器只负责class文件的加载,至于它是否可以运行,则由Execution Engine执行引擎决定 。
在这里插入图片描述
如上图:类加载器将javac编译过的字节码文件进行加载,并初始化为一个Class,这里的Class相当于一个类的模版,jvm中这个类的实例都要按照这个模版实例化。

  • 类加载的双亲委派机制:

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。 即所有类的加载先去找最顶层的加载器,找不到再继续向下找,直到找到为止。

采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object对象。

jvm自带的类加载器及层级关系如下:
在这里插入图片描述
如上图,虚拟机自带的加载器可分为四种,从顶层到下一层分别为:

  • 启动类加载器(Bootstrap):加载rt.jar包内的类,如Object等。
  • 扩展类加载器(Extension):加载ext目录下所有jar包的类。
  • 应用程序类加载器(AppClassLoader):一般为用户自己写的类。
    下面通过一个实例来证明类三种加载器父子关系,需要注意的是:bootstrap最顶层启动类加载器是获取不到的,获取时会输出null:
public class ClassLoaderDemo {
    public static void main(String[] args) {
        Object o = new Object();
        System.out.println(o.getClass().getClassLoader().getParent());//会直接报错java.lang.NullPointerException
        System.out.println(o.getClass().getClassLoader());//null 启动类加载器java内获取不到

        System.out.println("*************************************");


        MyObject myObject = new MyObject();
        System.out.println(myObject.getClass().getClassLoader().getParent().getParent());//启动类加载器 : null
        System.out.println(myObject.getClass().getClassLoader().getParent());//扩展类加载器 extension
        System.out.println(myObject.getClass().getClassLoader());//应用程序类加载器 application
    }
}

class MyObject{

}

运行结果如下(获取bootstrap的父加载器会报错,运行时已经注释掉):

null //启动类加载器,获取不到
*************************************
null//启动类加载器,获取不到
sun.misc.Launcher$ExtClassLoader@1540e19d//extend扩展类加载器
sun.misc.Launcher$AppClassLoader@18b4aac2//应用程序加载器

三、执行引擎,native相关,程序计数器,方法区

  • Execution Engine执行引擎负责解释命令,提交操作系统执行

  • 本地方法栈和本地接口:在本地方法栈中登记native方法,在Execution Engine 执行时加载本地方法库。本地接口的作用是融合不同的编程语言为 Java 所用,可以实现java与底层硬件的交互,企业级应用较少。

  • 程序计数器:是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
    这块内存区域很小,它是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。用以完成分支、循环、跳转、异常处理、线程恢复等基础功能。不会发生内存溢出(OutOfMemory=OOM)错误

  • 供各线程共享的运行时内存区域。它存储了每一个类的结构信息,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容。可以理解为存储了类的模版结构,模版内包括方法,构造函数等等类的组成部分。

四、栈

栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。8种基本类型的变量+对象的引用+实例方法都是在函数的栈内存中分配

1.栈存储什么?:

栈帧中主要保存3 类数据:

  • 局部变量(Local Variables):输入参数和输出参数以及方法内的变量。

  • 栈操作(Operand Stack):记录出栈、入栈的操作。

  • 栈帧数据(Frame Data):包括类文件、方法等等。

2.栈运行原理:

栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧 F1,并被压入到栈中,A方法又调用了 B方法,于是产生栈帧 F2 也被压入栈,B方法又调用了 C方法,于是产生栈帧 F3 也被压入栈,
……
执行完毕后,先弹出F3栈帧,再弹出F2栈帧,再弹出F1栈帧……

遵循“先进后出”/“后进先出”原则。

每个方法执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体JVM的实现有关,通常在256K~756K之间,与等于1Mb左右。

即:调用哪个方法,哪个方法入栈,执行完哪个方法,哪个方法就需要出栈,出栈时要依次先将栈顶的栈帧弹出,再弹出自己。

java.lang.StackOverflowError

往往是因为栈内存溢出,造成的原因经常是有方法递归调用自己,没有递归终止条件。

    private static void m1(){
        m1();
    }

    public static void main(String[] args) {
        System.out.println("执行前");
        m1();//Exception in thread "main" java.lang.StackOverflowError
        System.out.println("执行后");//无法输出
    }

3.栈+堆+方法区的交互关系

在这里插入图片描述
对于一个对象:

  • 栈中保存的是对象在堆中实例的引用
  • 堆中保存对象的实例以及对象在方法区中的对象类型的引用
  • 方法区保存的是对象的模版,对象的类型数据。
发布了16 篇原创文章 · 获赞 2 · 访问量 446

猜你喜欢

转载自blog.csdn.net/qq_31314141/article/details/104238810