据说看完这篇 JVM 要一小时(一)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 大家好,我是干货旋,今天又给大家怼干货了,我不就放纵了几天么,有些难搞的读者还以为我把号卖了,呐,你说我不出干货,我就问你这篇文章敢不敢三连。

大家好,我是干货旋,今天又给大家怼干货了,我不就放纵了几天么,有些难搞的读者还以为我把号卖了,呐,你说我不出干货,我就问你这篇文章敢不敢三连。

正文走起!

JVM 的主要作用是什么?

JVM 就是 Java Virtual Machine(Java虚拟机)的缩写,JVM 屏蔽了与具体操作系统平台相关的信息,使 Java 程序只需生成在 Java 虚拟机上运行的目标代码 (字节码),就可以在不同的平台上运行。

请你描述一下 Java 的内存区域?

JVM 在执行 Java 程序的过程中会把它管理的内存分为若干个不同的区域,这些组成部分有些是线程私有的,有些则是线程共享的,Java 内存区域也叫做运行时数据区,它的具体划分如下:

微信图片_20220417151934.jpg

  • 虚拟机栈 : Java 虚拟机栈是线程私有的数据区,Java 虚拟机栈的生命周期与线程相同,虚拟机栈也是局部变量的存储位置。方法在执行过程中,会在虚拟机栈中创建一个 栈帧(stack frame)。每个方法执行的过程就对应了一个入栈和出栈的过程。

微信图片_20220417151938.jpg

  • 本地方法栈: 本地方法栈也是线程私有的数据区,本地方法栈存储的区域主要是 Java 中使用 native 关键字修饰的方法所存储的区域。
  • 程序计数器:程序计数器也是线程私有的数据区,这部分区域用于存储线程的指令地址,用于判断线程的分支、循环、跳转、异常、线程切换和恢复等功能,这些都通过程序计数器来完成。
  • 方法区:方法区是各个线程共享的内存区域,它用于存储虚拟机加载的 类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • :堆是线程共享的数据区,堆是 JVM 中最大的一块存储区域,所有的对象实例都会分配在堆上。JDK 1.7后,字符串常量池从永久代中剥离出来,存放在堆中。
    堆空间的内存分配(默认情况下):
    命令行上执行如下命令,会查看默认的 JVM 参数。
java -XX:+PrintFlagsFinal -version
  • 输出的内容非常多,但是只有两行能够反映出上面的内存分配结果

微信图片_20220417151943.jpg

微信图片_20220417151946.jpg

微信图片_20220417151949.jpg

  • eden 区:8/10 的年轻代空间
  • survivor 0 : 1/10 的年轻代空间
  • survivor 1 : 1/10 的年轻代空间
  • 老年代 :三分之二的堆空间
  • 年轻代 :三分之一的堆空间
  • 运行时常量池:运行时常量池又被称为 Runtime Constant Pool,这块区域是方法区的一部分,它的名字非常有意思,通常被称为 非堆。它并不要求常量一定只有在编译期才能产生,也就是并非编译期间将常量放在常量池中,运行期间也可以将新的常量放入常量池中,String 的 intern 方法就是一个典型的例子。

请你描述一下 Java 中的类加载机制?

Java 虚拟机负责把描述类的数据从 Class 文件加载到系统内存中,并对类的数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称之为 Java 的类加载机制

一个类从被加载到虚拟机内存开始,到卸载出内存为止,一共会经历下面这些过程。

微信图片_20220417151953.jpg

类加载机制一共有五个步骤,分别是加载、链接、初始化、使用和卸载阶段,这五个阶段的顺序是确定的。

其中链接阶段会细分成三个阶段,分别是验证、准备、解析阶段,这三个阶段的顺序是不确定的,这三个阶段通常交互进行。解析阶段通常会在初始化之后再开始,这是为了支持 Java 语言的运行时绑定特性(也被称为动态绑定)。

下面我们就来聊一下这几个过程。

加载

关于什么时候开始加载这个过程,《Java 虚拟机规范》并没有强制约束,所以这一点我们可以自由实现。加载是整个类加载过程的第一个阶段,在这个阶段,Java 虚拟机需要完成三件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流。
  • 将这个字节流表示的一种存储结构转换为运行时数据区中方法区的数据结构。
  • 在内存中生成一个 Class 对象,这个对象就代表了这个数据结构的访问入口。

