深入理解Java虚拟机——Java内存区域

news2024/10/4 9:36:27

1.前言

Java内存区域也叫运行时数据区域,要记得把Java内存模型(JMM区分开来)。
请添加图片描述

根据线程是否共享可以把运行时数据区如上图所分。

  • 线程共享
    • 堆内存
    • 方法区
  • 线程私有
    • 栈内存
      • 本地方法栈
      • 虚拟机栈
    • 程序计数器

接下来,将逐个介绍每个内容。额外说一句,线程私有的三部分内容(程序计数器虚拟机栈本地方法栈)的生命周期都跟线程相同,也就是说随着线程的创建而初始化,伴随着线程的死亡而死亡。

2.Java内存区域

程序计数器

程序计数器是一块较小的内存空间,它可以看作是当前线程执行的字节码(.class)的行号指示器

字节码解释器工作的时候就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

它是程序控制流的指示器,分支、判断、循环、跳转、异常处理、线程恢复等基础功能都要依赖这个计数器完成。

如果正在执行的是Java方法,那么程序计数器指向正在执行的字节码地址;如果正在执行的是Native方法(本地方法),那么计数器的值应该为空。本地方法是Java内部用C++实现的方法。

程序计数器是唯一一个在Java虚拟机规范中没有规定任何OOM(OutOfMemoryError)情况的区域。

Java虚拟机栈

虚拟机栈是Java方法执行的线程内存模型,每个方法执行的时候都会创建一个栈帧,栈帧中存放了局部变量表操作数栈动态连接方法出口等信息。每个方法被调用直至执行完毕,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表

局部变量表存放了编译器可知的各种Java虚拟机基本数据类型(byte、boolean、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他的与此对象相关的位置)和returnAddress类型(指向了一条字节码指令地址)。

局部变量表中的存储空间以局部变量槽位单位,64位长度的long和double类型的数据会占用两个槽,其余的数据类型只占用一个槽。局部变量表所需要的空间(槽的个数)在编译期间就会完成分配,换句话说,局部变量表中的槽的个数在编译期间就会被确定下来,整个运行期间不会改变局部变量表的大小。而一个变量槽的大小是32kb还是64kb完全由虚拟机的具体实现决定。

这么说太抽象了,我们举个例子,动动手看一下字节码会稍微不那么抽象。

public class HelloWorld {
    public static void main(String[] args) {
        String str = "Hello World!";
        System.out.println(str);
    }
}

这段代码很简单。字节码的内容说到底其实就是字节流,硬看也能看,但是不太方便。我们可以使用javap命令查看字节码的内容,这样会更易懂些。这里只放了部分字节码,现在只是提一下。

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: ldc           #2                  // String Hello World!
         2: astore_1
         3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         6: aload_1
         7: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        10: return
      LineNumberTable:
        line 7: 0
        line 8: 3
        line 9: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  args   [Ljava/lang/String;
            3       8     1   str   Ljava/lang/String;
}

第五行就注明了操作数栈(stack)的深度以及局部变量表(locals)的槽个数。虽然如此,但是你不可以理解为操作数栈的深度也是在编译期间就已经确定了的。字符串在编译期间就会被放入常量池,ldc是将常量池中的Hello World!加载到操作数栈中,astore_1是将变量从操作数栈中存储到局部变量表的1号槽位。你可能会好奇为什么明明只有一个变量,但是这里为什么局部变量表的深度为2,这是因为0号槽位被用来存储this指针,this指针指向当前对象,所以实际使用了两个槽位。

操作数栈

操作数栈也可以称之为表达式栈(Expression Stack),在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)。某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈,使用它们后再把结果压入栈,比如:执行复制、交换、求和等操作。操作数栈,主要用于保存计算过程的中间结果同时作为计算过程中变量临时的存储空间

在上述字节码中的第六行aload_1就是将1号局部变量槽中的值加载到操作数栈,然后将执行打印操作。

异常

