JVM【类加载与GC垃圾回收机制】(上)

简介: JVM【类加载与GC垃圾回收机制】

🍎一.JVM


🍒1.1JVM简介


JVM 是 Java Virtual Machine 的简称,意为 Java虚拟机

虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统

常见的虚拟机:JVM、VMwave、Virtual Box

JVM 和其他两个虚拟机的区别:


  1. VMwave与VirtualBox是通过软件模拟物理CPU的指令集,物理系统中会有很多的寄存器
  2. JVM则是通过软件模拟Java字节码的指令集,JVM中只是主要保留了PC寄存器,其他的寄存器都进行了裁剪
    JVM 是一台被定制过的现实当中不存在的计算机


🍒1.2JVM执行流程


程序在执行之前先要把java代码转换成字节码(class文件),JVM 首先需要把字节码通过一定的方式类加载器(ClassLoader) 把文件加载到内存中 运行时数据区(Runtime Data Area) ,而字节码文件是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine)将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能,这就是这4个主要组成部分的职责与功能


162d1e730a424f05969338d98e266bec.png



🍎二.JVM运行时数据区

70dbd571630341ceac64241190b863ea.png

🍒2.1 程序计数器(线程私有)



程序计数器的作用:用来记录当前线程执行的行号的,用来存储下一条指令的地址

程序计数器是一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法,这个计数器值为空


总结:

程序计数器:内存最小的一块区域,保存了下一条要执行的指令地址在哪里,与书签类似


什么是线程私有?

由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此在任何一个确定的时刻,一个处理器(多核处理器则指的是一个内核)都只会执行一条线程中的指令。因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们就把类似这类区域称之为"线程私有"的内存


🍒2.2 栈(线程私有)


Java虚拟机栈(线程私有)


Java 虚拟机栈的作用:Java 虚拟机栈的生命周期和线程相同,Java 虚拟机栈描述的是 Java 方法执行的

内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,咱们常说的堆内存、栈内存中,栈内存指的就是虚拟机栈


67398e7986a6463b8a052fb273a4c7dc.png


Java 虚拟机栈中包含了以下 4 部分:


局部变量表: 存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在执行期间不会改变局部变量表大小。简单来说就是存放方法参数和局部变量

操作栈:每个方法会生成一个先进后出的操作栈。

动态链接:指向运行时常量池的方法引用。

方法返回地址:PC 寄存器的地址

关于虚拟机栈会产生的两种异常:

● 如果线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverFlow异常

● 如果虚拟机在拓展栈时无法申请到足够的内存空间,则会抛出OOM异常


出现StackOverflowError异常时有错误堆栈可以阅读,比较好找到问题所在。如果使用虚拟机默认参数,栈深度在多多数情况下达到1000-2000完全没问题,对于正常的方法调用(包括递归),完全够用


如果是因为多线程导致的内存溢出问题,在不能减少线程数的情况下,只能减少最大堆和减少栈容量的方式来换取更多线程


●本地方法栈

本地方法栈和虚拟机栈类似,只不过 Java 虚拟机栈是给 JVM 使用的,而本地方法栈是给本地方法使用的


🍒2.3 堆(线程共享)


堆:储存对象以及对象的成员变量,一个进程只有一个,多个线程共用一个堆,内存中空间最大的区域,我们看到下图对堆做了细分,Java堆是垃圾收集器管理的内存区域,所以后介绍GC的时候我们细说

763fc3976233433d87f00762e982dc3f.png

🍒2.4 方法区(线程共享)


方法区:存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,即就是储存“类对象”,被static修饰的变量或方法就成了类属性,.java文件会被编译成.class文件,.class会被加载到内存中,也就被JVM构造成类对象了,这个加载的过程叫做类加载,类对象描述了类的信息,如类名,类有哪些成员,每个成员叫什么名字,权限是什么,方法名等


a43bd7a5b6804ef69c6d2fcfa63e07fa.png

所以可以得到结论,静态的代码块,普通代码块,构造方法执行顺序为:静态的代码块->普通代码块->构造方法


🍎三.JVM类加载


🍒3.1类加载过程


对于一个类来说,它的生命周期是这样的:

d273e3fc87bf4248be4f41f3898a569c.png


其中前 5 步是固定的顺序并且也是类加载的过程,其中中间的 3 步我们都属于连接,所以对于类加载来

说总共分为以下几个步骤:


1. 加载(Loading)
2. 连接(Linking)
  .验证
  .准备
  .解析
3. 初始化(Initialization)


下面我们分别来看每个步骤的具体执行内容

(1) 加载(Loading)

“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,它和类加载 ClassLoading 是不同的,一个是加载 Loading 另一个是类加载 Class Loading,所以不要把二者搞混了。在加载 Loading 阶段,Java虚拟机需要完成以下三件事情:

● 通过一个类的全限定名来获取定义此类的二进制字节流。

● 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

● 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

81fba31909d44758bd73c0c1e10c1115.png

(2) 验证

主要就是验证读取到的内容是不是和规范中规定的格式完全匹配,如果不匹配,就会类加载失败,并且会抛出异常
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节 流中包含的信息符合《Java虚拟机规范》的全部约束要求,
保证这些信 息被当作代码运行后不会危害虚拟机自身的安全
验证选项:
文件格式验证
字节码验证
符号引用验证...

(3) 准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。
比如此时有这样一行代码:
public static int value = 123;
它是初始化 value 的 int 值为 0,而非 123。

(4) 解析

.class文件中,常量是集中放置的(常量池),并且每一个常量都有一个编号,.class文件中的结构体初始情况
下它只记录了常量的编号,解析过程简单来说就是根据编号将对应的常量填充到类对象中

(5) 初始化(Initialization)

这里是真正地对类对象进行初始化,特别是静态成员
类加载过程是在执行某方法(如main方法)之前执行的,类加载的时候会进行静态代码块的执行,想要创建实例,
必然先得类加载,静态代码块只会执行一次,构造方法与普通代码块每次实例对象都会执行,并且普通代码块比静态代码块先执行

常见笔试题


所以可以得到结论,静态的代码块,普通代码块,构造方法执行顺序为:静态的代码块->普通代码块->构造方法

class A{
    public A(){
        System.out.println("这是A的构造方法");
    }
    {
        System.out.println("这是A的代码块");
    }
    static {
        System.out.println("这是A的静态代码块");
    }
    public void fun(){
        System.out.println("方法A");
    }
}
class B extends A {
    public B() {
        System.out.println("这是B的构造方法");
    }
    {
        System.out.println("这是B的代码块");
    }
    static {
        System.out.println("这是B的静态代码块");
    }
}
public class Test extends B{
    public static void main(String[] args) {
        new Test();
        new Test();
        int s = 10;
        System.out.println(s);
    }
}

57ea65af8f32407a9cbc915821cffbee.png

相关文章
|
27天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
33 0
|
3天前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
26天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
1月前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
47 1
|
1月前
|
监控 算法 Java
Java虚拟机垃圾回收机制深度剖析与优化策略####
【10月更文挑战第21天】 本文旨在深入探讨Java虚拟机(JVM)中的垃圾回收机制,揭示其工作原理、常见算法及参数调优技巧。通过案例分析,展示如何根据应用特性调整GC策略,以提升Java应用的性能和稳定性,为开发者提供实战中的优化指南。 ####
43 5
|
1月前
|
存储 算法 Java
JVM进阶调优系列(10)敢向stop the world喊卡的G1垃圾回收器 | 有必要讲透
本文详细介绍了G1垃圾回收器的背景、核心原理及其回收过程。G1,即Garbage First,旨在通过将堆内存划分为多个Region来实现低延时的垃圾回收,每个Region可以根据其垃圾回收的价值被优先回收。文章还探讨了G1的Young GC、Mixed GC以及Full GC的具体流程,并列出了G1回收器的核心参数配置,帮助读者更好地理解和优化G1的使用。
|
1月前
|
算法 Java
JVM有哪些垃圾回收算法?
(1)标记清除算法: 标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。 (2)复制算法: 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。用在新生代 (3)标记整理算法: 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。用在老年代
23 0
|
1月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
298 1
|
3天前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
|
24天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。