JVM学习笔记

一、JVM是什么?

在这里插入图片描述
JVM (java 虚拟机) 是程序和硬件、操作系统之间的接口,它封装隐藏了针对不同硬件和操作系统的程序运行的底层实现,保证了Java语言的可移植性。Java源代码经编译器编译成字节码文件后,由类加载器装入虚拟机内存,虚拟机的可执行引擎控制字节码文件的执行过程。在这个过程中,JVM的主要任务可以从两大方面去认识:

  1. 程序的执行过程控制
  2. JVM的内存结构及管理机制

二、JVM在程序编译和执行中的作用

JVM虚拟机是程序和操作系统与硬件之间的接口,它的重要任务就是负责执行程序文件,并且保证程序的执行与平台无关,即Java语言的可移植性。

在程序执行时,会创建一个JVM的实例。该实例负责程序代码的执行过程。并且随程序执行结束而消失。
在这里插入图片描述
程序编译执行的过程主要包括三个部分(如上图):

  1. 类编译过程:源文件编译成字节码文件
  2. 类加载过程:字节码文件被加载进JVM内存
  3. 执行过程:由执行引擎负责控制执行字节码文件

1. 编译过程

字节码文件的结构

结构信息:包括class文件格式版本号及各部分的数量与大小的信息

元数据:对应于Java源码中声明与常量的信息。包含类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池

方法信息:对应Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息

2. 类加载机制(*重点)

双亲委派模型(Parents Delegation Model)

类的加载是由ClassLoader及其子类负责的。

  1. 类加载的过程,按 ClassLoader 的体系结构:
    自底向上地检查类是否加载;
    自顶向下地尝试加载类;

这样的方式,可以保证类的优先级层次关系。我们知道是否由同一个类加载器加载是判断两个类是同一个类的前提,也就是说如果类由不同的加载器加载,它们一定不是同一个类。那么对于java.lang.Object 类的加载,使用双亲委派模型,不论在哪里加载,它都会提交给最顶层的启动类加载器先加载,这样可以保证不论在哪里使用这个类,他们只会被加载一次。否则每个加载器都对他们进行加载,那么内存中将存在它的多个副本,这将造成很大的冗余和内存浪费。

  1. ClassLoader 体系结构----双亲委派模型
    在这里插入图片描述

2.1 何时加载

决定对类进行初始化的几种典型情景:

  1. 使用new创建一个新对象时
  2. 读取或调用类的静态字段和静态方法时(被final修饰的,在编译器放入常量池的除外)
  3. 初始化子类时,发现父类未被初始化时,
  4. 对类进行反射调用,发现类未被初始化时

2.2 类加载过程

类从加载到JVM内存,整个的生命周期的一般过程如下图所示:(实际上各阶段之间存在交叉混用,如解析过程也有可能发生在初始化之后。)
在这里插入图片描述

3. 执行机制

类的执行以线程为单位进行,执行过程就是对线程栈的压栈和出栈操作。

类的执行需要创建线程去执行,线程在创建过程中,会分配专属的(不与他人共享的)程序计数器和线程栈(Java虚拟机栈和本地方法栈)

栈是以栈帧为基本单位存储的,每个方法对应一个栈桢,方法调用执行的过程就是栈帧的压栈和出栈操作。【这部分的内存结构涉将在内存管理机制中详细说明

JVM提供两种执行机制:解释执行即时编译执行。(如下图)
在这里插入图片描述

字节码的执行过程会涉及到如何找到正确的方法(即确定符号引用转化直接引用的时机),如何执行方法内的字节码(解释和即时编译)

对于如何找到正确的方法可能发生在不同的阶段:

  1. 解析–发生在类加载阶段,适用于那些编译器已知,运行期不变,不会被复写的方法,如静态方法和私有方法。
  2. 静态分派----发生在编译阶段,适用于方法重载, 在编译阶段即可根据参数类型等确定调用的是哪一个方法。
  3. 动态分派----发生在运行阶段,适用于方法重写,因为只有到运行阶段才能根据实际的类型确定调用的是父类还是子类的方法。

三、JVM的内存管理机制(**重要)

1. 内存结构

JVM的内存结构笼统的可以分为堆、栈和方法区。
JVM 运行时的数据区域可以分为两大部分:线程共享的(java堆、方法区),线程私有的(程序计数器、虚拟机栈、本地方法栈),如下图所示。
在这里插入图片描述

程序计数器

记录了线程运行的位置,是JVM 内存中唯一一个不会发生溢出异常的组件。

本地方法栈

为执行Native方法服务的栈。Native方法就是用C++语言实现的方法 。
有可能出现栈溢出异常(StackOverflowError)和内存溢出异常(OutOfMemoryError)。

虚拟机栈

在这里插入图片描述
为执行Java方法服务的栈。以栈帧为单位进行存储,一个方法的调用和执行就对应了一个栈桢的压栈和出栈。栈帧的结构包括:局部变量表,操作数栈,动态链接和方法出入口信息。

—StackOverflowError(栈溢出异常):当线程申请的栈深度超出栈的内存,就会抛出栈溢出异常
—OutOfMemoryError(内存溢出异常):有的虚拟机栈可以进行动态的扩展,如果扩展后仍然不能满足线程申请内存的需求就会抛出内存溢出异常。

Java堆

这是线程共享的区域,主要是用来存放对象实例的,也是垃圾回收的主要区域。堆可以是内存上不连续的空间。可以分为新生代和老生代区域,对应垃圾回收的算法。

当堆无法分配对象空间,也无法扩展内存时,会抛出OutOfMemory异常。

方法区

也是线程间共享的区域,用来存储加载进虚拟机的类、常量和静态量。虚拟机对方法区的限制非常的宽松,可以是不连续的空间,也可以是苦丁大小的空间,并且可以选择不实现垃圾回收。

当方法区无法满足内存分配时,会抛出OutOfMemory异常。

2.内存管理------垃圾回收机制

判断一个对象是否需要回收的方法有两种:引用计数和可达性分析。
引用计数就是当统计到一个对象的引用为零时,就可以判断对象没有用可以回收了。
但是这种方法无法识别出两个互相引用且都需要回收的对象,所以可达性分析就是从GC Roots出发如果不能到达某个对象,说明该对象需要回收掉。

而关于回收的算法有以下几种

标记-清除算法

先标记出所有需要清除的对象,然后再一起回收掉。这种方法标记和回收的效率都很低,而且回收之后会造成大量的内存碎片。

标记-整理算法

标记出需要回收的对象,然后把活的对象向一个方向移动,再清除掉边界以外的内存。

复制算法

将内存分为两个部分,每次只使用一半的内存。然后将活的对象移动到另一半内存,再一次性回收掉原来的一半的内存。这种方法直接将原本的内存空间缩减到一半,代价较高。

分代回收算法

分代算法,是将内存分为新生代和老生代。
—新生代的对象存活周期短,有大量的对象死去,采用复制算法,只需复制少量存活的对象。
—老生代的对象存活率高,没有额外的空间对其复制,采用标记清除和标记整理的算法。


参考资料: 1. https://virtual.51cto.com/art/202004/615042.html 2. 深入理解JVM(第二版)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值