JVM—运行时数据区

运行时数据区概述

JVM运行时数据区如下图:

整个JVM构成里面,主要由三部分组成:类加载系统、运行时数据区、执行引擎。

按照线程使用情况和职责分成两大类:

  • 线程独享(程序执行区域)

    • 虚拟机栈、本地方法栈、程序计数器
    • 不需要垃圾回收
  • 线程共享(数据存储区域)
    • 堆和方法区
    • 存储类的静态数据和对象数据
    • 需要垃圾回收

Java堆在JVM启动时创建内存区域去实现对象、数组与运行时常量的内存分配,它是虚拟机管理最大的区域,也是垃圾回收的主要内存区域。

内存划分:

核心逻辑就是三大假说,基于程序运行情况进行不断地优化设计。

堆内存为什么会存在新生代和老年代?

分代收集理论:当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”(Generational Collection)的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:

  • 弱分代假说 (Weak Generational Hypothesis) :绝大多数对象都是朝生夕灭的。
  • 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。

这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。

  • 如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,那么把它们集中放在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间;
  • 如果剩下的都是难以消亡的对象,那把它们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域。

这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用。

内存模型变迁

JDK 1.7内存模型如下图:

  • Young 年轻区︰主要保存年轻对象,分为三部分,Eden区、两个Survivor区。
  • Tenured年老区︰主要保存年长对象,当对象在Young复制转移一定的次数后,对象就会被转移到Tenured区。
  • Perm永久区︰主要保存class、method、filed对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到OOM:PermGen space的错误。
  • Virtual区:最大内存和初始内存的差值,就是Virtual区。

JDK 1.8内存模型如下图:

  • 由2部分组成,新生代(Eden+ 2*Survivor ) +年老代(OldGen )
  • JDK1.8中变化最大的是Perm永久区用Metaspace进行了替换
  • 注意:区别于JDK1.7,Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中。

JDK 1.9内存模型如下图:

  • 取消新生代、老年代的物理划分
  • 将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的新生代、老年代区域

虚拟机栈

栈帧是什么

栈帧(Stack Frame)是用于支持虚拟机进行方法执行的数据结构。

栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。

栈内存为线程私有的空间,每个线程都会创建私有的栈内存,生命周期与线程相同,每个Java方法在执行的时候都会创建一个栈帧(Stack Frame)。栈内存大小决定了方法调用的深度,核内存过小则会导致方法调用的深度较小,如递归调用的次数较少。

虚拟机栈的构成如下:

当前栈帧

一个线程中方法的调用链可能会很长,所以会有很多栈帧。只有位于JVM虚拟机栈栈顶的元素才是有效的,即称为当前栈帧,与这个栈帧相关连的方法称为当前方法,定义这个方法的类叫做当前类

执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。如果当前方法调用了其他方法,或者当前方法执行结束,那这个方法的栈帧就不再是当前栈帧了。

什么时候创建栈帧

调用新的方法时,新的栈帧也会随之创建。并且随着程序控制权转移到新方法,新的栈帧成为了当前栈帧。方法返回之际,原栈帧会返回方法的执行结果给之前的栈帧(返回给方法调用者),随后虚拟机将会丢弃此栈帧。

栈异常的两种情况

  • 如果线程请求的栈深度大于虚拟机所允许的深度(Xss默认1m),会抛出StackOverflowError异常
  • 如果在创建新的线程时,没有足够的内存去创建对应的虚拟机栈,会抛出OutOfMemoryError异常【不一定会复现,不同机器不同现象】

本地方法栈

本地方法栈和虚拟机栈相似,区别就是虚拟机栈为虚拟机执行Java服务(字节码服务),而本地方法栈为虚拟机使用到的Native方法(比如C++方法)服务。

简单地讲,一个Native Method就是一个Java调用非Java代码的接口。

方法区

方法区(Method Area)是可供各个线程共享的运行时内存区域,方法区本质上是Java语言编译后代码存储区域,它存储每一个类的结构信息,例如:运行时常量池、成员变量、方法数据、构造方法和普通方法的字节码指令等内容。很多语言都有类似区域。

方法区的具体实现有两种:永久代(PermGen)、元空间(Metaspace)。

方法区存储什么数据

方法区构成如下:

