JAVA基础—JVM内存结构基础需知

2024-03-16 03:04

本文主要是介绍JAVA基础—JVM内存结构基础需知,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.JVM内存结构

JVM内存结构分为5个区域:方法区,虚拟机栈,本地方法栈、堆、程序计数器。
在这里插入图片描述
1.方法区(Method Area):用于存储类的结构信息、常量、静态变量、即使编译器编译后的代码等数据。方法区也是所有线程共享的区域。
2. 堆(Heap):堆时Java虚拟机管理的最大的一块内存区域,用于存储对象实例。Java堆时所有线程共享的内存区域,在Jvm启动时就被创建。Java堆可以分为新生代(Young Generation)、老年代(Old Generation)等区域,用于实现垃圾回收。
3. 虚拟机栈(VM Stack):每个线程在创建时会被分配一个虚拟机栈,用于存储方法调用的栈帧。栈帧包含局部变量表、操作数栈、动态链接、方法返回地址等信息。在方法调用时,会创建一个新的栈帧压入虚拟机栈,并在方法返回时将栈帧出栈。
4. 本地方法栈(Nactive Method Stack):类似于虚拟机栈,用于支持本地方法(Native Method)的调用
5. 程序计数器(Program Counter Register):是一块较小的内存空间,可以看作时当前线程所执行的字节码的行号指示器。在多线程环境下,每个线程都有一个独立的程序计数器,用于唇齿当前线程正在执行的指令地址。

2.方法区(Method Area)—线程共享

在进行类加载时,方法区会存储类的元数据信息。方法区内的类卸载很苛刻,所以正常是认为方法区内没有垃圾回收的。
类的卸载通常需要满足一定的条件,包括:

  • 类实例的引用数量为零:即没有任何类的实例被引用,包括静态变量对该类的引用。
  • 类的ClassLoader被卸载:ClassLoader负责加载类到内存中,当ClassLoader被卸载时,它加载的类也会被卸载。
  • 没有被其他类引用:即没有其他类通过反射等方式引用该类。

在方法区,还有一个空间,运行时常量池(Runtime Constant Pool):是方法区的一部分,用于存储编译期生成的各种字面量和符号引用。

3.堆(Heap)—线程共享

堆(Heap)是Java虚拟机管理机管理的最大一块内存区域,用于存储对象实例。堆内存是所有线程共享的内存区域,在JVM启动时就被创建。在堆内存中,会被动态地分配内存,用于存储新创建的对象。

  • 堆内存通常被划分为几个不同的区域,包括:
    1. 新生代(Young Generation):新创建的对象会被分配到新生代中。新生代通常使用复制算法(Copying),将内存分为两块,一块为Eden区,另一块有两个 Survivor 区,通常称为 Survivor0 和 Survivor1(或者叫做 From 区和 To 区)。
      Survivor两个区是对称的,没有先后关系,所以在新生代的垃圾回收过程中,对象会从 Eden 区复制到其中一个 Survivor 区,然后经过多次垃圾回收后仍然存活的对象会被移动到另一个 Survivor 区。最终,经过多次垃圾回收后仍然存活的对象会被移动到老年代。因此,同一个 Survivor 区可能同时存在来自 Eden 区和另一个 Survivor 区的对象,而复制到老年代的对象只有从第一个 Survivor 区过来的对象。
    2. 老年代(Old Generation):老年代主要存储一些较大的、存活时间较长的对象。在新生代经过多次垃圾回收后仍然存活的对象会被移动到老年代。老年代通常使用标记-清除(Mark-Sweep)算法或标记-整理(Mark-Compact)算法进行垃圾回收。

在这里插入图片描述

  • 在堆中的字符串常量池(String Pool)

    字符串常量池用于存储字符串常量。在Java中,字符串常量通常在编译器就确定了其值,并且会被放入到字符串常量池中。当程序中需要使用相同内容的字符串常量时,虚拟机会直接从字符串常量池中获取,而不是创建一个新的字符串对象。
    举个例子,对于JVM底层,String str = new String(“test”)创建对象流程:

    1. “test”字符串:JVM会首先检查字符串常量池是否已经存在相同的字符串,如果存在则直接返回该对象的引用,否则就在字符串常量池中创建一个新的字符串对象。
    2. ‘new String(“test”)’这部分代码会在堆内存中创建一个新的字符串对象,即使字符串常量池已经存在了“test”的字符串常量。但因为使用了‘new’关键字,明确要求创建一个新的对象,而不是直接使用常量池中已有的对象。
      因此,这一行代码会生成两个对象。而日常我们创建字符串常量值,直接使用String bb=“bbb”,这种创建方式只会在字符串常量池创建一个对象。
      它和普通的堆内存有一些区别,例如,字符串常量池中的字符串对象是不可变的,而且具有一定的缓存机制,以提高字符串对象的复用率和性能。

4.JAVA虚拟机栈(VM Stack)—线程私有