《Java 虚拟机规范》并未规定全限定名是如何获取的,所以现在业界有很多获取全限定名的方式:

  • 从 ZIP 包中读取,最终会改变为 JAR、EAR、WAR 格式。
  • 从网络中获取,最常见的应用就是 Web Applet。
  • 运行时动态生成,使用最多的就是动态代理技术。
  • 由其他文件生成,比如 JSP 应用场景,由 JSP 文件生成对应的 Class 文件。
  • 从数据库中读取,这种场景就比较小了。
  • 可以从加密文件中获取,这是典型的防止 Class 文件被反编译的保护措施。

加载阶段既可以使用虚拟机内置的引导类加载器来完成,也可以使用用户自定义的类加载器来完成。程序员可以通过自己定义类加载器来控制字节流的访问方式。

数组的加载不需要通过类加载器来创建,它是直接在内存中分配,但是数组的元素类型(数组去掉所有维度的类型)最终还是要靠类加载器来完成加载。

验证

加载过后的下一个阶段就是验证,因为我们上一步讲到在内存中生成了一个 Class 对象,这个对象是访问其代表数据结构的入口,所以这一步验证的工作就是确保 Class 文件的字节流中的内容符合《Java 虚拟机规范》中的要求,保证这些信息被当作代码运行后,它不会威胁到虚拟机的安全。

验证阶段主要分为四个阶段的检验:

  • 文件格式验证。
  • 元数据验证。
  • 字节码验证。
  • 符号引用验证。

文件格式验证

这一阶段可能会包含下面这些验证点:

  • 魔数是否以 0xCAFEBABE 开头。
  • 主、次版本号是否在当前 Java 虚拟机接受范围之内。
  • 常量池的常量中是否有不支持的常量类型。
  • 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。
  • CONSTANT_Utf8_info 型的常量中是否有不符合 UTF8 编码的数据。
  • Class 文件中各个部分及文件本身是否有被删除的或附加的其他信息。

实际上验证点远远不止有这些,上面这些只是从 HotSpot 源码中摘抄的一小段内容。

元数据验证

这一阶段主要是对字节码描述的信息进行语义分析,以确保描述的信息符合《Java 语言规范》,验证点包括

  • 验证的类是否有父类(除了 Object 类之外,所有的类都应该有父类)。
  • 要验证类的父类是否继承了不允许继承的类。
  • 如果这个类不是抽象类,那么这个类是否实现了父类或者接口中要求的所有方法。
  • 是否覆盖了 final 字段,是否出现了不符合规定的重载等。

需要记住这一阶段只是对《Java 语言规范》的验证。

字节码验证

字节码验证阶段是最复杂的一个阶段,这个阶段主要是确定程序语意是否合法、是否是符合逻辑的。这个阶段主要是对类的方法体(Class 文件中的 Code 属性)进行校验分析。这部分验证包括

  • 确保操作数栈的数据类型和实际执行时的数据类型是否一致。
  • 保证任何跳转指令不会跳出到方法体外的字节码指令上。
  • 保证方法体中的类型转换是有效的,例如可以把一个子类对象赋值给父类数据类型,但是不能把父类数据类型赋值给子类等诸如此不安全的类型转换。
  • 其他验证。

如果没有通过字节码验证,就说明验证出问题。但是不一定通过了字节码验证,就能保证程序是安全的。

符号引用验证

最后一个阶段的校验行为发生在虚拟机将符号引用转换为直接引用的时候,这个转化将在连接的第三个阶段,即解析阶段中发生。符号引用验证可以看作是对类自身以外的各类信息进行匹配性校验,这个验证主要包括

  • 符号引用中的字符串全限定名是否能找到对应的类。
  • 指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。
  • 符号引用的类、字段方法的可访问性是否可被当前类所访问。
  • 其他验证。

这一阶段主要是确保解析行为能否正常执行,如果无法通过符号引用验证,就会出现类似 IllegalAccessErrorNoSuchFieldErrorNoSuchMethodError 等错误。

验证阶段对于虚拟机来说非常重要,如果能通过验证,就说明你的程序在运行时不会产生任何影响。

准备

准备阶段是为类中的变量分配内存并设置其初始值的阶段,这些变量所使用的内存都应当在方法区中进行分配,在 JDK 7 之前,HotSpot 使用永久代来实现方法区,是符合这种逻辑概念的。而在 JDK 8 之后,变量则会随着 Class 对象一起存放在 Java 堆中。

下面通常情况下的基本类型和引用类型的初始值

微信图片_20220417152000.jpg

除了"通常情况"下,还有一些"例外情况",如果类字段属性中存在 ConstantValue 属性,那就这个变量值在初始阶段就会初始化为 ConstantValue 属性所指定的初始值,比如

