JVM工作原理

JVM的生命周期

(1)两个概念:JVM实例和JVM执行引擎实例
JVM实例对应了一个独立运行的java程序,它是进程级别的
JVM执行引擎实例则对应了属于用户运行程序的线程,它是线程级别的

(2)VM实例的诞生:当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点。
JVM实例的运行main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程main()属于非守护线程, 守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程
JVM实例的消亡:当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出

JVM的内部体系结构分为三部分

灰色区域是线程私有的;
亮色是线程共享,且存在垃圾回收(堆)。

一、类加载器(ClassLoader)子系统

作用:用来装载.class文件
在这里插入图片描述

JVM的类加载机制
(1)装载
装载过程负责找到二进制字节码并加载至JVM中
JVM通过类名、类所在的包名通过ClassLoader来完成类的加载
同样,也采用以上三个元素来标识一个被加载了的类:类名+包名+ClassLoader实例ID。
(2)连接
校验:检查载入Class文件数据的正确性(文件格式验证、元数据验证、字节码验证(0x cafe bene)、符号引用验证)
准备:准备阶段为变量分配内存并设置类变量的初始化,实例变量会随着对象分配到Java堆中。
解析:这里主要的任务是把常量池中的符号引用替换成直接引用
(3)初始化
初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化
在四种情况下初始化过程会被触发执行:
1.调用了new;
2.反射调用了类中的方法;
3.子类调用了初始化;
4.JVM启动过程中指定的初始化类。
在这里插入图片描述

类加载器(Class loader)哪几种?
启动类加载器:是由C++编写,用于加载JAVA_HOME>\lib目录下的类,也就是我们用到的基本的像String什么的都是这个加载器加载的
拓展类加载器:是用于加载<JAVA_HOME>\lib\ext中的类
应用程序类加载器:加载用户自己实现的类
自定义加载器~

双亲委派机制:
如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。双亲委派模型要求除了顶层的启动类加载器外,其余的加载器都应该有自己的父类加载器。如果没有双亲委任机制,不能保证Object类的唯一性(基础类的统一性问题,上述代码所展示的),那么系统中会存在多种不同的Object类。
双亲委派的原因:
(1)主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String。
(2)同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不同的 ClassLoader加载就是不同的两个类
在这里插入图片描述

二、执行引擎

作用:执行字节码,或者执行本地方法
2.1:解释器
读取字节码,对其进行解释并逐一执行。解释器解释字节码的速度较快,但执行速度较慢。解释器的缺点是,当一种方法多次调用时,每次都需要解释。
2.2:JIT编译器
JIT编译器消除了解释器的缺点(一次调用多次,每次解释都需要一个方法),执行引擎将使用解释器的帮助进行转换,当发现重复的代码时,它将使用JIT编译器进行编译整个字节码并将其更改为本地代码。此本地代码将直接用于重复的方法调用,从而提高系统性能。
A:中间代码生成器-生成中间代码
B:代码优化器-代码优化器负责优化上面生成的中间代码
C:目标代码生成器-目标代码生成器负责生成机器代码/本机代码
D:分析器-一个特殊组件,负责查找热点,即该方法是否被多次调用
2.3:垃圾收集器
垃圾收集器是执行引擎的一部分,它收集/删除未引用的对象。可以通过调用”System.gc()”来触发垃圾回收,但是不能保证执行。JVM的垃圾收集器仅收集那些由new关键字创建的对象。因此,如果您创建了没有new的任何对象,则可以使用finalize方法执行清理。
2.4:本地方法接口(JNI)
JNI将与本机方法库进行交互,并提供执行引擎所需的本机库。
2.5:本地方法库
它是执行引擎所需的本机库的集合。
在这里插入图片描述

三、运行时数据区

方法区:所有类级别的数据(包括静态变量)都将存储在此处。每个JVM只有一个方法区, 是一个共享资源。
堆区:所有对象及其对应的实例变量和数组都将存储在此处。由于方法和堆区域共享多个线程的内存,因此所存储的数据不是线程安全的。

堆的物理分区=新生区+养老区,如下图:

在这里插入图片描述

堆的逻辑分区=新生区+养老区+永久代(JDK1.7)JDK1.8之后永久区被替代成元数据,如下图:
元空间和永久代之间最大的区别在于:永久代使用的是JVM的堆内存,而JDK1.8之后的元空间并不在虚拟机中,而是使用本机的物理内存。
在这里插入图片描述

当我们执行new Person()时,其实是new在新生区的伊甸园区,然后往下走,走到养老区,但是并未到元空间(元空间/永久代主要保存的是JDK自身所携带的Class,Interface的元数据(rt包),jar包等,不会被垃圾回收器回收,直到JVM关闭才会释放此处内存)。

新生代GC(MinorGC过程(复制(复制算法)->清空->互换))
发生在新生代的垃圾收集动作,因为大多数Java对象存活率都不高,所以Minor GC非常频繁,一般回收速度也比较快。
1:eden、SurvivorFrom复制到SurvivorTo,年龄+1
首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到survivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄己经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1
2:清空eden、SurvivorFrom
然后,清空Eden和survivorFrom中的对象,也即复制之后有交换,谁空谁是to区。
3:SurvivorTo和SurvivorFrom互换
最后,SurvivorTo和SurvivorFrom互换,原SurvivorTo成为GC时的SurvivorFrom区。部分对象会在From和To区域中复制来复制去,如此交换15次(由」VM参数MaxTenurinThreshold
决定,这个参数默认是15)最终如果还是存活,就存入到老年代。

老年代GC(一般是由标记清除或者是标记清除与标记压缩的混合实现)(Major GC/Full GC)
指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC(但并不是绝对的)。Major GC的速度一般要比Minor GC慢上10倍以上。

虚拟机栈:对于每个线程,将会创建一个单独的运行时栈。为每个方法调用,在栈内存创建一个条目,称之为栈帧。所有局部变量都会在栈内存中创建。由于它不是共享的资源,所以是线程安全的。
栈又分为三个子实体:
A:局部变量数组-与方法相关,涉及局部变量,并在此存储相应的值。
B:操作数栈-如果需要执行任何中间操作,操作数栈将充当运行时工作空间来执行操作。
C:栈帧数据-对应于方法的所有符号存储在此处。在任何异常的情况下,捕获的区块信息将被保持在帧数据中。
栈帧的概念:java中的方法被扔进虚拟机的栈空间之后就成为“栈帧”,比如main方法,是程序的入口,被压栈之后就成为栈帧。
在这里插入图片描述

PC寄存器:每个线程都有单独的PC寄存器,用于保存当前执行指令的地址,一旦指令执行,PC寄存器将更新到下一条指令。
本地方法栈:本机方法堆栈保存本机方法信息。对于每个线程,将创建单独的本地方法栈。
程序计数器:线程私有。是一块较小的内存,是当前线程所执行的字节码的行号指示器。是Java虚拟机规范中唯一没有规定OOM(OutOfMemoryError)的区域。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值