【JVM学习】2.Java虚拟机运行时数据区

来源: 公众号: 猿人谷

这里我们先说句题外话,相信大家在面试中经常被问到介绍Java内存模型,我在面试别人时也会经常问这个问题。但是,往往都会令我比较尴尬,我还话音未落,面试者就会“背诵”一段(Java虚拟机是由堆、方法区、虚拟机栈,吧啦吧啦。。。),估计心里还一脸自豪的想幸好哥提前在网上搜过,早有准备。每每这个时候,我都不忍心打断,因为“背诵”的真的太顺畅了!

这也怪不得面试者,首先Java虚拟机方面的知识,对中高级程序猿来说,工作中正面接触Java虚拟机的东西不多。其次,这个其次咱得好好唠唠,网上搜个Java内存模型,度娘推的第一页大都是介绍Java运行时数据区的,起到了一定的误导作用,大写的尴尬。

本篇将给各位小伙伴先详细介绍Java运行时数据区的组成,Java内存模型也是虚拟机里面的重点,后面会单独抽出一篇来进行介绍。

1.运行时数据区介绍

程序运行所需的内存空间,有些是不能在编译期就能确定,得要在运行期根据实际运行状况动态地在系统中创建。Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。

如图所示,堆和方法区是所有线程共享的公共区域,堆和方法区所占的内存空间是由JVM负责管理的,在该区域内的内存分配是由HotSpot的内存管理模块维护的,而内存的释放工作则由垃圾收集器自动完成。虚拟机栈、本地方法栈、程序计数器是线程的私有区域,每个线程都关联着唯一的栈和程序计数器,并仅能使用属于自己的那份栈空间和程序计算器来执行程序。

2.堆(Heap)

对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。堆是可供各个线程共享的运行时内存区域,在虚拟机启动的时候就被创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述就是:所有的对象实例以及数组对象都要在堆上分配。但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。

Java堆的容量可以是固定的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”(Garbage Collected Heap)。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,Java虚拟机将堆划分为新生代和老年代。其中,新生代又被分为Eden区,以及两个大小相同的Survivor区(From Survivor,To Survivor)。默认情况下,Java虚拟机采取的是一种动态分配的策略(JVM参数-XX:+UsePSAdaptiveSurvivorSizePolicy),根据生成对象的速率,以及Survivor区的使用情况,动态调整Eden区和Survivor区的比例。也可以通过参数(SurvivorRatio)来调整这个比例,SurvivorRatio这个参数就是新生代中Eden区与Survivor区的容量比值,默认是8,代表Eden:Survivor=8:1。

是否可能有两个对象共用一段内存的事故?

当调用new指令时,会在Eden区划出一块作为存储对象的内存。由于堆空间是线程共享的,因此直接在这里边划空间是需要进行同步的。否则,将有可能出现两个对象共用一段内存的事故。解决方法就是,Java堆中可能划出多个线程私有的分配缓冲区TLAB(Thread Local Allocation Buffer,对应的虚拟机参数-XX:+UseTLAB,默认开启)。

具体来说,每个线程可以向Java虚拟机申请一段连续内存,比如2048字节,作为线程私有的TLAB。这个操作需要加锁,线程需要维护两个指针(实际上可能更多,但重要也就两个),一个指向TLAB中空余内存的起始位置,一个则指向TLAB末尾。接下来的new指令,便可以直接通过指针加法(bump the pointer),也有人叫做指针碰撞来实现,即把指向空余内存位置的指针加上所请求的字节数。如果加法后空余内存指针的值仍小于或等于指向末尾的指针,则代表分配成功。否则,TLAB已经没有足够的空间来满足本次新建操作。这个时候,便需要当前线程重新申请新的TLAB。

3.方法区(Method Area)

方法区与堆一样是线程共享的,在虚拟机启动的时候创建,方法区可视为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

方法区类似于传统语言编译后的代码存储区域,它存储每个类的结构信息,如:

  • 常量池
  • 方法数据
  • 方法和构造函数的字节码
  • 类、实例、接口初始化时用到的特殊方法

备注:《深入理解Java虚拟机》里将方法区归纳为用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。这区域的内存回收目标主要是针对常量池的回收和对类型的卸载。

4.程序计数器(Program Counter Register)

Java虚拟机可以支持多条线程同时执行,每一条Java虚拟机线程都有自己的程序计数器。在任意时刻,一条Java虚拟机线程只会执行一个方法的代码,这个正在被线程执行的方法称为该线程的当前方法(current methon)。如果这个方法不是native的,那程序计数器保存的就是Java虚拟机正在执行的字节码指令的地址。如果该方法是native方法,那程序计数器的值为空(undefined)。程序计数器的容量至少应当保存一个returnAddress类型的数据或者一个与平台相关的本地指针的值。

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

5.虚拟机栈(VM Stack)

每一条Java虚拟机线程都有自己私有的Java虚拟机栈,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

Java虚拟机栈可能发生如下异常情况:

  • 如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError异常。
  • 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存区创建对应的虚拟机栈,那Java虚拟机将会抛出一个OutOfMemoryError异常

6.本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务。

Java虚拟机规范允许本地方法栈实现成固定大小或者根据计算来动态扩展和收缩。如果采用固定大小的本地方法栈,那么每一个线程的本地方法栈容量可以在创建栈的时候独立选定。

与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

7.扩展知识点

7.1 栈上分配和逃逸分析

在栈中分配的基本思路是这样的:分析局部变量的作用域仅限于方法内部,则JVM直接在栈帧内分配对象空间,避免在堆中分配。这个分析过程称为逃逸分析(也有叫逸出分析),而栈帧内分配对象的方式称为栈上分配

这样做的目的是减少新生代的收集次数,间接提高JVM性能。虚拟机是允许堆逃逸分析开关进行配置的,从Sun Java 6u23以后,HotSpot默认开启逃逸分析。

7.2 栈帧

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。

在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中。因此一个栈帧需要分配多少内存,不会收到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。

一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态。对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),与这个栈帧相关联的方法称为当前方法(Current Method)。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。栈帧的概念结构如下:

8.运行时数据区脑图

高清、无码、完整脑图可以私信或留言告知哦!!!

原文地址:https://www.cnblogs.com/alterem/p/11567111.html

时间: 2024-12-18 18:43:10

【JVM学习】2.Java虚拟机运行时数据区的相关文章

Java虚拟机 运行时数据区

Java在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途.创建和销毁的时间,有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,有些则是与线程一一对应,随线程的开始和结束而创建和销毁. Java虚拟机所管理的内存将会包括以下几个运行时数据区域 程序计数器(Program Counter Register) 它是一块较小的内存空间,它的作用可以看做是当先线程所执行的字节码的信号指示器. 每一条JVM线程都有自己的PC寄存器,各条线程之间互不影响,独立存

Java虚拟机运行时数据区结构

本文部分参考自<Java虚拟机规范(Java SE 7版)>的中译本和周志明的<深入理解Java虚拟机>,另加个人理解.原书对Java虚拟机运行时数据区描述只有6页,同时参考其他网络网资料,个人能力所限,不排除存在认知错误. JVM将程序运行期间使用的内存划分为若干个运行时数据区,其中一些会随着虚拟机启动而创建,随着虚拟机退出而销毁.另外一些与线程一一对应,随着线程开始而创建,随着线程结束而销毁.数据区划分如下图所示意: Java堆(Java Heap) 在JVM中,Java堆是可

Java虚拟机 运行时数据区解析及用途

Java在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途.创建和销毁的时间,有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,有些则是与线程一一对应,随线程的开始和结束而创建和销毁. Java虚拟机所管理的内存将会包括以下几个运行时数据区域 程序计数器(Program Counter Register) 它是一块较小的内存空间,它的作用可以看做是当先线程所执行的字节码的信号指示器. 每一条JVM线程都有自己的PC寄存器,各条线程之间互不影响,独立存

Java 虚拟机运行时数据区介绍

引言 Java 虚拟机(Virtual Machine)在执行 Java 程序时, 会将它所管理的内存划分为不同的数据区,这些分块有不同的功能,理解它们各自的特点,对于深入理解java程序运行机制和调优Java代码,具有重要意义.其实个人觉得,理解 Java 虚拟机的原理,也没想象中的复杂(也许我的想法有点天真啊^_^),它的运行机制,就像一个小型操作系统,这也许就是虚拟机的来源吧. Java VM运行时数据区结构图 运行时数据区域解释 本地方法栈 该区域所发挥的作用与虚拟机栈类似,只不过它是为

Java虚拟机运行时数据区

PS:时间一晃好久了,距离上一篇文章过去许久了,出自两个方面的原因,其一,公司的项目接近尾声,用户提出的需求功能需要马上的解决:其二,出自整天加速完善和修改需求功能,下班之后几乎不想再继续下去了,懒散情绪有了. 扯淡话题结束了,开始继续学习笔记文章的整理,每天一小步,久而久之就是巨大的一步.今天要扯的话题是Java虚拟机的运行时数据区. Java虚拟机(JVM)是 由JDK提供的一个软件程序,而其任务就是执行Java程序,下面给出虚拟机执行Java的过程图. 从图中可以看出由Java源文件编译出

Java虚拟机运行时数据区域

Java虚拟机在执行Java程序的过程中会将其管理的内存划分为若干个不同的数据区域,这些区域有各自的用途,及创建和销毁的时间,有些区域随虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束来建立和销毁.Java虚拟机所管理的内存包括以下几个运行时数据区域,如图(图片引自网络): 1.1 程序计数器(Program Counter Register) 程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的信号指示器.字节码解释器就是通过改变该计数器的值来选取下一条需要执

Java虚拟机运行时数据区域划分

Java虚拟机数据运行时区域 方法区(Method Area) 存储加载的类信息,常量,静态变量,编译器编译后的代码等数据.虽然JVM规范把方法区描述为堆的一个逻辑部分,但它却有一个别名叫做Non-Heap.Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项是常量池,用于存放编译器生成的各种字面量和符号引用,包括字符串常量.JVM加载Class后把常量池中的数据放入到运行时常量池.方法区的运行时数据(包括运行时常量池)是线程共享的. 堆(Java Heap) 堆是被线程共享的

虚拟机运行时数据区划分

假设①新建Company类,然后继续②创建Company实例,即Company company = new Company(); 下面解释①②两部动作所涉及到的虚拟机运行时内存区域划分: 1,方法区:存放类信息,常量,静态变量(①总新建类(类信息)存放在方法去中): 2,虚拟机栈:创建栈帧,存放局部变量信息表,方法出口等信息(②中Company company即创建了一个reference,则昂在了局部变量信息表中): 3,堆:存放对象实例,(②中new Company()所需内存空间则在堆提供

《Java虚拟机规范》阅读笔记-运行时数据区

Java虚拟机运行时数据区包括PC寄存器.Java虚拟机栈.Java堆.方法区.本地方法栈.运行时常量池六个部分. 1. PC寄存器 PC寄存器(又叫程序计数器,Program Counter Register),每一条Java虚拟机线程都有自己的PC寄存器.PC寄存器报错当前正在执行方法的字节码指令地址:如果当前方法是native的,则PC寄存器的值为undefined. 2. Java虚拟机栈 Java虚拟机栈(Java Virtual Machine Stack),每一条Java虚拟机线程