public static final int value = "666";

编译时就会把 value 的值设置为 666。

解析

解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程。

  • 符号引用:符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,符号引用和虚拟机的布局无关。
  • 直接引用:直接引用可以直接指向目标的指针、相对便宜量或者一个能间接定位到目标的句柄。直接引用和虚拟机的布局是相关的,不同的虚拟机对于相同的符号引用所翻译出来的直接引用一般是不同的。如果有了直接引用,那么直接引用的目标一定被加载到了内存中。

这样说你可能还有点不明白,我再换一种说法:

在编译的时候一个每个 Java 类都会被编译成一个 class 文件,但在编译的时候虚拟机并不知道所引用类的地址,所以就用符号引用来代替,而在这个解析阶段就是为了把这个符号引用转化成为真正的地址的阶段。

《Java 虚拟机规范》并未规定解析阶段发生的时间,只要求了在 anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、ldc2_w、multianewarray、new、putfield 和 putstatic 这 17 个用于操作符号引用的字节码指令之前,先对所使用的符号引用进行解析。

解析也分为四个步骤

  • 类或接口的解析
  • 字段解析
  • 方法解析
  • 接口方法解析
相关文章
|
存储 设计模式 缓存
据说看完这篇 JVM 要一小时(四)
大家好,我是干货旋,今天又给大家怼干货了,我不就放纵了几天么,有些难搞的读者还以为我把号卖了,呐,你说我不出干货,我就问你这篇文章敢不敢三连。
据说看完这篇 JVM 要一小时(四)
|
存储 缓存 算法
据说看完这篇 JVM 要一小时(三)
大家好,我是干货旋,今天又给大家怼干货了,我不就放纵了几天么,有些难搞的读者还以为我把号卖了,呐,你说我不出干货,我就问你这篇文章敢不敢三连。
据说看完这篇 JVM 要一小时(三)
|
存储 算法 Java
据说看完这篇 JVM 要一小时(二)
大家好,我是干货旋,今天又给大家怼干货了,我不就放纵了几天么,有些难搞的读者还以为我把号卖了,呐,你说我不出干货,我就问你这篇文章敢不敢三连。
据说看完这篇 JVM 要一小时(二)
|
2月前
|
Java Docker 索引
记录一次索引未建立、继而引发一系列的问题、包含索引创建失败、虚拟机中JVM虚拟机内存满的情况
这篇文章记录了作者在分布式微服务项目中遇到的一系列问题,起因是商品服务检索接口测试失败,原因是Elasticsearch索引未找到。文章详细描述了解决过程中遇到的几个关键问题:分词器的安装、Elasticsearch内存溢出的处理,以及最终成功创建`gulimall_product`索引的步骤。作者还分享了使用Postman测试接口的经历,并强调了问题解决过程中遇到的挑战和所花费的时间。
|
6天前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
18 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
2月前
|
存储 算法 Oracle
不好意思!耽误你的十分钟,JVM内存布局还给你
先赞后看,南哥助你Java进阶一大半在2006年加州旧金山的JavaOne大会上,一个由顶级Java开发者组成的周年性研讨会,公司突然宣布将开放Java的源代码。于是,下一年顶级项目OpenJDK诞生。Java生态发展被打开了新的大门,Java 7的G1垃圾回收器、Java 8的Lambda表达式和流API…大家好,我是南哥。一个Java学习与进阶的领路人,相信对你通关面试、拿下Offer进入心心念念的公司有所帮助。
不好意思!耽误你的十分钟,JVM内存布局还给你
|
2月前
|
存储 算法 Java
JVM自动内存管理之垃圾收集算法
文章概述了JVM内存管理和垃圾收集的基本概念,提供一个关于JVM内存管理和垃圾收集的基础理解框架。
JVM自动内存管理之垃圾收集算法
|
2月前
|
存储 Java 程序员
JVM自动内存管理之运行时内存区
这篇文章详细解释了JVM运行时数据区的各个组成部分及其作用,有助于理解Java程序运行时的内存布局和管理机制。
JVM自动内存管理之运行时内存区
|
2月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
2月前
|
存储 安全 Java
JVM内存结构
这篇文章详细介绍了Java虚拟机(JVM)的内存结构,包括类的加载过程、类加载器的双亲委派机制、沙箱安全机制、程序计数器、Java栈、Java堆、本地方法和本地方法栈等关键组件及其作用。
JVM内存结构