《十年架构师详解JVM运行原理》是一份深度讲解JVM工作原理的指南,对于Java开发者而言,JVM是一门必备的基础知识。尽管绝大多数开发者在工作一两年后对JVM的实际应用并不多,只有那些具有自学能力及专门从事性能优化相关工作的开发者才会深入学习及理解JVM。但实际上,对于初学者而言,学习及深入理解JVM是至关重要的,因为JVM是Java开发的基石。

2024-06-28 23:56:36 作者:6kYzQ!yIEmp_M6UkZ
[x]

[[312896]]

\n1. JVM体系结构简介

\n如果你是一名Java使用者,那么了解JVM的体系结构是至关重要的。提到Java,我们往往会想到Java编程语言,但实际上,Java是一项技术,包括了四个方面:Java编程语言、Java类文件格式、Java虚拟机以及Java应用程序接口(Java API)。它们之间的关系如图所示:

 

 

Java平台的结构由Java虚拟机和Java应用程序接口组成,Java语言充当了进入该平台的通道,使用Java语言编写并编译的程序可以在该平台上运行。该平台的架构如下图所示:在运行时环境中,Java平台代表着开发人员编写Java代码(.java文件),然后将其编译为字节码(.class文件),然后将字节码装入内存。一旦字节码进入虚拟机,就会被解释器执行,或者经过即时代码发生器选择性地转换为机器码执行。JVM在其生命周期中有一个明确的目标,即运行Java程序。因此,当Java程序启动时,会产生JVM的一个实例;当程序运行结束时,该实例也随之消失。在Java平台的体系结构中,可以明显看出Java虚拟机(JVM)占据了核心位置,这是程序与底层操作系统和硬件无关的重要组成部分。它的底部是移植接口,由两个部分组成:适配器和Java操作系统。依赖于特定平台的部分被称为适配器;JVM通过移植接口在具体的平台和操作系统上实现。在JVM的顶部是Java的基本类库和扩展类库以及它们的API。利用Java API编写的应用程序和Java小程序可以在任何Java平台上运行,无需考虑底层平台。这是因为有Java虚拟机(JVM)实现了程序与操作系统的分离,从而实现了Java的平台无关性。接下来,我们将从JVM的基本概念和运行过程两个方面入手,对其进行深入研究。JVM基本概念\n(1) 基本概念:JVM是一种虚拟计算机,能够运行Java代码,具有一套字节码指令集、一组寄存器、一个栈、一个垃圾回收器、以及一个存储方法域。Java虚拟机(JVM)是在操作系统上运行的,它与硬件没有直接的连接。在程序运行过程中,我们都了解到Java源文件经过编译器可以生成对应的.Class文件,也就是字节码文件,而这些字节码文件又通过Java虚拟机中的解释器编译成特定机器上的机器码。十年架构师详解JVM运行原理

可以用以下方式来说明:

① Java源文件通过编译器编译成字节码文件->

② 字节码文件经由JVM转换为机器码

十年架构师详解JVM运行原理每个平台都有不同的解释器,但它们实现的虚拟机是相同的,这就是Java能够跨平台的原因。当一个程序开始运行时,虚拟机就会实例化。同时,多个程序启动时会引入多个虚拟机实例。当程序退出或关闭时,虚拟机实例将会被销毁,这意味着多个虚拟机实例之间无法共享数据。(3) JVM有三种不同的类型:① Sun 公司的 HotSpot;② BEA 公司的 JRockit;③ IBM 公司的 J9 JVM。在 JDK1.7 以前,我们使用的是 Sun 公司的 HotSpot。但由于 Sun 公司和 BEA 公司都被 Oracle 收购,JDK1.8 将融合 Sun 公司的 HotSpot 和 BEA 公司的 JRockit 的精华,形成 JDK1.8 的 JVM。JVM体系结构的第三部分:ClassLoader类加载器\nClassLoader类加载器是JVM体系结构中的一部分,它的主要职责是加载.class文件。.class文件具有特定的文件标识,并且受ClassLoader负责加载。然而,是否可以运行这些文件取决于Execution Engine的决定。① 进行二进制class文件的定位和导入。\n② 确认导入的类是否正确。\n③ 为类分配内存并初始化。\n④ 协助解析符号引用。\n⑤ 本地接口(Native Interface):\n本地接口的作用是将不同的编程语言融合为Java所用。它最初设计的目的是将C/C++程序引入Java环境,因为在Java诞生之时,C/C++非常流行,若想在这个市场站稳脚跟,就必须实现对C/C++程序的调用。因此,在内存中专门开辟了一块区域,用于处理被标记为native的代码。具体实现方式是在Native Method Stack中记录native方法。在执行引擎进行加载本地库时。目前该方法的使用越来越少,除非是与硬件相关的应用,例如使用Java程序驱动打印机或者管理生产设备,企业级应用中已经相对不常见。目前这种方法的使用越来越少了,除非涉及与硬件有关的应用,比如通过Java程序来控制打印机,或者用Java来管理生产设备,在企业级应用中已经比较罕见。这是因为如今不同领域之间的通信非常发达,可以使用Socket通信,也可以使用Web Service等。(3) 执行引擎(Execution Engine):负责执行已装载类中方法中的指令。(4) 运行时数据区域:指的是虚拟机内存或者 JVM 内存,在整个计算机内存中开辟的一块内存,用于存储 JVM 需要使用的对象、变量等。运行时数据区域可以分为多个小区,包括方法区、虚拟机栈、本地方法栈、堆以及程序计数器。JVM数据运行区解析(栈负责运行,堆负责存储):要点:JVM调优的重点在于优化堆和方法区。(1)本地方法栈是指Native Method Stack,在执行引擎运行时会加载本地方法库,将本地方法注册到本地方法栈中。

