首页 > 其他分享 >JVM运行时数据区详解

JVM运行时数据区详解

时间:2023-09-05 22:03:04浏览次数:42  
标签:Java 对象 虚拟机 详解 线程 内存 JVM 方法 运行

Java内存区域详解(重点)

详解JVM运行时数据区之程序计数器

详解JVM运行时数据区之堆内存 (qq.com)

图片

JDK1.7 & JDK1.8的不同

Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。

JDK1.7

image-20230905114324674

JDK1.8

image-20230905114427564

线程私有的:

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈

线程共享的:

  • 堆外内存(永久代或元空间、代码缓存)

下面我们按顺序梳理下这些都是作咩的

1 程序计数器

程序计数器是一块较小的内存空间,可以看做是当前线程执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选去下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。

为了线程切换后能够恢复到正确的执行位置,每条线程都要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存为”线程私有“的内存。

总结一下,程序计数器的作用

  • 字节码解释器通过改变程序计数器来一次读取指令,实现代码的流程控制:如顺序执行、选择、循环、异常处理
  • 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪了

为何程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域?

因为它仅仅只是一个运行指示器,它需要存储的内容是下一个需要待执行的命令的地址。

无论代码有多少,最坏情况下死循环也不会让这块内存区域超限,因为程序计数器所维护的就是下一条待执行的命令的地址,所以不存在OutOfMemoryError。

2 虚拟机栈

每个线程在创建的时候都会创建一个Java虚拟机栈,其内部保存一个个的栈帧,对应着一次次Java方法调用,是线程私有的,生命周期和线程保持一致。

方法调用的数据需要通过Java虚拟机栈进行传递,每一次方法调用都会有一个对应的栈帧压入栈中,每一个方法调用结束后,都会有一个栈帧被弹出。

image-20230905172425140

由一个个栈帧组成,每个栈帧中都拥有局部变量表操作数栈动态链接方法返回地址。和数据结构上的栈类似,两者都是先进后出的数据结构,只支持出栈和入栈两种操作。

2.1 局部变量表

image-20230905173504776

局部变量表主要存放了编译期可见的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)

2.2 操作数栈

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

2.3 动态链接

image-20230905202111750

主要服务于一个方法需要调用其他方法的场景。Class文件的常量池里保存有大量的符号引用比如方法引用的符号引用。当一个方法要调用其他方法,需要将常量池中指向其他方法的符号转化为其在内存地址中的直接饮用。也叫动态连接。

动态链接的作用:将符号引用转化为调用方法的直接饮用。

栈空间正常调用情况下不会出现问题,如果多层递归出不来的话,就会导致栈中压入太多栈帧占用太多空间,导致栈空间过深。那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就会抛出StackOverflow错误

Java方法有两种返回方式,一种是return语句正常返回,一种是抛出异常。不管哪种返回方式,都会导致栈帧被弹出。也就是说,栈帧随着方法调用而创建,随着方法结束而销毁。无论方法正常完成还是异常完成都算方法结束

除了StackOverflow错误之外,栈还可能会出现OutOfMemoryError错误,这是因为如果栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError错误。

3 本地方法栈

和虚拟机栈发挥作用类似,惟一的区别是:虚拟机栈为虚拟机执行Java方法服务,本地方法栈则为虚拟机使用到的Naive方法服务

在HotSpot虚拟机中和Java虚拟机栈合二为一。

也会出现 StackOverFlowErrorOutOfMemoryError 两种错误。

4 堆

Java虚拟机所管理的内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

什么对象不在堆上分配内存呢

在Java中,典型的对象不在堆上分配的情况有两种:TLAB(Thread Local Allocation Buffer)和栈上分配(严格来说TLAB也是属于堆,只是在TLAB比较特殊)。JVM在Server模式下的逃逸分析可以分析出某个对象是否永远只在某个方法、线程的范围内,并没有“逃逸”出这个范围,逃逸分析的一个结果就是对于某些未逃逸对象可以直接在栈上分配。由于该对象一定是局部的,所以栈上分配不会有问题。

Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。为了进行高效的垃圾回收,虚拟机把堆内存逻辑上划分成三块区域(==分代的唯一理由就是优化 GC 性能==):

  • 新生带(年轻代):新对象和没达到一定年龄的对象都在新生代
  • 老年代(养老区):被长时间使用的对象,老年代的内存空间应该要比年轻代更大
  • 元空间(JDK1.8 之前叫永久代):像一些方法中的操作临时对象等,JDK 8 版本之后 PermGen(永久代) 已被 Metaspace(元空间) 取代,元空间使用的是本地内存