主要有如下三种类型:

  • Class:

    • 1.类型信息,比如Class
    • 2.方法信息:比如Method(方法名称、方法参数列表、方法返回值信息)
    • 3.字段信息:比如Field(字段类型、字段名称需要特殊设置才能保存的住)
    • 4.类变量(静态变量):JDK1.7之后,转移到堆中存储
    • 5.方法表(方法调用的时候):调用某个类的方法时,根据某个类的方法表去查找合适的方法进行调用的。
  • 运行时常量池(字符串常量池):从class中的常量池加载而来,JDK1.7之后,转移到堆中存储
    • 字面量类型
    • 引用类型:内存地址
  • JIT编译器编译之后的代码缓存

如果需要方法方法区中类的其他信息,都必须先获得Class对象,才能去方法该Class对象关联的方法信息或者字段信息。

永久代和元空间的区别是什么

  • JDK1.8之前使用的方法区实现是永久代,JDK1.8及以后使用的实现是元空间
  • 存储位置不同:
    • 永久代所使用的内存区域是JVM进程所使用的区域,它的大小受整个JVM的大小所限制;
    • 元空间所使用的内存区域是物理内存区域,元空间的使用大小只会受物理内存大小的限制。
  • 存储内容不同:
    • 永久代存储的信息基本上就是上面方法区存储内容中的数据;
    • 元空间只存储类的元信息,而静态变量和运行时常量池都挪到了堆中

为什么要使用元空间来替代永久代

  • 字符串存在永久代中,容易出现性能问题和永久代内存溢出;
  • 类及方法的信息等比较难确定大小,所以对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出;
  • 永久代会为GC带来不必要的复杂度,并且回收效率偏低。

方法区实现变迁历史如下图:

字符串常量池

三种常量池的比较

  • class常量池:一个class文件只有一个class常量池

    • 字面量:数值型(int、float、long、double)、双引号引起来的字符串值等
    • 符号引用:Class、Method、Field等
  • 运行时常量池:一个class对象有一个运行时常量池
    • 字面量:数值型(int、float、long、double)、双引号引起来的字符串值等
    • 符号引用:Class、Method、Field等
  • 字符串常量池:全局只有一个字符串常量池·双引号引起来的字符串值
    • 双引号引起来的字符串值

字符串的存储位置

  • 单独使用""引号创建的字符串都是常量,编译期就已经确定存储到Sstring Pool中。

  • 使用new String("")创建的对象会存储到heap中,是运行期新创建的。

  • 使用只包含常量的字符串连接符如"aa"+"bb"创建的也是常量,编译期就能确定已经存储到StringPool中。

  • 使用包含变量的字符串连接如"aa"+s创建的对象是运行期才创建的,存储到heap中。

  • 运行期调用String的intern()方法可以向String Pool中动态添加对象。

程序计数器

程序计数器(Program Counter Register),也叫PC寄存器,是一块较小的内存空间,它可以看作是当前线程所执行的字节码指令的行号指示器。字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支,循环,跳转,异常处理,线程回复等都需要依赖这个计数器来完成。