(2) 计算机寄存器

每个线程都有一个计算机寄存器,它是一个指针,指向方法区中的方法字节码(即将执行的下一条指令代码),由执行引擎读取下一条指令,它占用非常小的内存空间,几乎可以忽略不计。(3) 方法区\n方法区是所有线程共享的区域,其中包含所有字段和方法的字节码,以及一些特殊方法(如构造函数和接口代码)的定义。简而言之,所有方法的定义信息都存储在这个区域,这个区域是共享区域的一部分。静态变量、常量、类信息和运行时常量池存在于方法区中,而实例变量存在于堆内存中。(4) 栈 \n① 栈是什么 \n栈也称为栈内存,负责管理Java程序的运行。它在线程创建时创建,生命周期与线程相同。当线程结束时,栈内存也会被释放。对于栈来说,不存在垃圾回收问题,只要线程一结束,该栈就结束。它是线程私有的。

的基本类型变量和引用变量都是在函数的栈内存中分配的。2. 栈结构可以存储哪些数据?

栈帧主要存储三种数据:

本地变量包括输入参数、输出参数和方法内的变量;

栈操作记录出栈和入栈操作;

栈帧数据包括类文件和方法等。③栈的运行原理是这样的:栈中的数据都是以栈帧的形式存在的。栈帧是一个内存区域,它包含了方法和运行时数据的集合。当一个方法A被调用时,就会产生一个栈帧F1,并被压入栈中。如果A方法又调用了方法B,那么就会产生栈帧F2,并将其压入栈中。接着,如果B方法又调用了方法C,就会产生栈帧F3,并被压入栈中... 依次执行完毕后,先弹出后进。即先弹出F3栈帧,再弹出F2栈帧,最后弹出F1栈帧。遵循“后进先出”/“先进后出”原则。(5) 堆是JVM中最大的内存区域,用来存放应用程序的对象和数据。堆是线程共享的,也是垃圾回收的主要区域。每个JVM实例只有一个堆,堆的大小可以调整。当类加载器读取类文件后,需要将类、方法和常量存放到堆内存。这样可以方便执行器执行。堆内存分为三个部分:①新生区,②老年区,③持久化区。其中,新生区是类生命周期的诞生、成长和消亡的区域。在这个区域中,一个类从诞生开始到被垃圾回收器回收结束整个生命周期。新生区可分为两个部分:伊甸区和幸存者区。所有的类都是在伊甸区创建的。幸存区一共有两个,分别是0区和1区。当伊甸园的空间用完后,程序需要继续创建对象,此时Java虚拟机的垃圾回收器将会对伊甸园进行垃圾回收操作,即进行Minor GC,并将伊甸园中未被回收的对象移动到幸存0区。当0区已满,执行垃圾回收后,将数据迁移到1区。如果一个地方也被占满了怎么办呢?继续搬迁至老年社区。如果老年代也达到饱和状态,就会触发Major GC(FullGCC)进行老年代的内存清理。在进行完全垃圾回收后,如果发现仍然无法保存对象,就会引发"OutOfMemoryError"异常。如果发生java.lang.OutOfMemoryError: Java 堆空间异常,表示Java虚拟机的堆内存不足。有两个原因造成这个问题:一是Java虚拟机的堆内存设置不够,可以通过参数-Xms和-Xmx来进行调整。在代码中创建了大量的大对象,并且它们很长时间都没有被垃圾收集器收集(即被引用)。

