• jvm深入研究文档--java代码的执行


    阿丹:

             作为Java工程师,了解Java代码的执行原理对于开发高效、可靠的应用程序非常有用。以下是几个了解Java代码执行原理的用途:

    1. 性能优化:了解Java代码的执行原理可以帮助你编写更高效的代码。你可以了解到Java程序在编译和运行时的优化过程,以及如何利用这些知识来提高代码的执行效率。

    2. 调试和故障排除:当代码在执行时出现错误或异常,通过了解Java代码的执行原理,你可以更好地理解错误的原因。这有助于你进行调试和故障排除,找到问题所在并修复它们。

    3. 内存管理:了解Java代码的执行原理可以帮助你更好地理解Java内存模型和垃圾回收机制。你可以编写更有效的代码,避免内存泄漏和内存溢出等常见问题。

    4. 多线程开发:多线程是Java的一个重要特性,也是开发复杂应用程序的必备工具。了解Java代码的执行原理可以帮助你更好地理解多线程编程的挑战,以及如何避免并发问题和线程安全性问题。

    总而言之,了解Java代码的执行原理可以提高你作为Java工程师的技术能力。这不仅有助于你编写高效、可靠的代码,还能帮助你更好地处理调试、故障排除、内存管理和多线程开发等方面的问题。

     java代码的执行过程:

    1、编译阶段

            在编译阶段java中的jvm虚拟机并不涉及到操作。首先我们先来看一下这个流程图。

    概括:

            就是将我们人写的可读性高的.java代码文件使用javac的指令来将这些代码转换成电脑可执行的.class文件。

    那么具体在编辑阶段干了什么事情呢?如下:

    当Java代码在执行过程中,首先会经历编译阶段。下面是Java编译阶段的具体工作流程和原理:

    1. 词法分析和语法分析:编译器首先会对源代码进行词法分析和语法分析。词法分析器会将源代码分解为一个个的词法单元(tokens),例如关键字、标识符、运算符等。然后语法分析器会根据语法规则将这些词法单元组合成抽象语法树(Abstract Syntax Tree,AST),以描述代码的结构和语义。

    2. 语义分析:在语义分析阶段,编译器会对AST进行分析,检查代码中的语义错误和类型错误。它会验证变量和函数的作用域、类型的匹配、表达式的合法性等。如果发现错误,编译器会生成相应的错误消息。

    3. 符号表生成:编译器会生成符号表,将代码中定义的变量、函数名和类名等信息记录下来。符号表用于后续的代码生成和类型检查。

    4. 中间代码生成:在这一阶段,编译器会生成中间代码。中间代码是一种抽象的、与具体机器无关的代码表示。它将源代码转化为一系列的指令,以便后续的优化和最终的目标代码生成。

    5. 优化:编译器会对生成的中间代码进行优化,以提高代码的执行效率和性能。优化包括但不限于常量折叠、死代码删除、循环展开、方法内联等技术,以减少执行时间和内存消耗。

    6. 目标代码生成:最后,编译器将经过优化的中间代码转化为目标机器的机器代码。这个过程会涉及到寄存器的分配、指令的选择和代码的布局等步骤。生成的机器代码可以直接在目标硬件上执行。

    在Java的编译阶段中,编译器根据源代码的语法和语义规则,将源代码转化为中间代码和最终的目标机器代码。编译阶段的工作流程和原理确保了Java代码的正确性和执行效率,为后续的运行时环境(如JVM)提供了合适的代码。

    在完成编译阶段之后使用

    java myclassfilename

    在同一目录下的dos窗口中使用java +.class文件的名字就可以执行这个可执行的.calss文件了。

    2、类加载阶段

    类加载阶段

    Java程序在JVM中运行时,类加载是其中一个非常重要的阶段。类加载器负责将字节码文件(.class文件)加载到内存中,并创建Java类的运行时数据结构。下面是JVM类加载阶段的关键概念和工作流程的详细解释:

    类的加载过程:类加载的过程可以分为以下三个步骤:
    1. 加载:

      • 加载是类加载的第一个阶段,它负责将字节码文件(.class文件)转化为二进制数据,并创建Java类的运行时数据结构(如Class对象)。加载过程会从文件系统、网络或其他源加载字节码文件,并将其读取到内存中。

      • JVM会按需加载类,即在需要使用某个类之前,它会先加载该类。例如,当代码中使用了某个类的静态方法或属性时,JVM会加载该类。

    2. 链接: 类加载的第二个阶段是链接,它包括验证、准备和解析三个过程:

      • 验证:验证阶段主要是对加载的字节码进行合法性验证和安全性验证。这个过程会检查字节码文件的格式是否正确、符号引用是否合法,以及访问权限等安全问题。

      • 准备:在准备阶段,JVM会为类的静态变量分配内存,并初始化默认值。这些静态变量包括基本类型数据(例如int、boolean等)和引用类型(例如对象引用)。

      • 解析:解析阶段是将类中的符号引用替换为直接引用的过程。符号引用是一种代表引用的符号,而直接引用则是对内存中真实对象的直接指向。通过解析阶段,JVM能够确定方法、字段和类的访问位置。

      • 验证阶段主要是对加载阶段中的字节码数据进行一些语法和语义上的检查,确保它们符合Java虚拟机的规范。准备阶段主要是为类的静态变量分配内存空间,并将它们初始化为默认值(例如,整数为0,对象引用为null等)。解析阶段则是将字节码中的符号引用转化为直接引用,使得程序在执行时可以正确地找到这些引用。

    3. 初始化: 初始化是类加载的最后一个阶段,它涉及对类的静态变量进行显式初始化和执行静态代码块。JVM在使用某个类之前,会确保该类的初始化操作已经完成。

           1.类初始化时,JVM会按照静态变量的定义顺序依次进行初始化,并执行静态代码块。在初始化过程中,可以执行一些特殊的业务逻辑,例如静态资源的加载和单例对象的创建。

            2.这是类加载的最后一个阶段,主要对连接阶段中准备阶段赋初始值的静态变量进行初始化操作。这个阶段会执行类的初始化代码,包括静态变量赋值、静态代码块执行等。这个阶段完成后,类就完成了全部的加载和初始化过程,可以随时被程序使用了。
    工作原理:
    • 类的加载、链接和初始化是JVM执行的一系列操作,用于将字节码文件转化为可执行的代码,以及执行一些必要的初始化操作。
    • JVM中的类加载器负责加载字节码文件,并为每个加载的类创建一个运行时数据结构(Class对象)。这个数据结构包含了类的描述信息、类的方法和字段等。
    • 类加载器按照委托模型,首先尝试让父类加载器加载请求的类,如果父类加载器无法加载,则由子类加载器进行加载。这种层次结构的设计保证了类的一致性和安全性。
    • 链接阶段主要是对加载的类进行验证、准备和解析。验证阶段确保加载的字节码文件是合法且安全的;准备阶段为静态变量分配内存并初始化默认值;解析阶段将符号引用替换为直接引用。
    • 初始化阶段是执行类的静态初始化过程,包括显式初始化静态变量和执行静态代码块。JVM在使用某个类之前,会确保该类已经完成初始化。
    类加载器:JVM中有三个重要的类加载器:

    当Java程序在JVM中运行时,存在多个类加载器。类加载器负责将字节码文件加载到内存中,并创建Java类的运行时数据结构。下面是JVM中常见的类加载器:

    • 启动类加载器(Bootstrap Class Loader):

      • 它是JVM的一部分,是最顶层的类加载器。它负责加载核心Java类库(如java.lang包中的类)。启动类加载器使用本地代码来实现,通常由JVM的实现提供。
      • jvm启动的时候,会优先加载/lib这个目录的核心类库
    • 扩展类加载器(Extension Class Loader):

      • 扩展类加载器负责加载Java的扩展库,它从jre/lib/ext目录下的JAR文件或其他目录中加载类。它是用Java代码实现的,是纯Java类。
      • 负责加载/lib/ext这个目录的类
    • 应用程序类加载器(Application Class Loader):

      • 也称为系统类加载器,它是加载应用程序的类文件的加载器。它是通过Java类sun.misc.Launcher$AppClassLoader实现的。
      • 负责加载我们自己书写的代码中的类
    • 自定义类加载器:
      • 根据我们的需要,加载特定的类

    这些类加载器之间形成了层次结构,称为类加载器的委托模型。在委托模型中,一个类加载器首先会委托给它的父类加载器进行加载,只有在父类加载器无法加载该类时,子类加载器才会尝试加载。这种机制保证了类的一致性和安全性。

    常见的类加载器委托模型如下:

    • 应用程序类加载器是扩展类加载器的父加载器,并且启动类加载器是扩展类加载器的父加载器。
    • 这样的层次结构可以确保系统类库的类由启动类加载器加载,扩展类库的类由扩展类加载器加载,应用程序的类由应用程序类加载器加载。
    • 这种层次结构和委托模型确保了类的加载过程是可控和安全的,避免了类的冲突和不一致性。

    双亲委派机制:

    保证了系统的类加载安全

     除了这三种常见的类加载器,还可以通过自定义类加载器来实现特殊的加载需求。自定义类加载器可以继承java.lang.ClassLoader类,并重写其方法来实现自定义的加载逻辑。例如,可以通过自定义类加载器实现从数据库或网络中加载类文件。

    总结起来,JVM中存在多个类加载器,负责将字节码文件加载到内存中,并创建Java类的运行时数据结构。常见的类加载器包括启动类加载器、扩展类加载器和应用程序类加载器,它们按照委托模型进行加载,并构成一个层次结构。这种机制确保了类的一致性和安全性。同时,也可以通过自定义类加载器来实现特殊的加载需求

    3、执行阶段

            在Java程序的执行阶段,Java虚拟机使用解释器将字节码逐行解释为本地机器码并执行。这个过程涉及到解释器按照Java字节码指令集的规范,逐条解释执行字节码指令,以达到程序运行的目的。

            当解释器遇到方法调用时,它首先会定位到该方法的字节码指令集中的地址,然后解释执行方法体。方法体是按照Java字节码规范编码的指令序列,这些指令序列会执行方法中的各个操作。在解释执行方法体时,解释器会将Java虚拟机中的数据类型转换为本地机器码中的数据类型,并将字节码中的符号引用转化为直接引用。这样,在方法执行的过程中,解释器就可以直接调用本地计算机的硬件资源,实现Java程序在本地计算机上的执行。

            当解释器遇到对象创建时,它会先为对象分配内存空间。在Java中,对象是通过类实例化而产生的,因此解释器会根据类的定义和属性,分配足够的内存空间来存储对象的信息。然后,解释器会设置对象的属性,这些属性包括在类的定义中声明的变量以及在方法中赋值的变量等。最后,解释器会返回新创建的对象的引用,这个引用可以用来调用该对象的方法或者访问该对象的属性。

            需要注意的是,在执行阶段中可能会涉及到异常处理机制。如果程序在执行过程中出现了异常,Java虚拟机会根据异常类型抛出异常并终止程序的执行。因此,在编写Java程序时,需要考虑到可能出现的异常情况,并使用异常处理机制来避免程序崩溃或者出现不可预期的行为。

            总之,Java程序的执行阶段是Java虚拟机将字节码转换为本地机器码并执行的过程。这个阶段涉及到解释器逐行解释字节码指令并执行的方法调用和对象创建等操作。在执行过程中可能会涉及到异常处理机制来避免程序出现不可预期的行为。        

    解释器是Java语言的一种执行方式,也是Java虚拟机(JVM)的一部分。它的工作是把Java程序的字节码转化为机器码并在特定平台上运行。

    Java解释器使用了解释执行的模式,它在运行时逐行解释并执行Java程序的字节码。Java解释器将字节码转换成底层的机器指令,并在运行时动态地执行这些指令,从而实现Java程序的运行。

    此外,解释器相当于运行Java字节码的“CPU”,但该“CPU”不是通过硬件实现的,而是用软件实现的。

    总结一下:

    1. Java虚拟机使用解释器将字节码逐行解释为本地机器码并执行。
    2. 解释器按照Java字节码指令集的规范逐条解释执行字节码指令。
    3. 在方法调用时,解释器会定位到方法的字节码指令集中的地址,然后解释执行方法体。
    4. 解释器将Java虚拟机中的数据类型转换为本地机器码中的数据类型,并将字节码中的符号引用转化为直接引用。
    5. 在对象创建时,解释器会先为对象分配内存空间,然后设置对象的属性,最后返回新创建的对象的引用。
    6. Java程序的执行阶段涉及到异常处理机制来避免程序出现不可预期的行为。
    7. 解释器是Java虚拟机(JVM)的一部分,它把Java程序的字节码转化为机器码并在特定平台上运行。
    8. 解释器使用解释执行的模式,将字节码转换成底层的机器指令,并在运行时动态地执行这些指令。
    9. 解释器相当于运行Java字节码的“CPU”,但该“CPU”不是通过硬件实现的,而是用软件实现的


    什么时候?开始分配内存呢?

    在Java代码执行时,根据不同的方法、属性、修饰符等,内存分配的时间和位置会有所不同。以下是一般的先后顺序和内存分配情况:

    1. 类加载阶段:在程序执行前,Java虚拟机首先需要加载类文件,这个阶段会为类分配内存。类加载器将.class文件中的字节码数据读入内存中,并生成对应的Class对象。这些Class对象和对应的字节码数据会存储在Java虚拟机的方法区(Method Area)中。
    2. 变量和对象创建阶段:在Java程序执行过程中,会创建变量和对象。局部变量(例如,方法中的参数)会在栈区(Stack)中分配内存,而对象(通过new关键字创建的实例)会在堆区(Heap)中分配内存。此外,如果对象引用另一个对象,那么这些被引用的对象也会在堆区中分配内存。
    3. 方法调用阶段:在Java程序执行过程中,会调用方法。每次方法调用都会在栈区中创建一个新的栈帧(Stack Frame),这个栈帧中包含了局部变量、操作数栈等信息。当方法调用结束时,对应的栈帧就会被弹出,释放其占用的栈区内存。
    4. 修饰符修饰:Java程序中的修饰符(例如public、private、protected等)并不直接参与内存分配。它们主要影响的是变量的访问权限(也就是变量在哪些范围内可以被访问)。

    总的来说,Java的内存主要分为以下几个区域:

    • 堆区(Heap):这是Java用于动态分配内存的区域,所有的对象实例以及数组都是在堆上分配的。
    • 栈区(Stack):这是用于存放方法调用和局部变量的区域。它的内存分配和回收都由Java虚拟机自动完成,而且它的空间大小是有限的。
    • 方法区(Method Area):也称为Non-Heap(非堆)区,它存储已被加载的类信息、常量、静态变量等数据。方法区的内存回收目标主要针对常量池的回收和对类型的卸载。
    • 程序计数器(Program Counter Register):这是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

    需要注意的是,这些区域的命名和具体工作方式可能会因不同的Java虚拟机实现有所差异。但是总的内存管理策略和原则是一致的:即通过类加载器将.class文件中的字节码数据读入内存中,然后根据需要分配到不同的内存区域中。

  • 相关阅读:
    数据结构——优先级队列(堆)
    mac gitee新建工程遇到的一些问题
    PC业务校验(已有该名称,已有该编码)
    lwip多网卡自适应选择
    【C语言】文件操作
    【AcWing】828. 模拟栈
    10 【异步组件 组合式函数(hooks)】
    C++设计模式之MVC
    什么是边缘计算网关?
    550. 游戏玩法分析 IV
  • 原文地址:https://blog.csdn.net/weixin_72186894/article/details/132856424