JVM 内存结构?

ops/2024/11/14 12:58:05/

JVM 内存结构

这里的JVM内存结构,是指Runtime Data Areas(运行时数据区)。包含:

  • 方法区(Method Area)
  • 虚拟机栈(VM Stack)
  • 本地方法栈(Native Method)
  • 堆(Heap)
  • 程序计数器(Program Counter Register)

0

各部分是否私有

通过上图可以知道:

  1. 线程私有的部分:程序计数器本地方法栈虚拟机栈
  2. 线程间共享的部分:方法区

程序计数器

注意:这里的 程序计数寄存器物理中的****CPU的寄存器 是不一样的。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。 它是线程私有的

工作细节:通过改变这个计数器的值来选取下一条需要执行的字节码指令程序控制流,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成

虚拟机栈

虚拟机栈(Java Stack),也叫Java栈,就是我们平常说的线程私有的

除了Native 方法(它通过本地方法栈实现),其他的 Java 方法调用都是通过栈来实现的(也需要和其他运行时数据区域比如程序计数器配合)。

每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息

img
  • 局部变量表:存放局部变量的,以**局部变量槽(Slot)**来表示。局部变量表的大小是确定的,在编译期间完成分配的。

  • 操作数栈:主要作为方法调用的中转站使用,用于存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中

  • 动态连接:用于 一个方法需要调用其他方法的场景。ava 源文件被编译成字节码文件时,变量、方法的引用都是 符号引用(保存在Class 文件的常量池里)。动态链接 就是在方法调用其他方法时,将 符号引用转换为 直接引用的

问:垃圾回收是否涉及栈内存?

答:不会,因为栈的运行结束后会按从顶至底的顺序移除栈对应的线程的方法,所以不需要垃圾回收机制来处理长久不用的垃圾。

问:栈内存分配越大越好吗?

答:不是的,我们的物理内存是有限的,如果栈内存分配过大,会导致我们能运行的线程数变少。

可以通过-Xss size(分配的大小)来指定栈内存的大小。

问:方法内的 局部变量 是线程安全的吗?

分析:如果它是线程私有的,那肯定是线程安全的。否则就是线程县城不安全的。

答:分情况:

  1. 如果这个方法中的局部变量不是static修饰。那就是线程安全的。
  2. ①如果这个方法中的局部变量是static修饰。那就是线程不安全的。②如果传入的参数是一个可变对象,并且这个对象在多个线程中被共享和修改,那就可能出现线程安全问题。

本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机 栈为虚拟机执行 Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务

一些带有native关键字的方法就是需要JAVA去调用本地的C或者C++方法。

Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java 世界里“几乎”所有的对象实例都在这里分配内存。

但也不绝对,从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去)那么对象可以直接在栈上分配内存

Java 堆区域可细分为:

在这里插入图片描述

  • JDK7 及之前分为:新生代内存(Young Generation)、老生代(Old Generation)、永久代(Permanent Generation)
  • JDK8 及之后:新生代内存(Young Generation)、老生代(Old Generation);Metaspace(元空间) (元空间:使用的是本地内存,它只是替代了 永久代 的作用,但**它不属于 堆内存,而是存放于本地内存**)

1、新生代内存(Young Generation):

  • 作用:用于存放新创建的对象。大部分对象通常具有短暂的生命周期,因此在新生代中频繁进行垃圾回收,可以高效地回收大量短生命周期的对象,减少内存占用。
  • GC回收:新对象会先被分配到Eden区。当Eden区满了之后触发Minor GC(新生代垃圾回收)。在回收过程中,存活下来的对象会被移到其中一个Survivor区;当Survivor区也满了,对象会进一步晋升到老年代。
  • 组成:由年轻区(Eden区)、两个Survivor区from survivorto survivor)。默认情况下,新生代的Eden区和Survivor区的空间大小比例是8:2,可以通过-XX:SurvivorRatio参数调整。

在这里插入图片描述

2、老年代:存放经过多次垃圾回收仍然存活的对象,或者是大对象(某些JVM可以直接在老年代分配大对象)。老年代中的对象被认为是长时间存活的,因此回收的频率较低。

大对象:通常指的是需要占用较多连续内存空间的Java对象实例

3、永久代、元空间:(二者作用一样,只是元空间 替代 永久代)

  • 作用:它是具体实现的方法区:用来存放类的元数据信息,包括类的结构信息、常量池、静态变量、即时编译后的代码等。

为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?

主要考虑的是,永久代使用的是JVM内存,因此,当存放的数据太多时,很容易导致永久代内存溢出(PermGen OutOfMemoryError)。

而元空间使用本地内存而不是 JVM 堆内存,理论上只受限于操作系统的内存大小。这大大减少了因为类信息、常量等存储导致内存溢出的风险。

方法区

方法区(Method Area)是线程共享的。它是Java 虚拟机规范的一个逻辑部分,别名 非堆(Non-Heap),目的是与 Java的 堆(Heap)区分开。在不同的虚拟机实现上,方法区的实现是不同的。

