写在前面

本文描述的有关于 JVM 的运行时数据区是基于 HotSpot 虚拟机。

概述

JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机的进程启动而存在,有的区域则依赖于用户线程的启动和结束而建立和销毁。

HotSpot 运行时数据区

运行时数据区在 HotSpot 1.8 之前的版本和 1.8 版本有所不同,主要是 方法区移到元空间 了。

图 1-1:JDK1.8 之前 JVM 运行时数据区

图 1-2:JDK1.8 JVM 运行时数据区

线程私有区域

程序计数器

程序计数器是一块很小的区域,它存储的是当前线程正在执行的字节码的地址(在这里,其实有两个“当前”,一个是:当前正在被 CPU 执行的线程,另一个是:当前这个被执行的线程中正在被执行的字节码指令)。字节码解释器工作时就是改变程序计数器的值来选取下一条需要执行的字节码。对于单核心而言,多线程是通过线程轮流切换的方式实现的,在任一时刻只有一个线程能够得到 CPU 的执行权从而执行线程中的字节码指令,因此,为了使线程切换后能够恢复到正在执行的字节码的位置,每个线程都需要拥有自己的程序计数器。

注意:程序计数器是唯一的一块在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 的区域。由于它是线程私有的,所以它的生命周期随着线程的创建而创建,随着线程的结束而死亡 。

虚拟机栈

虚拟机栈也是线程私有的,所以它的生命周期与程序计数器相同。虚拟机栈描述的是 Java 方法执行的内存模型。

每个方法在执行的时候都会创建一个栈帧(一个方法对应一个栈帧,栈帧即栈的基本单位)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被线程执行从开始到结束,就对应着一个栈帧在虚拟机栈中入栈(压栈)和出栈(弹栈)的过程。局部变量表中存放了编译可知的各种基本数据类型(byte,short,int,long,float,double,char,boolean)、对象引用(reference 类型,它存储的是:对象的地址或者是指向代表对象的句柄)。

Java 虚拟机规范中规定了虚拟机栈可能出现的两种异常状况:StackOverflowError 和 OutOfMemoryError。

StackOverflowError: 若当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候会抛出 StackOverflowError。

OutOfMemoryError: 若虚拟机栈动态扩展过程中,如果线程请求申请栈空间无法申请到足够的内存,就会抛出 OutOfMemoryError。

本地方法栈

本地方法栈与虚拟机栈类似,虚拟机栈是执行 Java 方法开辟的内存空间,而本地方法栈是执行 Native 方法开辟的内存空间。

与虚拟机栈一样,本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常,抛出条件也是类似的。

线程间共享的内存区域

堆是所有线程共享的一块区域,主要用来存放对象和数组。

在 Java 虚拟机规范中有描述:所有的对象实例和数组都要在堆上分配,但是 随着 JIT(JUST-IN-TIME)编译器的发展与逃逸分析技术的逐渐成熟,并不是所有对象都只在堆上分配了,比如:随着逃逸分析技术的逐渐成熟,在即时能被回收的对象也有可能会在虚拟机栈上分配。

由于现在都采用分代回收算法,所以从内存回收的角度来看,堆还可以细分为:新生代、老年代。新生代又可以分为:Eden 空间、From Survivor 空间、To Survivor 空间。

注意:1.8 中已经彻底将方法区的实现由之前的永久代改为元空间。

堆里面可能抛出的异常就是 OutOfMemoryError, 出现这种错误的表现形式主要有两种:

OutOfMemoryError: GC Overhead Limit Exceeded:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。

java.lang.OutOfMemoryError: Java heap space:假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发java.lang.OutOfMemoryError: Java heap space 错误。

方法区

方法区和堆一样也是所有线程共享的一块区域,主要用来存储已经被虚拟机加载的类信息、常量(final 修饰的)、静态变量、即时编译器(JIT)编译后产生的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。

永久代就是方法区域?

早些时候,很多开发者更愿意称方法区为“永久代”。其实“永久代”这个称呼的由来是因为 HotSpot 团队并不打算为方法区重新设计垃圾回收算法,为了在方法区中能够沿用堆中的分代回收算法,所以按照堆中的命名方式,将方法去称为“永久代”。对于 JRocket、J9 而言是不存在“永久代”的概念的,所以当 HotSpot 1.8 和 JRocket 合并时,就彻底放弃了“永久代”的概念(其实从 1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。

方法区的垃圾回收很困难!!!

由于 Java 虚拟机规范对方法区的限制非常松,甚至可以不实现垃圾回收,一般而言,这个区域的内存回收很不令人满意,尤其是类型的卸载,条件非常苛刻,但是由于现代框架大量的依赖于 JIT 技术,导致方法区的占用比逐渐提高,所以对于方法区的回收至关重要。根据 Java 虚拟机规范规定,当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。

运行时常量池

JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。

这块区域在 1.7 之前原来是方法区的一部分,Class 文件中有一项信息是常量池(或者说是一张常量表,Class 文件以表存储数据)。

图 1-3:Class 文件常量池

运行时常量池存储的东西较为复杂,主要分为字面量和符号引用

字面量

存放的字面量主要包括 常量(final 修饰的),比如:final int x = 1、静态变量(static 修饰的),还有一些其他的字面量。

符号引用

符号引用主要包括:类的完全限定名、字段名称和描述符、方法名称和描述符,包括很多符号,比如:() 也可以看做符号引用。

字面量和符号引用将在类加载(ClassLoader 加载 Class 字节码文件)后进入方法区的运行时常量池中存放。不过,除了保存 Class 文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。运行时常量池相对于 Class 文件常量池一个重要的特征就是具备动态性,Java 语言并不要求常量一定产生于编译期的 Class 文件的常量池中,也并不是只有 Class 文件常量池中的常量才能够进入运行时常量池中,在线程执行方法的过程当中可能产生新的常量存放到运行时常量池中,例如:String 类的 intern() 方法。当运行时常量池无法申请到内存的时候就会抛出 OutOfMemoryError 异常。

直接内存

直接内存并不是 JVM 运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。

在 JDK1.4 中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。

本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

总结

Java 虚拟机包含的内容很多,本篇文章也只是对 Java 内存管理模块的 Java 虚拟机运行时数据区做了简要的分析,关于内存管理模块的其他部分后续会继续更新,敬请期待!

参考

干货分享

如果大家想要实时关注我更新的文章以及我分享的干货的话,可以关注我的公众号 我们都是小白鼠。公众号内有一些整理过的 原创精品脑图,不仅包含技术点的知识脉络,更多的底层原理的梳理,目前涵盖 Redis,RabbitMQ,Mysql,Java 虚拟机等 ,这些都是博主自己的学习笔记,整理的过程花费了很多心血,除此之外还有一些整理过的 面试题 以及日常开发常用到的一些 开发工具 等,在公众号内分别回复【技术脑图】、【面试题】、【开发工具】即可获取。

面试常问的 Java 虚拟机运行时数据区的更多相关文章

  1. 《深入理解Java虚拟机》(二)Java虚拟机运行时数据区

    Java虚拟机运行时数据区 详解 2.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第二章 ,为了整理思路,简单记录一下,方便后期查阅. 2.2 运行时数据区域 Java虚拟机 ...

  2. Java 虚拟机运行时数据区

    写在前面 本文描述的有关于 JVM 的运行时数据区是基于 HotSpot 虚拟机. 概述 JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以 ...

  3. Java虚拟机运行时数据区

    运行时数据区程序计数器Java虚拟机栈本地方法栈Java堆(GC堆)方法区运行时常量池 运行时数据区 Java虚拟机在运行Java程序时,会将它所管理的内存划分为若干个内存区域.这些数据区域有各自的用 ...

  4. 【深入理解Java虚拟机】Java虚拟机运行时数据区

    Java虚拟机运行时数据区 线程私有 程序计数器 1.当前线程所执行的字节码的行号指示器. 2.唯一不会发生OutOfMemoryError的区域 3.如果执行的是java方法,计数器值为虚拟机字节码 ...

  5. 【JVM从小白学成大佬】2.Java虚拟机运行时数据区

    目录 1.运行时数据区介绍 2.堆(Heap) 是否可能有两个对象共用一段内存的事故? 3.方法区(Method Area) 4.程序计数器(Program Counter Register) 5.虚 ...

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

    来源: 公众号: 猿人谷 这里我们先说句题外话,相信大家在面试中经常被问到介绍Java内存模型,我在面试别人时也会经常问这个问题.但是,往往都会令我比较尴尬,我还话音未落,面试者就会"背诵& ...

  7. 笔记:Java虚拟机运行时数据区

    Java虚拟机在执行Java程序的过程中会把它管的内存划分为以下若干个不同的区域: 1.程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器:由于Java虚拟机的 ...

  8. Java 虚拟机运行时数据区详解

    本文摘自深入理解 Java 虚拟机第三版 概述 Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟 ...

  9. 深入理解Java虚拟机&运行时数据区

      其中,程序计数器.虚拟机栈.本地方法栈3个区域随线程而生,随线程而灭.

随机推荐

  1. frp 内网穿透访问内网Web服务

    ps:最近想要通过域名(公网)访问或者测试在本地搭建的 web 服务(不想在公网IP服务器上再部署个服务,也不想通过teamview等工具远程卡到爆!), 由于本地机器没有公网 IP,无法将域名解析到 ...

  2. java线程间的协作

    本次内容主要讲等待/通知机制以及用等待/通知机制手写一个数据库连接池. 1.为什么线程之间需要协作 线程之间相互配合,完成某项工作,比如:一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行 ...

  3. java后台生成并下载二维码

    这个功能在项目开发中是很基础的,平时用到的也很多,这里简单记录一下,以便以后使用的时候参考 前提业务要求:前台页面展示数据,有下载按钮,点击下载,下载对应数据的二维码. 首先,在pom.xml文件中添 ...

  4. Python 【基础常识概念】

    深浅拷贝 浅copy与deepcopy 浅copy: 不管多么复杂的数据结构,浅拷贝都只会copy一层 deepcopy : 深拷贝会完全复制原变量相关的所有数据,在内存中生成一套完全一样的内容,我们 ...

  5. Python进阶练习与爬取豆瓣T250的影片相关信息

    (一)Python进阶练习 正所谓要将知识进行实践,才会真正的掌握 于是就练习了几道题:求素数,求奇数,求九九乘法表,字符串练习 import re #求素数 i=1; flag=0 while(i& ...

  6. JVM05——JVM类加载机制知多少

    我们已经讲过 JVM 相关的很多常见知识点,感兴趣的朋友可以在我的往期文章中查看.接下来将继续为各位带来 JVM 类加载机制.关注我的公众号「Java面典」了解更多 Java 相关知识点. 类生命周期 ...

  7. 一文掌握Redis的三种集群方案

    在开发测试环境中,我们一般搭建Redis的单实例来应对开发测试需求,但是在生产环境,如果对可用性.可靠性要求较高,则需要引入Redis的集群方案.虽然现在各大云平台有提供缓存服务可以直接使用,但了解一 ...

  8. 部署nginx后无法访问数据库,查看www-error.log日志报错Class 'mysqli' not found in /usr/local/nginx/html/mysql.php on line 2

    检查你的php-mysql包是否安装 [root@localhost nginx]# rpm -qa php-mysql 没有任何输出则没有安装,接下来用yum安装php-mysql yum -y i ...

  9. vue基础响应式数据

    1.vue 采用 v……vm……m,模式,v---->el,vm---->new Vue(实例),m---->data 数据,让前端从操作大量的dom元素中解放出来. 2.vue响应 ...

  10. element中的树形组件,如何获取父级菜单的id

    一般多选的树形组件,使用getCheckedNodes()方法只能获取到本级的菜单id,只有在子菜单全部选中的情况下才会选中上级.但我们想要不全选中子级的情况下也要获取它的上级,甚至上上级等,怎么办呢 ...