下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间区域属于老年代,右边的区域属于永久代。

图片

4.1 年轻代 (Young Generation)

年轻代是所有新对象创建的地方。当填充年轻代时,执行垃圾收集。这种垃圾收集称为 Minor GC。年轻一代被分为三个部分——伊甸园(Eden Memory)和两个幸存区(Survivor Memory,被称为from/to或s0/s1),默认比例是8:1:1

  • 大多数新创建的对象都位于 Eden 内存空间中
  • 当 Eden 空间被对象填充时,执行Minor GC,并将所有幸存者对象移动到一个幸存者空间中
  • Minor GC 检查幸存者对象,并将它们移动到另一个幸存者空间。所以每次,一个幸存者空间总是空的
  • 经过多次 GC 循环后存活下来的对象被移动到老年代。通常,这是通过设置年轻一代对象的年龄阈值来实现的,然后他们才有资格提升到老一代

4.2 老年代(Old Generation)

旧的一代内存包含那些经过许多轮小型 GC 后仍然存活的对象。通常,垃圾收集是在老年代内存满时执行的。老年代垃圾收集称为 主GC(Major GC),通常需要更长的时间。

大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在 Eden 区和两个Survivor 区之间发生大量的内存拷贝

4.3 方法区

元空间放在后边的方法区再说~

4.0 字符串常量池

字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

// 在堆中创建字符串对象”ab“
// 将字符串对象”ab“的引用保存在字符串常量池中
String aa = "ab";
// 直接返回字符串常量池中字符串对象”ab“的引用
String bb = "ab";
System.out.println(aa==bb);// true

HotSpot 虚拟机中字符串常量池的实现是 src/hotspot/share/classfile/stringTable.cpp ,StringTable 可以简单理解为一个固定大小的HashTable ,容量为 StringTableSize(可以通过 -XX:StringTableSize 参数来设置),保存的是字符串(key)和 字符串对象的引用(value)的映射关系,字符串对象的引用指向堆中的字符串对象。

JDK1.7 之前,字符串常量池存放在永久代。JDK1.7 字符串常量池和静态变量从永久代移动了 Java 堆中。

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

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

5 方法区

不管是 JDK8 之前的永久代,还是 JDK8 及以后的元空间,都可以看作是 Java 虚拟机规范中方法区的实现。

HotSpot 虚拟机方法区的两种实现

虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫 Non-Heap(非堆),目的应该是与 Java 堆区分开。

图片

方法区用于存储已被虚拟机加载的类型信息常量静态变量即时编译器编译后的代码缓存等。

5.0 运行时常量池

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

Class 文件中除了有类的版本/字段/方法/接口等描述信息外,还有用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference)的 常量池表(Constant Pool Table)

  • 字面量是源代码中的固定值的表示法,即通过字面我们就能知道其值的含义。字面量包括整数、浮点数和字符串字面量。

  • 常见的符号引用包括类符号引用、字段符号引用、方法符号引用、接口方法符号。

image-20230905213324201

常量池表会在类加载后存放到方法区的运行时常量池中。

运行时常量池的功能类似于传统编程语言的符号表,尽管它包含了比典型符号表更广泛的数据。

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

5.1 类型信息

对每个加载的类型(类 class、接口 interface、枚举 enum、注解 annotation),JVM 必须在方法区中存储以下类型信息

  • 这个类型的完整有效名称(全名=包名.类名)
  • 这个类型直接父类的完整有效名(对于 interface或是 java.lang.Object,都没有父类)
  • 这个类型的修饰符(public,abstract,final 的某个子集)
  • 这个类型直接接口的一个有序列表

5.2 域(Field)信息

  • JVM 必须在方法区中保存类型的所有域的相关信息以及域的声明顺序
  • 域的相关信息包括:域名称、域类型、域修饰符(public、private、protected、static、final、volatile、transient 的某个子集)

5.3 方法(Method)信息

JVM 必须保存所有方法的

  • 方法名称

  • 方法的返回类型

  • 方法参数的数量和类型

  • 方法的修饰符(public,private,protected,static,final,synchronized,native,abstract 的一个子集)

  • 方法的字符码(bytecodes)、操作数栈、局部变量表及大小(abstract 和 native 方法除外)

  • 异常表(abstract 和 native 方法除外)

    • 每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引

标签:Java,对象,虚拟机,详解,线程,内存,JVM,方法,运行
From: https://blog.51cto.com/u_15872442/7380253

相关文章

  • MyBatis-Plus详解
    MyBatis-Plus是一个功能强大、易于使用的MyBatis增强工具,在MyBatis的基础上只做增强不做改变,它提供了许多实用的功能和扩展,可以极大地简化和提高开发效率。特性:l 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑l 损耗小:启动即会自动注入基本CURD,性能基本无损......
  • 【愚公系列】2023年09月 WPF控件专题 Calendar控件详解
    (文章目录)前言WPF控件是WindowsPresentationFoundation(WPF)中的基本用户界面元素。它们是可视化对象,可以用来创建各种用户界面。WPF控件可以分为两类:原生控件和自定义控件。原生控件是由Microsoft提供的内置控件,如Button、TextBox、Label、ComboBox等。这些控件都是WPF中常见......
  • MySQL分页查询详解:优化大数据集的LIMIT和OFFSET
    最近在工作中,我们遇到了一个需求,甲方要求直接从数据库导出一个业务模块中所有使用中的工单信息。为了实现这一目标,我编写了一条SQL查询语句,并请求DBA协助导出数据。尽管工单数量并不多,只有3000多条,但每个工单都包含了大量的信息。DBA进行了多次导出操作,不幸的是,每次尝试导出都导致......
  • nginx配置详解
    worker_processes设置worker的数量,Nginx的进程模型采用的是master、worker模式,一个master负责协调,多个worker负责与客户端交互。此处设置为auto即可events设置使用的模型和每个worker的连接数。Linux操作系统中模型建议使用epoll。worker的连接数通常设置为10240......
  • Spring源码分析(十二)ApplicationContext详解(中)
    上篇文章已经对ApplicationContext的一部分内容做了介绍,ApplicationContext主要具有以下几个核心功能:国际化借助Environment接口,完成了对Spring运行环境的抽象,可以返回环境中的属性,并能出现占位符借助于Resource系列接口,完成对底层资源的访问和加载接触了ApplicationEventPublishe......
  • $('.panel-collapse').on('show.bs.collapse', function () {})详解
    $('.panel-collapse').on('show.bs.collapse',function(){});这段代码是在使用jQuery来绑定事件。$('.panel-collapse')部分是一个选择器,它选择了当前页面上所有有panel-collapse这个类的元素。如果你在HTML中有这样的元素:<divclass="panel-collapse"></div>,那么这......
  • 匿名函数中带有for的写详解
    max,min,filte,map,sorted五个内置函数匀可和lamdba函数结合使用用sorted来个例子吧例1; 结果为: 由上得出sorted排序的权重x【0】来决定,而x(帽号前的X)是匿名函数传入的变量参数帽号后的x[0]是返回给匿名函数的值 例2: 结果为: 可见如果中刮号将整个lambda函数刮住得到......
  • 【愚公系列】2023年09月 WPF控件专题 DatePicker控件详解
    (文章目录)前言WPF控件是WindowsPresentationFoundation(WPF)中的基本用户界面元素。它们是可视化对象,可以用来创建各种用户界面。WPF控件可以分为两类:原生控件和自定义控件。原生控件是由Microsoft提供的内置控件,如Button、TextBox、Label、ComboBox等。这些控件都是WPF中常见......
  • 14.MySQL数据库设计详解
    MySQL数据库设计需要根据具体的业务需求和数据模型来制定,以下是一个示例数据库设计,包括创建表、定义索引、外键关系和示例数据插入的MySQL代码。这个示例涵盖了一个简单的电子商务系统,包括用户、产品和订单数据。--创建用户表CREATETABLEusers(user_idINTAUTO_INCREM......
  • MySQL的Json类型个人用法详解
    前言虽然MySQL很早就添加了Json类型,但是在业务开发过程中还是很少设计带这种类型的表。少不代表没有,当真正要对Json类型进行特定查询,修改,插入和优化等操作时,却感觉一下子想不起那些函数怎么使用。比如把json里的某个键和值作为SQL条件,修改某个键下的子键的值,其中可能会遇到数组形式......