② 养老区是用来存储从新生区筛选出的 JAVA 对象,一般来说,大部分池对象都在该区域活跃。

③ 永久区

,也称为永久存储区,是一个用于存储JDK自带的Class和Interface的元数据的内存区域。换句话说,这个区域存储了运行环境必需的类信息。在这个区域中加载的数据不会被垃圾回收器回收,只有在关闭JVM时才会释放该区域占用的内存。当出现java.lang.OutOfMemoryError: PermGen space错误时,表示Java虚拟机对永久代Perm内存设置不足。有两个原因:一是程序启动时需要加载大量的第三方jar包。举个例子,若在一台Tomcat服务器上部署了过多的应用。

b。大量动态生成的类不断被加载,最终导致永久代被占满。注意:以下文字仅供参考。\n

解释:

在Jdk1.6及其以前版本中,存在一个名为“常量池”的数据结构。分配于永久代。 JDK1.7已经有了,但已经逐步"去除永久代"。从Jdk1.8版本开始,将不再出现java.lang.OutOfMemoryError: PermGen space这种错误。【说明】关于方法区和堆内存的异议:\n实际上,方法区和堆一样,都是各个线程共享的内存区域。它们用于存储虚拟机加载的类信息、普通常量、静态常量、编译器编译后的代码等等。尽管JVM规范将方法区描述为堆的一个逻辑部分,但方法区还有一个别名叫做Non-Heap(非堆),目的是为了和堆分开。对于HotSpot虚拟机,许多开发者习惯称方法区为"永久代(Parmanent Gen)",但严格来说它们并不相同,可以说永久代只是方法区的一种实现方式。在jdk1.7版本中,字符串常量池已不再放在永久代中。常量池是方法区的一部分。除了包含类的版本、字段、方法、接口等描述信息外,Class文件还包含常量池,这部分内容在类加载后会存入方法区的运行时常量池中。

5. 堆内存调优简介

 

 

代码测试:

public class JVMTest {  public static void main(String[] args){  long maxMemory = Runtime.getRuntime().maxMemory();// 返回Java虚拟机试图使用的最大内存量。```\nlong totalMemory = Runtime.getRuntime().totalMemory(); // 获取 Java 虚拟机中的内存总量。\n```System.out.println("MAX_MEMORY=" + maxMemory + "(字节)、" + (maxMemory/(double)1024/1024) + "MB");\nSystem.out.println("TOTAL_MEMORY=" + totalMemory + "(字节)" + (totalMemory/(double)1024/1024) + "MB");\n说明:在Run as ->Run Configurations中输入"-XX:+PrintGCDetails"可以查看堆内存运行原理图:\n(1) 在jdk1.7中:\n(2) 在jdk1.8中:\n6.通过参数设置自动触发垃圾回收:\npublic class JVMTest\n{\npublic static void main(String[] args)\n{\nlong maxMemory = Runtime.getRuntime().maxMemory();//返回Java虚拟机试图使用的最大内存量。 长 totalMemory = Runtime. getRuntime().totalMemory();//获取Java虚拟机中的总内存量。System.out.println("MAX_MEMORY=" + maxMemory + "(字节)、" + (maxMemory/(double)1024/1024) + "MB");\nSystem.out.println("TOTAL_MEMORY=" + totalMemory + "(字节)" + (totalMemory/(double)1024/1024) + "MB");\nString str = "www.baidu.com";\nwhile(true){\n str += str + new Random().nextInt(88888888) + new Random().nextInt(99999999);\ \n在Run as ->Run Configurations中输入设置“-Xmx8m –Xms8m –xx:+PrintGCDetails”可以参看垃圾回收机制原理:\n在Run as ->Run Configurations中输入设置“-Xmx8m –Xms8m –xx:+PrintGCDetails”可以参看垃圾回收机制原理:

十年架构师详解JVM运行原理

十年架构师详解JVM运行原理

十年架构师详解JVM运行原理

十年架构师详解JVM运行原理

十年架构师详解JVM运行原理

十年架构师详解JVM运行原理

十年架构师详解JVM运行原理

十年架构师详解JVM运行原理
在线咨询 拨打电话

电话

02088888888

微信二维码

微信二维码