在《Java虚拟机规范》中,对虚拟机栈规定了两类异常。

  • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度,则会抛出该异常
  • OutOfMemoryError:如果Java虚拟机栈容量可以动态扩展(Java默认的虚拟机HotSpot不支持栈容量动态扩展),当栈扩容时无法申请到足够的内存时则会抛出该异常。

本地方法栈

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

Java堆

对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java 世界里“几乎”所有的对象实例都在这里分配内存。

这里“几乎”是因为即时编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配、标量替换优化手段已经导致一些微妙的变化悄然发生,所以所有的Java对象实例都在栈内存上分配也渐渐的不是那么绝对了。

配置堆内存

Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的。

  • -Xmx:Java Heap最大值,默认值为物理内存的1/4,最佳设值应该视物理内存大小及计算机内其他内存开销而定;
  • -Xms:Java Heap初始值,Server端JVM最好将-Xms和-Xmx设为相同值,开发测试机JVM可以保留默认值;

异常

  • OutOfMemoryError:如果Java堆内存中没有足够内存完成实例的分配,并且堆也无法扩展时,Java虚拟机会抛出该异常。

方法区

方法区与Java堆一样,是各个线程共享的内存区域。它用于存储已被虚拟机加载的类型信息常量(使用final关键字修饰)、静态变量(使用static关键字修饰)、即时编译器编译后的代码缓存等数据

在这里就不得不提到,元空间和永久代的概念。方法区实际上是属于《Java虚拟机规范》中的一个逻辑部分。而永久代和元空间都是HotSpot虚拟机对方法区的实际实现,有点类似接口和实现类的关系。

永久代和元空间

JDK1.6的HotSpot中,对方法区的实现为永久代,永久代使用了堆内存的一部分作为实现,此时字符串常量池、静态变量都存放在永久代中

请添加图片描述

到了JDK1.7的HotSpot,将原本存在于永久代中的字符串常量池、静态变量移出到堆内存中。

请添加图片描述

再到了JDK1.8的HotSpot中,对永久代的实现变为元空间,元空间不再使用堆的内存,而是使用本地内存,即操作系统的内存,但是静态变量和字符串常量池仍然存在于堆内存中。

请添加图片描述

之所以这么做,是因为使用堆内存的一部分作为永久代来实现方法区并不是一个好的决定,这种设计导致了Java应用更容易遇到堆内存内存溢出的问题。(永久代可以使用-XX: MaxPermSize来设定上限,即使不设置也有默认大小)

配置元空间

  • -XX:MetaspaceSize=N:设置 Metaspace 的初始(和最小大小)
  • -XX:MaxMetaspaceSize=N:设置 Metaspace 的最大大小

异常

  • OutOfMemoryError:如果方法区无法满足新的内存分配需求时,将抛出该异常。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分

Class文件中除了有类的版本、字 段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量 一定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。

异常

  • OutOfMemoryError:既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存 时会抛出该异常。

3.关于StringTable的拓展知识

intern()方法

上面提到了String的intern(),而intern()方法的作用是主动将字符串对象的值放入StringTable

  • 如果StringTable中没有该字符串对象,则将该字符串对象放入StringTable,返回StringTable中的该值(地址)

    • 如果是JDK1.6及之前的版本,并不是直接将该字符串对象放入StringTable而是将创建一个副本,将该副本放入StringTable
  • 如果有该字符串对象,则直接返回该字符串对象的地址

创建字符串的两种方式

  • String str = “string”

    • 这种方式首先会查看StringTable中是否已经存在我们要创建的字符串,如果已经存在,就直接将该变量指向常量池中的字符串;如果不存在就会创建该字符串对象,同时将该字符串放入StringTable中
  • String str = new String(“string”)

    • 这种方式将会重新开辟内存将该字符串对象放入堆内存中,不管StringTable中是否已经存在该字符串。同时如果StringTable中没有该字符串,会将该字符串放入StringTable中。

注意:不管以哪种方式创建字符串,如果StringTable中没有该字符串,就会将其加入其中。只不过第一种方式会查看StringTable中是否有该字符串,如果没有就创建,有的话则直接引用。