方法区主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码、运行时常量池。虚拟机启动时就会创建方法区。

JDK中方法区的不同实现

以HotSpot虚拟机来讲,在JDK不同版本汇总,主要关注字符串常量池位置变迁

  1. Jdk1.6及之前: 有永久代,运行时常量池在永久代,运行时常量池包含字符串常量池
  2. Jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池从永久代里的运行时常量池分离到堆里
  3. Jdk1.8及之后: 无永久代,运行时常量池在元空间,字符串常量池里依然在堆里
    在这里插入图片描述

问:为什么要将字符串常量池移动到堆中?

答:主要是因为永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集 (Full GC)的时候才会被执行 GC。Java 程序中通常会有大量的被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效及时地回收字符串内存

String 类的 intern()方法

参考:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

调用字符串对象的intern方法,会将该字符串对象尝试放入到字符串常量池中:

  • 如果字符串常量池中没有该字符串对象,会将该字符串对象复制一份,再放入到字符串常量池中;
  • 如果有该字符串对象,则放入失败;

知道了intern()方法的作用,下面来看看String中创建字符串时的区别(以下面的代码示例):

要知道:

  1. 直接使用双引号声明出来的String对象会直接存储在字符串常量池中。如:String a = "jack";
  2. 使用new创建的String对象,则会在 字符串常量池中创建字符串,并在堆中创建String对象。
public static void main(String[] args) {String s = new String("1");s.intern();String s2 = "1";System.out.println(s == s2);String s3 = new String("1") + new String("1");s3.intern();String s4 = "11";System.out.println(s3 == s4);
}
JDK1.6 中输出 false、false

在这里插入图片描述

因为,JDK1.6中,字符串常量池 位于 堆的永久代区域(我们知道 JVM 堆中的 永久代 是是具体实现的方法区),而上图中的 堆(Heap)是特指新生代、老年代。所以:

  1. String s = new String("1");:会在 字符串常量池 中创建字符串"1”,然后在JAVA 堆中创建String对象obj(存储的是 字符串常量池“1”的引用),然后栈中的s指向Java heap中的 obj
  2. String s2 = "1";是栈中的s2直接去 字符串常量池 中查找是否有“1”,有则直接指向。无则在字符串常量池中创建,然后再指向。
jdk1.7 中输出 false、true

在这里插入图片描述

jdk1.7中,字符串常量池 从永久代 移动到正常的 Java 堆(中的 年轻代、老年代)中了。


http://www.ppmy.cn/ops/113381.html

相关文章

五、I/O与网络编程-5.2、网络编程

5.2、网络编程 5.2.1、什么是网络四元组? 答: 网络四元组(Network Quadruple)是指在计算机网络中,用于标识通信连接的四个元素,分别是源IP地址、源端口号、目的IP地址和目的端口号。它们共同组成了网络通…

【Unity踩坑】UI Image的fillAmount不起作用

在游戏场景中,我们经常在界面上展示进度条,当然有各种形状的,线性的,长方形的,圆形,环形等等。 Unity中实现这种效果的话,最基本的方法说是改变Image的fillAmout属性。 如果你是初次使用UI Ima…

数据结构和算法之树形结构(1)

文章出处: 数据结构和算法之树形结构(1) 关注码农爱刷题,看更多技术文章!! 树形结构是数据结构四种逻辑结构之一,也是被广泛使用的一种逻辑结构,它描述的是数据元素之间一对多的逻辑关系。树是一种非线性的数据结构&a…

MATLAB基础:7.计算与编程策略

计算与编程策略 一、矢量化编程 MATLAB以矩阵为基本元素 什么是矢量化编程 将矩阵视为一个整体,对矩阵中的元素同时进行某种操作或运算,即整块的操作大量数据 矢量化编程的优点 代码大大简化,编程效率高,代码可读性高程序执行…

Git常用指令大全详解

Git常用指令大全详解 Git,作为目前最流行的分布式版本控制系统,其强大的功能和灵活性为开发者提供了极大的便利。无论是个人项目还是团队协作,Git都扮演着不可或缺的角色。本文将详细总结Git的常用指令,帮助大家更好地掌握这一工…

openGemini 社区人才培养计划:助力成长,培养新一代云原生数据库人才

一、摘要 在技术革新的浪潮中,数据库技术是现代信息技术的基石,openGemini社区携手开发者,启动人才培养计划,旨在培养新一代云原生数据库技术人才,共同推动云原生数据库技术创新。 二、社区介绍 openGemini是一款开…

Unity引擎绘制多边形属性图

大家好,我是阿赵   在制作游戏的时候,经常会遇到需要绘制多边形属性图的需求,比如这种效果: 可以根据需要的属性的数量变化多边形的边数,然后每一个顶点从中心点开始到多边形的顶点的长度代表了该属性的强度&#xf…

排序题目:三次操作后最大值与最小值的最小差

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题:三次操作后最大值与最小值的最小差 出处:1509. 三次操作后最大值与最小值的最小差 难度 5 级 题目描…