首先知道:栈后进先出
虚拟机栈用于存储方法调用的信息,包括局部变量表、操作数栈、动态链接、方法返回地址等。

  1. 方法调用:每次方法调用时,都会创建一个新的栈帧压入虚拟机栈,方法执行结束后,对应的栈帧会被弹出。

    	  public static void main(String[] args) {int a = 1;int b = 2;int sum = add(a, b);System.out.println("sum: " + sum);}public static int add(int a, int b) {return a + b;}
    

    mian方法在jvm中的执行可以用以下步骤来描述:

    1. 调用 main 方法,创建 main 方法的栈帧。
    2. 在 main 方法中声明局部变量 a 和 b,并将值赋给它们。
    3. 调用 add 方法,创建 add 方法的栈帧。
    4. 在 add 方法中将 a 和 b 相加,并将结果返回。
    5. add 方法执行结束,其栈帧被弹出,返回到 main 方法。
    6. 在 main 方法中将 add 方法返回的结果存储到 sum 变量中。
    7. 打印 sum 的值。
    8. main 方法执行结束,其栈帧被弹出,程序结束执行。
  2. 局部变量:每个栈帧中都包含一个局部变量表,用于存储方法中的局部变量。局部变量表中存储的是基本数据类型和对象引用,不存储对象本身。
    在 Java 中,对象的引用通常是存储在栈上的,而对象实例则存储在堆上。当我们创建一个对象时,实际上是在堆上分配了一块内存来存储对象的实例数据,并返回一个引用指向这个对象。这个引用会被存储在栈上,作为对堆中对象的引用。

  3. 操作数栈:每个栈帧都包含一个操作数栈,用于存储方法执行过程中的操作数。操作数栈用于执行方法的运算操作,如加减乘除等。

  4. 异常处理:虚拟机栈也用于异常处理。当方法出现异常时,虚拟机会查找虚拟机栈中的异常处理器,以确定如何处理异常。

虚拟机栈的大小可以通过 JVM 的启动参数来指定,例如 -Xss 参数用于指定每个线程的栈大小。虚拟机栈的大小会影响方法调用的深度,如果方法调用的层级过深,可能会导致栈溢出异常。

5. 本地方法栈(Native Method Stack)—线程私有

本地方法栈与虚拟机栈类似,用于支持Java调用本地方法(Native Method)的过程。本地方法栈也是线程私有的,每个线程在调用本地方法时都会创建一个对应的本地方法栈。
本地方法栈与虚拟机栈区别在于,虚拟机栈用于执行Java方法的Java字节码,而本地方法栈用于执行本地方法的机器码(Native Code)。本地方法是使用本地语言(如C、C++)编写的方法,通过Java的本地接口(JNI)调用。
本地方法栈与虚拟机栈的大小可以分别设置,并且本地方法栈的大小可能会影响到本地方法的调用。如果本地方法栈空间不足,可能会导致栈溢出异常。

在Java中,Native指的是使用其他编程语言(如C、C++)等编写的方法,这些代码通过Java的本地接口(JNI、Java Native Interface)来与Java代码进行交互。通过使用Native方法,Java程序可以调用本地系统的功能或者特定的硬件设备,从而实现更高级的功能和性能优化。
使用 Native 方法需要在 Java 中声明 Native 方法,并使用 native 关键字修饰,然后通过 JNI 在本地代码中实现这些 Native 方法。在运行时,Java 虚拟机会加载本地库,并通过 JNI 调用本地方法。需要注意的是,使用 Native 方法会降低程序的可移植性,并增加程序的复杂度,因此应该谨慎使用。

6. 程序计数器(Program Counter Register)—线程私有

程序计数器是一块较小的内存空间,也可以称为寄存器,在Java虚拟机中用于存储当前正在执行的字节码指令的地址或索引。在多线程环境下,每个线程都有一个独立的程序计数器,用于指示当前线程执行的位置。
在Java虚拟机中,程序计数器不会发生内存溢出(OutOfMemoryError)的情况,因为它只是一个指示器,并不会存储任何对象或数据。
在这里插入图片描述
程序计数器在不同情况下记录的内容略有不同:

  1. 执行Java方法时:程序计数器记录的是正在执行的虚拟机字节码指令的地址,即程序计数器存储的是下一条要执行的指令在方法内的偏移量。这样,当线程中断或切换后再恢复执行时,可以准确地知道接下来应该执行哪条指令。
    举例:

    	public class Example {public static void main(String[] args) {int a = 1;int b = 2;int sum = add(a, b);System.out.println("sum: " + sum);}public static int add(int a, int b) {return a + b;}
    }
    

    假设 add 方法的字节码指令如下(简化表示):

    1. 将局部变量 a 压入操作数栈
    2. 将局部变量 b 压入操作数栈
    3. 执行加法操作
    4. 将结果存入局部变量表
    5. 返回结果
      当程序执行到 add 方法时,程序计数器会记录当前执行的位置,比如可能会记录为第一个字节码指令的地址。随着方法的执行,程序计数器会逐步指向下一条要执行的字节码指令,以便虚拟机可以准确地控制方法的执行流程。
  2. 执行Native方法时:由于Native方法是由本地语言编写地,不是Java字节码,因此程序计数器通常是空的或者。

7.小结

JVM的内存结构只是JVM中很基础的一部分,但了解JVM的结构,对于我们学习JAVA的基础类很有帮助,比如说引用对象(栈)和实例对象(堆)的存储区域、String在JVM的存特殊设计(字符串常量池)、JAVA类初始化元数据加载的空间(方法区)、基本数据类型存储空间(栈)、静态变量(方法区)等等。
下面是jvm结构的总结:
在这里插入图片描述

这篇关于JAVA基础—JVM内存结构基础需知的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/814073

相关文章

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

Java NoClassDefFoundError运行时错误分析解决

《JavaNoClassDefFoundError运行时错误分析解决》在Java开发中,NoClassDefFoundError是一种常见的运行时错误,它通常表明Java虚拟机在尝试加载一个类时未能... 目录前言一、问题分析二、报错原因三、解决思路检查类路径配置检查依赖库检查类文件调试类加载器问题四、常见

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows