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. 李宏毅2022机器学习HW3 Image Classification

    Homework3 数据集下载 在本地环境下进行实验总是令人安心,但是又苦于网上找不到数据集,虽然kaggle上有数据集但是下载存在问题 于是有了一个天才的想法,间接从kaggle上下载(利用outp ...

  2. 初级算法 - C++反转链表

    顾名思义, 就是将链表的所有结点反转. 解释见:[剑指offer]反转链表,C++实现(链表) 代码: #include <iostream> struct NodeList { int ...

  3. java基础之StringBuilder---03

    StringBuilder概述 StringBuilder是一个可变的字符串类,我们可以把它看成是一个容器,这里的可变指的是StringBuilder对象中的内容是可变的. 如果对字符串进行拼接操作, ...

  4. sql判断字符串中含中文方法

    基于UTF-8字符集 它是一种多字节字符集,编码为变长编码.那么它的编码范围根据:http://www.iteye.com/topic/977671 作者提供的资料学习,整理出它编码范围如下: u2e ...

  5. 前后端分离项目(七):实现"添加"功能(前端视图)

    好家伙,本篇用于测试"添加"接口,为后续"用户注册"功能做铺垫   (完整代码在最后) 我们要实现"添加"功能 老样子我们先来理清一下思路, ...

  6. jvm调优监控工具jps、jstack、jmap、jhat、jstat使用详解

    目录 前言 jps(Java Virtual Machine Process Status Tool) jstack jmap(Memory Map)和jhat(Java Heap Analysis ...

  7. Emqx高可用架构

    目录 优化前架构 主要问题 haproxy问题 优化后架构 优化功能点 emq版本升级 linux系统调优 haproxy调优 测试工具 依赖安装 配置erl环境变量 安装压测软件 测试指令与结果展示 ...

  8. 【Azure 服务总线】使用Azure Service Bus 时,出现证书错误: 所使用的证书具有无法验证的信任链

    问题描述 在Azure中连接 Service Bus 服务发送消息时发生证书错误,抛出证书异常消息: 或 The X.509 certificate CN=servicebus.chinaclouda ...

  9. 【Azure Developer】在微软云中国区,如何使用Microsoft GraphAPI连接到B2C Tenant

    问题描述 如题所述,当在中国区使用Microsoft GraphAPI连接B2C Tenant时候,如何来设置中国区的Endpoint呢?在GitHub的示例中,并没有示例介绍如何连接中国区.如 问题 ...

  10. 【Azure 应用服务】App Service 配置 Application Settings 访问Storage Account得到 could not be resolved: '*.file.core.windows.net'的报错。没有解析成对应中国区 Storage Account地址 *.file.core.chinacloudapi.cn

    问题描述 App Service 配置 Application Settings 访问Storage Account.如下: { "name": "WEBSITE_CON ...