字符串拼接的两种方式

  • String str = “a” + “b”

    • 该拼接方式会在编译期进行优化,如果字符串"ab"已经存在于StringTable中,那么会将该字符串对象引用StringTable中的值
  • String str = new String(“a”) + new String(“b”)

    • 该拼接方式会在运行期进行优化,如果对字节码进行反编译,会发现其实这种底层拼接方式是使用了StringBuilder进行拼接,最后调用StringBuilder的toString方法,效果等价于new String(),所以以该种拼接方式产生的字符串对象并不会存在于StringTable中,而是在堆内存中

参考:《深入理解Java虚拟机》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/421870.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是文件传输协议,文件传输协议又是怎么工作的

文件传输协议FTP是一种仍在使用的协议,在上载和下载文件时仍然比较流行,通常是那些太大的文件,需要花费很长时间才能通过常规电子邮件程序作为附件下载进行传输。 从技术上讲,它是“文件传输实用程序”,是许多TCP / I…

腾讯云4核8G12M轻量服务器配置性能评测

腾讯云轻量4核8G12M服务器,之前是4核8G10M配置,现在公网带宽和月流量包整体升级,12M公网带宽下载速度可达1536KB/秒,系统盘为180GB SSD盘,每月2000GB免费流量,腾讯云百科来详细说下4核8G12M轻量应用服务器配…

碳化硅材料在功率半导体中的优劣

开关电源工作频率的提高受到开关损耗的制约 开关电源的工作频率是指开关变换器操作的频率。在开关电源中,一个开关变换器被用来将直流(DC)能源转换为可用于电子设备的交流(AC)能源。开关变换器的基本原理是通过对开关…

3.4 函数的单调性和曲线的凹凸性

学习目标: 如果我要学习函数的单调性和曲线的凹凸性,我会采取以下几个步骤: 理解概念和定义:首先,我会学习单调性和凹凸性的定义和概念。单调性是指函数的增减性质,可以分为单调递增和单调递减&#xff1b…

Python使用PyQt5实现指定窗口置顶

文章目录前言一、网上找到的代码二、尝试与借鉴后的代码——加入PyQt界面1.引入库2.主代码3.完整主代码4.UI界面代码总结前言 工作中,同事随口提了一句:要是能让WPS窗口置顶就好了,老是将窗口切换来切换去的太麻烦了。 然后,这个…

docker-compose 安装nginx php mysql phpadmin

一 摘要 本文主要介绍基于docker docker-compose 安装 lnmp 三件套,以及用phpmysadmin 验证下部署可正确。 二 环境信息 2.1 操作系统 [root2023001 ~]# cat /etc/centos-release CentOS Linux release 7.9.2009 (Core) [root2023001 ~]#2.2 docker [root20230…

【opencv】图像数字化——认识OpenCV中的Mat类( 7 访问多通道Mat对象中的值)

7 访问多通道Mat对象中的值 7.1使用成员函数at() #include <opencv2/core/core.hpp> #include<iostream> using namespace std; using namespace cv; int main() {Mat mm (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 32), Vec3f(3, …

C++【深入理解多态】

文章目录一、多态概念与实现&#xff08;1&#xff09;多态的概念&#xff08;2&#xff09;怎么构成多态&#xff08;3&#xff09;虚函数重写的2个例外&#xff08;4&#xff09;经典剖析巩固知识点&#xff08;5&#xff09; override 和 final&#xff08;6&#xff09;小总…

YOLO算法改进指南【初阶改进篇】:2.改进DIoU-NMS,SIoU-NMS,EIoU-NMS,CIoU-NMS,GIoU-NMS

非极大值抑制(Non-maximum Suppression (NMS))的作用简单说就是模型检测出了很多框,我应该留哪些。 本篇将演示如何修改:NMS、Merge-NMS、Soft-NMS、CIoU-NMS、DIoU-NMS、GIoU-NMS、EIoU-NMS、SIoU-NMS 1. NMS过程 NMS过程 For a prediction bounding box B, the model c…

基于JDK11从源码角度剖析可重入锁ReentrantLock的获取锁和解锁