为什么需要程序计数器?

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(针对多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换(系统上下文切换)后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为"线程私有"的内存。

存储的什么数据?

如果一个线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法,这个计数器的值则为空。

异常:此内存区域是唯一一个在Java的虚拟机规范中没有规定任何OutOfMemoryError异常情况的区域。

直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。在JDK1.4中新加入了NIO类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

直接内存(堆外内存)与堆内存比较:

  • 直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显;
  • 直接内存IO读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显。

JVM—运行时数据区的更多相关文章

  1. Jvm运行时数据区

    一:运行时数据区 Java虚拟机在执行Java程序的过程中会把它管理的内存分为若干个不同的数据区域.这些区域有着各自的用途,一级创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户 ...

  2. Java内存管理:Java内存区域 JVM运行时数据区

    转自:https://blog.csdn.net/tjiyu/article/details/53915869 下面我们详细了解Java内存区域:先说明JVM规范定义的JVM运行时分配的数据区有哪些, ...

  3. JVM 运行时数据区 (三)

    JVM运行时数据区 运行时数据区由 程序计数器.java虚拟机栈.本地方法栈.堆.方法区 组成: 1.程序计数器 每一个Java线程都有一个程序计数器,用于保存程序执行到当前方法的哪一个指令,它是线程 ...

  4. JVM总结(一):概述--JVM运行时数据区

    大三下,趁着寒假重温一遍JVM,准备在一个系列来总价一下学习JVM的整个过程.争取在接下来的一个星期内更新完这一个系列,然后回家过年. JVM运行时数据区 线程私有的数据区 程序计数器 虚拟机栈 本地 ...

  5. JVM运行时数据区与JVM堆内存模型小结

    前提 JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型. 那JVM运行时数据区又是什么? 它包括:程序计数器.虚拟机栈.本地方法栈.方法区.堆. 来看看它们都是干嘛的 ...

  6. JVM运行时数据区和垃圾回收机制

    最近参考各种资料,尤其是<深入理解Java虚拟机 JVM高级特性和最佳实践>,大牛之作.把最近学习的Java虚拟机组成和垃圾回收机制总结一下. 你不会的都是新知识,学无止境,每天进步一点点 ...

  7. Jvm运行时数据区 —— Java虚拟机结构小记

    关于jvm虚拟机的文章网上都讲烂了.尤其是jvm运行时数据区的内容. 抱着眼见为实的想法,自己翻了翻JVM规范,花了点时间稍微梳理了一下. 以下是阅读Java虚拟机规范(Java SE 8版)的第二章 ...

  8. Java中的字符串常量池和JVM运行时数据区的相关概念

    什么是字符串常量池 JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池 工作原理 当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量 ...

  9. JVM运行时数据区及对象在内存中初始化的过程

    JVM运行时数据区 Java虚拟机所管理的内存区域,也称为运行时数据区,分为以下几个运行时数据区,如图所示 程序计数器:当前程序所执行字节码的行号指示器 程序计数器(Program Counter R ...

  10. JVM 运行时数据区(二)

    @ 目录 运行时数据区 共享区 堆区 方法区 隔离区 虚拟机栈 栈帧 本地方法栈 程序计数器 运行时数据区 JVM 运行时数据区主要分为5块 方法区 JDK1.8以后叫做元数据区(Metaspace) ...

随机推荐

  1. 【Azure 存储服务】使用PowerShell脚本创建存储账号(Storage Account)的共享访问签名(SASToken) : New-AzStorageContainerSASToken

    问题描述 使用PowerShell脚本如何来创建存储账号(Storage Account)的共享访问签名呢?查询到可以使用 New-AzStorageContainerSASToken 命令来生成Az ...

  2. [QT] 记录一些使用技巧

    目录 概述 打开窗口 弹出消息框 判断文件存在 获取时间 获取子控件 TableWidget设置不可编辑 QT QString判断纯数字 Qt 保存文件选择器 读写ini 概述 最近花了好几天的时间编 ...

  3. mysql-编写脚本-批量插入数据

    一.代码 -- 报工设置,添加数据 set @org_id = '租户id'; set @created_user = 'yike'; set @updated_user = 'yike'; set ...

  4. FolkMQ 作个简单的消息中间件(最简单的那种), v1.3.1 发布

    功能简介 角色 功能 生产端(或发起端) 发布消息.定时消息(或叫延时).顺序消息.可过期消息.事务消息.发送消息(rpc)支持 Qos0.Qos1 消费端(或接收端) 订阅.取消订阅.消费-ACK( ...

  5. C++ //类模板对象做函数参数 //三种方式 //1.指定传入的类型 --直接显示对象的数据类型 //2.参数模板化 --将对象中的参数变为模板进行传递 //3.整个类模板化 --将这个对象类型 模板化进行传递

    1 //类模板对象做函数参数 2 //三种方式 3 //1.指定传入的类型 --直接显示对象的数据类型 4 //2.参数模板化 --将对象中的参数变为模板进行传递 5 //3.整个类模板化 --将这个 ...

  6. Java 辨析之实例化和初始化

    在面向对象编程中,实例化和初始化是两个相关但不同的概念: 实例化(Instantiation): 实例化是指创建一个类的新的具体对象的过程.当程序运行时,通过 new 关键字调用类的构造函数来创建该类 ...

  7. 16 Educational Codeforces Round 142 (Rated for Div. 2)C. Min Max Sort(递归、思维、dp)

    C. Min Max Sort 很不错的一道题目,不过脑电波和出题人每对上,\(qwq.\) 正难则反. 我们考虑最后一步是怎么操作的. 最后一步一定是对\(1\)和\(n\)进行操作 那么上一步呢? ...

  8. SelectZenEmpty 下拉框 支持 最大长度 超出... vue 组件

    <template> <Select v-model="innerValue" :disabled="disabled" :clearable ...

  9. 痞子衡嵌入式:使用恩智浦GUI Guider快速创建全新LCD屏示例工程的步骤

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是使用恩智浦GUI Guider快速创建全新LCD屏示例工程的步骤. 在痞子衡旧文 <在i.MXRT1170上快速点亮一款全新LCD ...

  10. JavaFx 圆形头像实现

    原文:JavaFx 圆形头像实现 - Stars-One的杂货小窝 本文基于TornadoFx框架进行编写,封装工具代码是kotlin版本 圆形头像框组件封装成了stars-one/common-co ...