Terminal 4.

JVM

2024/09/28
loading

JVM 是 Java Virtual Machine 的缩写,它是一个虚构出来的计算机,一种规范。
大白话带你认识 JVM

JVM跨平台原理

本质:不同操作系统上运行的JVM是不一样的

字节码的作用

编译时:
Java代码———>字节码
运行时(解释):
① Java代码——>机器指令

| 快于
|
字节码———>机器指令

JAVA语言使用编译+解释
(python仅解释 c++纯编译性)

有其他高级语言可以编译字节码,
也可以实现跨平台(兼容多种语言

JVM整体结构

请看图片

  • 类加载子系统:把磁盘上的东西(字节码)加载到内存
  • JIT编译器:类似快表,缓存反复执行的(热点)指令不用每次编译
  • :产生的对象在里面
  • Java方法栈:把某个线程的信息,定义的局部变量等
  • 本地方法栈:native方法(jvm里面的)
  • 程序计数器:记录等一下要去执行的下一条指令的地址(方便线程切换)

类加载子系统


不同类加载器加载的目录不一样(jre/lib ;jre/lib/etc; classpath你所指定的类加载器的路径)
双亲委派(BootStrapClassLaoder-ExtClassLoader-AppClassLaoder)
子类加载类时,先用父类加载类

  • 避免类重复加载
  • 防止核心API被篡改

Tomcat为什么要自定义类加载器
为了进行类的隔离
应用A和应用B设置各自的类加载器
JVM中判断一个类是不是已经被加载的逻辑是:类名+对应的类加载器实例


运行时数据区

方法区&堆:多线程共享
Java方法栈&本地方法栈&程序计数器:各线程独享

程序计数器

PC Register

  • 物理寄存器的抽象实现
  • 记录待执行的下一条指令的地址
  • 程序控制流的指示器,循环、if-else。异常处理、线程恢复等都依赖它来完成
  • 解释器工作时就是通过它来获取下一条需要执行的字节码指令的
  • 他是唯一一个在JVM规范中没有规定任何OutOfMemoryError情况的区域(不会内存溢出)

虚拟机栈(Java栈、Java方法栈)

  • 虚拟机栈是线程私有的
  • 一个方法开始执行栈帧入栈、方法执行完对应的栈帧就出栈,所以虚拟机栈不需要进行垃圾回收
  • 虚拟机栈存在OutOfMemoryError、StackOverflowError
  • 线程太多就有可能出现OutOfMemoryError,线程创建时没有足够的内存去创建虚拟机栈了
  • 方法调用层次太多,可能就会出现StackOverflowError
  • 可以通过-Xss来设置虚拟机栈的大小

    栈帧


    操作数栈
    Operand Stack
    计算机组成原理 加法器

本地方法栈

native method,在java中定义的方法,但由其他语言实现
虚拟机栈存的是Java方法调用过程中的栈帧,本地方法栈存的是本地方法调用过程中的栈帧
也是线程私有的,也可能出现OOM和SOF

堆是JVM中最重要的一块区域,JVM规范中规定所有的对象和数组都应该存放在堆中,在执行字节码指令时,会把创建的对象存入堆中,对象对应的引用地址存入虚拟机栈中的栈帧中,不过当方法执行完之后,刚刚所创建的对象并不会立马被回收,而是要等JVM后台执行GC后,对象才会被回收

  • -Xms,指定堆的初始化内存大小
  • Xmx,指定堆的最大内存大小

垃圾回收

垃圾标记阶段

找到JVM中(主要是堆中)有那些垃圾对象

  • 引用计数法
  • 可达性分析法

引用计数法
每个对象都保存一个引用计数器属性,用户记录对象被引用次数
优点:
实现简单,计数器为0则表示是垃圾对象
缺点:

  1. 需要额外的空间来存储引用计数
  2. 需要额外的时间来维护引用计数
  3. 无法处理循环引用的问题

可达性分析法

以GC Roots为起始点,然后一层一层找到所引用的对象,被找到的对象就是存货对象,那么其他不可达的对象就是垃圾对象
此处应有
GC Roots
GC Roots是一组引用,包括:

  • 线程中虚拟机栈中正在执行的方法中方法参数、局部变量所对应的对象引用
  • 线程中本地方法栈中正在执行的方法中方法参数、局部变量所对应的对象引用
  • 方法区中保存的类信息中静态属性所对应的对象引用
  • 方法区中保存的类信息中常量属性所对应的对象引用
  • etc

    标记-清除(Mark-Sweep)算法

    针对某块内存空间,比如新生代、老年代,如果可用内存不足后,就会STW,暂定用户现成的执行,然后执行算法进行垃圾回收
  1. 标记阶段:从GC Roots开始遍历,找到可达对象,并在对象头中进行记录
  2. 清除阶段:堆内存空间进行线性遍历,如果发现对象头中没有记录是可达对象,则回收它

缺点

  1. 效率不高
  2. 内存碎片
    优点:
    思路简单

复制算法

将内存空间分为两块,每次只使用一块,在进行垃圾回收时,将可达对象复制到另外没有被使用的内存块中,然后再清除当前内存块中的所有对象,后续再按同样的流程进行垃圾回收,交换着来
优点

  1. 效率高
  2. 不会出现内存碎片
    缺点
  3. 需要更多的内存
  4. 对象存放的地址发生了改变,需要额外的时间修改栈帧中记录的地址引用
  5. 垃圾对象少时效率低

标记-整理(Mark-Compact)算法

第一阶段和标记-清除算法一样
第二阶段将所有存货对象移动到内存的一端
最后清理边界外所有的空间
效率有点低

分代收集算法

(只是一个理念)
不同对象的存活市场是不一样的,也就可以针对不同的对象采取不同的垃圾回收算法

默认几乎所有的垃圾收集器都是采用分代收集算法进行垃圾回收的

  • 新生代中的对象存活时间比较短,可以利用复制算法,他适合垃圾对象比较多的情况
  • 老年代中的对象存活时间比较长,可以用标记清除或标记整理算法,
    比如:
    • CMS垃圾收集器采用的就是标记-清除算法
    • Serial Old垃圾收集器采用的就是标记-整理算法

垃圾回收器

CMS

STW时间变短,用户体验更好,但是吞吐量变低了(单位时间内执行的用户线程更少了)

并发标记:遍历老年代,标记出所有可达对象
不需要STW,用户线程与垃圾收集线程一起执行

G1(Garbage-First)


JIT

JIT(Just-In-Time)编译器是一种动态编译技术,常用于将字节码或其他中间表示形式转换为本地机器码。JIT 编译器与传统的静态编译器不同,它在运行时根据需要进行编译,并将已编译的代码缓存以供后续重复执行。

JIT 编译器通常与虚拟机(如 Java 虚拟机)一起使用,用于提高程序的执行性能。以下是 JIT 编译器的基本工作过程:

解释执行:一开始,虚拟机会使用解释器直接解释字节码指令。这种解释执行的方式灵活但速度较慢。

热点探测:JIT 编译器会监控程序的运行情况,收集信息来确定哪些代码片段被频繁执行,即所谓的热点代码。

即时编译:当某个代码片段被判定为热点时,JIT 编译器将该片段的字节码转换为本地机器码。这个过程只发生一次,后续执行相同的代码时将直接使用已编译的机器码,不再解释执行。

缓存和优化:编译得到的机器码通常会被缓存起来,以备后续执行同样的代码。JIT 编译器还可以根据运行时的信息进行优化,例如内联函数调用、消除冗余检查等,以进一步提高性能。

通过将热点代码编译成机器码,JIT 编译器能够显著提高程序的执行速度。它在程序运行初期可能会有一些性能损失,因为需要额外的编译时间,但随着时间的推移和热点代码的不断优化,整体性能会得到提升。

JIT 编译器是许多现代虚拟机的关键组件,它们帮助解决了解释执行和静态编译之间的折衷,实现了高效的动态代码执行。


后话

0929,概念上的东西总是学得很快
但是好久没敲代码了,国庆该练练手了(捋袖子)

CATALOG
  1. 1. JVM跨平台原理
    1. 1.1. 字节码的作用
  2. 2. JVM整体结构
  3. 3. 类加载子系统
  4. 4. 运行时数据区
    1. 4.1. 程序计数器
    2. 4.2. 虚拟机栈(Java栈、Java方法栈)
      1. 4.2.1. 栈帧
    3. 4.3. 本地方法栈
    4. 4.4.
  5. 5. 垃圾回收
    1. 5.1. 垃圾标记阶段
      1. 5.1.1. 可达性分析法
    2. 5.2. 标记-清除(Mark-Sweep)算法
    3. 5.3. 复制算法
    4. 5.4. 标记-整理(Mark-Compact)算法
    5. 5.5. 分代收集算法
      1. 5.5.1. CMS
      2. 5.5.2. G1(Garbage-First)
  6. 6. JIT
  7. 7. 后话