ReentrantLock是可重入的独占锁&#xff0c;同时只能有一个线程可以获取该锁&#xff0c;其他获取该锁的线程会被阻塞而被放入该锁的AQS阻塞队列里面。 ReentrantLock是JUC包提供的显式锁的一个基础实现类&#xff0c;实现了Lock接口。我们先来看下ReentrantLock的类图&#x…

SpringBoot WebSocket服务端创建

引入maven <!--websocket--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>新建WebSocket配置文件 import org.springframework.context.annotatio…

【蓝桥杯嵌入式】第十四届蓝桥杯嵌入式省赛(第一场)客观题及详细题解

题1 解析  编码器&#xff0c;具有编码功能的逻辑电路&#xff0c;能将每一个编码输入信号变换为不同的二进制的代码输出&#xff0c;是一个组合逻辑电路。 答案 ABC 题2 解析   减法计数器的计数值到0时&#xff0c;会产生一个重装载值&#xff0c;此处重载后就会变成111…

改进YOLO系列:CVPR2023最新 PConv |提供 YOLOv5 / YOLOv8 模型 YAML 文件

论文链接:https://arxiv.org/pdf/2303.03667v2.pdf 一、论文介绍 为了设计快速神经网络,许多工作都集中在减少浮点运算(FLOPs)的数量上。然而,作者观察到FLOPs的这种减少不一定会带来延迟的类似程度的减少。这主要源于每秒低浮点运算(FLOPS)效率低下。 为了实现更快的…

buildSrc + gradle插件:多项目共享gradle依赖管理

自定义gradle 插件&#xff0c;配合 buildSrc 形式的组件库版本管理&#xff0c; 用于实现多 project 项目共享一套版本管理信息 前言 随着组件化越来越常见&#xff0c;module数量越来越多&#xff0c;依赖管理的混乱问题大家想必是都遇到过甚至正在经历着。 对于依赖管理的…

iOS - 接入 Live2D

1.安装 Cmake 1.1 从官方下载 https://cmake.org/download/ 下载成功以后,在终端输入 sudo "/Applications/CMake.app/Contents/bin/cmake-gui" --install校验是否成功 cmake --version1.2 从 Homebrew 安装 (这个方法没有成功) brew install cmake如果提示 co…

简单的配置Sawgger+knife4j完成API测试功能

目的&#xff1a;减少postman的使用&#xff0c;以及生成对应的接口文档 1、添加依赖 基于自身spring boot 版本2.7.X 我选择的是&#xff1a; <dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId>…

网络中的一些基本概念

组建网络的重要设备 集线器,交换机(组建局域网,不能跨局域网组建网络),路由器(wifi本质上是无线路由器,路由器的本质的把俩个局域网给连起来) 网络通信的一些基础概念 IP地址 标识了网络设备所在的位置 端口号 标识了一个具体的应用程序 协议 协议是网络通信的概念,约定好…

校园安全AI视频行为分析系统 yolov7

校园安全AI视频行为分析系统以yolov7网络模型算法为核心&#xff0c;校园安全AI视频行为分析算法模型对现场画面中学生打架、异常跌倒、攀爬翻墙、违规闯入、明火烟雾、睡岗离岗、抽烟打电话等行为主动识别预警存档。YOLOv7 在 5 FPS 到 160 FPS 范围内&#xff0c;速度和精度都…

计算机系统-存储器层次结构

本篇不是学习课程时的笔记&#xff0c;是重看这本书时的简记。对于学习本课程的同学&#xff0c;未涉及的内容不代表考试不涉及&#xff0c;部分省略的部分是在该课程的讨论课中学习的(存储器山&#xff0c;矩阵乘法)&#xff0c;对于核心内容的掌握&#xff0c;需要学习相关实…

还在crud?快来学习架构设计啦---微服务下的依赖管理(maven篇)

文章目录一、前言二、实战2.1 创建父工程统一依赖的版本管理2.2 创建公共使用的 common工程2.3 创建子工程并引入父工程的依赖以及公共工程2.4 搭建启动环境2.5 启动程序开始验证三、总结一、前言 2023年口罩放开的第一年&#xff0c;大多数人都是想着重新开始&#xff0c;抓住…