精华内容
下载资源
问答
  • 说一下 jvm 运行时数据区
    2020-09-07 22:45:27

    说一下 jvm 运行时数据区?

    运行时数据区包括堆、方法区、栈、本地方法栈、程序计数器。

    1、堆

    堆解决的是对象实例存储的问题,垃圾回收器管理的主要区域。

    2、方法区

    方法区可以认为是堆的一部分,用于存储已被虚拟机加载的信息,常量、静态变量、即时编译器编译后的代码。

    3、栈

    栈解决的是程序运行的问题,栈里面存的是栈帧,栈帧里面存的是局部变量表、操作数栈、动态链接、方法出口等信息。

    • 栈帧

    每个方法从调用到执行的过程就是一个栈帧在虚拟机栈中入栈到出栈的过程。

    • 局部变量表

    用于保存函数的参数和局部变量。

    • 操作数栈

    操作数栈又称操作栈,大多数指令都是从这里弹出数据,执行运算,然后把结果压回操作数栈。

    4、本地方法栈

    与栈功能相同,本地方法栈执行的是本地方法,一个Java调用非Java代码的接口。

    5、程序计数器(PC寄存器)

    程序计数器中存放的是当前线程所执行的字节码的行数。JVM工作时就是通过改变这个计数器的值来选取下一个需要执行的字节码指令。

    更多相关内容
  • jvm运行时数据区详解

    千次阅读 2019-03-23 16:55:30
    运行时数据区 程序计数器 程序计数器指的是当前线程正在执行的字节码指令地址(行号), java中最小的执行单位是线程,因为虚拟机的是多线程的,每个线程是抢夺cpu时间片,程序计数器就是存储这些指令去做什么...

    运行时数据区

    程序计数器

    程序计数器指的是当前线程正在执行的字节码指令地址(行号),

    java中最小的执行单位是线程,因为虚拟机的是多线程的,每个线程是抢夺cpu时间片,程序计数器就是存储这些指令去做什么,比如循环,跳转,异常处理等等需要依赖它

    每个线程都有属于自己的程序计数器,而且互不影响,独立存储。

    Java虚拟机栈

    栈-是数据结构-存储数据

    存储什么数据?存储当前线程的运行方法时所需的数据,指令,返回地址

    我们知道方法运行时都会出现一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等

     本地方法栈

    本地方法栈和虚拟机栈所发挥的作用是相似的。本地方法栈为虚拟机运行native方法服务,与虚拟机栈相同的是栈的深度是固定的,当线程申请的大于虚拟机栈的深度就会抛出StackOverflowError异常,当然虚拟机栈也可以动态的扩展,如果扩展到无法申请到足够的内存就会抛出out ofMemoryError异常

    方法区

    它用于存储已被虚拟机加载的类信息(class文件)、常量(1.7有变化存储在堆里面)、静态变量、即时编译器编译后的代码等数据

    package com.jvm;

     

    import java.util.ArrayList;

    import java.util.List;

     

    /**

     * -XX:MaxPermSize=10M 方法区最大大小10M

    */

    public class Test {

     

        public static void main(String[] args) {

            List<String> list = new ArrayList<String>();

            int i = 0;

            while (true) {

                list.add(String.valueOf(i++).intern());   //不断创建线程

            }

        }

    }

    字符串常量池在JDK6的时候还是存放在方法区(永久代)所以它会抛出OutOfMemoryError:Permanent Space;而JDK7后则将字符串常量池移到了Java堆中,上面的代码不会抛出OOM,若将堆内存改为20M则会抛出OutOfMemoryError:Java heap space;至于JDK8则是纯粹取消了方法区这个概念,取而代之的是元空间(Metaspace,所以在JDK8中虚拟机参数”-XX:MaxPermSize”也就没有了任何意义,取代它的是”-XX:MetaspaceSize“”-XX:MaxMetaspaceSize”等。

    Java堆

    此区域存放的是存放对象实例,java堆是所有线程共享区域的内存区域

    堆大小=新生代+老年代;(新生代占堆空间的1/3、老年代占堆空间2/3

    新生代又被分为了edenfrom survivorto survivor(8:1:1)

    为什么是8:1:1?

    新生代这样划分是为了更好的管理堆内存中的对象,方便GC算法---复制算法来进行垃圾回收。

    JVM每次只会使用eden和其中一块survivor来为对象服务,所以无论什么时候,都会有一块survivor空间,因此新生代实际可用空间只有90%

    新生代GCminor gc----------指发生在新生代的垃圾回收动作,因为JAVA对象大多数都是朝生夕死的特性,所以minor gc非常平凡,使用复制算法快速的回收。

    新生代几乎是所有JAVA对象出生的地方,JAVA对象申请的内存和存放都是在这个地方。

    当对象在eden(其中包括一个survivor,假如是from),当此对象经过一次minor gc后仍然存活,并且能够被另外一块survivor所容纳(这里survivor则是to了),则使用复制算法将这些仍然存活的对象复制到to survior区域中,然后清理掉edenfrom survivor区域,并将这些存活的对象年龄+1,以后对象在survivor中每熬过一次gc则增加1,当年龄达到某个值时(默认15,通过设置参数-xx:maxtenuringThreshold来设置),这些对象就会成为老年代!

    但是也不一定,当一些较大的对象(需要分配连续的内存空间)则直接进入老年代。

    老年代GCmajor gc----------指发生在老年代的垃圾回收动作,所采用是的标记--整理算法。

    老年代几乎都是经过survivor熬过来的,它们是不会那么容易死掉,因此major gc不会想minor gc那样频繁

    JVM 新生代为何需要两个 Survivor 空间?

    我们知道,目前主流的虚拟机实现都采用了分代收集的思想,把整个堆区划分为新生代和老年代;新生代又被划分成 Eden 空间、 From Survivor To Survivor 三块区域。 

    看书的时候有个疑问,为什么非得是两个 Survivor 空间呢?要回答这个问题,其实等价于:为什么不是0个或1 Survivor 空间?为什么2 Survivor 空间可以达到要求? 

    为什么不是0个 Survivor 空间? 
    这个问题等价于:为什么需要 Survivor 空间。我们看看如果没有 Survivor 空间的话,垃圾收集将会怎样进行:一遍新生代 gc 过后,不管三七二十一,活着的对象全部进入老年代,即便它在接下来的几次 gc 过程中极有可能被回收掉。这样的话老年代很快被填满, Full GC 的频率大大增加。我们知道,老年代一般都会被规划成比新生代大很多,对它进行垃圾收集会消耗比较长的时间;如果收集的频率又很快的话,那就更糟糕了。基于这种考虑,虚拟机引进了幸存区的概念:如果对象在某次新生代 gc 之后任然存活,让它暂时进入幸存区;以后每熬过一次 gc ,让对象的年龄+1,直到其年龄达到某个设定的值(比如15岁), JVM 认为它很有可能是个老不死的对象,再呆在幸存区没有必要(而且老是在两个幸存区之间反复地复制也需要消耗资源),才会把它转移到老年代。 

    总之,设置 Survivor 空间的目的是让那些中等寿命的对象尽量在 Minor GC 时被干掉,最终在总体上减少虚拟机的垃圾收集过程对用户程序的影响。 

    为什么不是1 Survivor 空间? 

    回答这个问题有一个前提,就是新生代一般都采用复制算法进行垃圾收集。原始的复制算法是把一块内存一分为二, gc 时把存活的对象(EdenSurvivor to)从一块空间(From space)复制到另外一块空间(To space),再把原先的那块内存(From space)清理干净,最后调换 From space To space 的逻辑角色(这样下一次 gc 的时候还可以按这样的方式进行)。 

    我们知道,在 HotSpot 虚拟机里, Eden 空间和 Survivor 空间默认的比例是 8:1 。我们来看看在只有一个 Survivor 空间的情况下,这个 8:1 会有什么问题。此处为了方便说明,我们假设新生代一共为 9 MB 。对象优先在 Eden 区分配,当 Eden 空间满 8 MB 时,触发第一次 Minor GC 。比如说有 0.5 MB 的对象存活,那这 0.5 MB 的对象将由 Eden 区向 Survivor 区复制。这次 Minor GC 过后, Eden 区被清理干净, Survivor 区被占用了 0.5 MB ,还剩 0.5 MB 。到这里一切都很美好,但问题马上就来了:从现在开始所有对象将会在这剩下的 0.5 MB 的空间上被分配,很快就会发现空间不足,于是只好触发下一次 Minor GC 。可以看出在这种情况下,当 Survivor 空间作为对象出生地的时候,很容易触发 Minor GC ,这种 8:1 的不对称分配不但没能在总体上降低 Minor GC 的频率,还会把 gc 的时间间隔搞得很不平均。把 Eden : Survivor 设成 1 : 1 也一样,每当对象总大小满 5 MB 的时候都必须触发一次 Minor GC ,唯一的变化是 gc 的时间间隔相对平均了。 

    上面的论述都是以新生代使用复制算法这个既定事实作为前提来讨论的。如果不是这样,比如说新生代采用标记-清除或者标记-整理算法来实现幸存对象的移动,好像确实是只需要一个 Survivor 就够了。

    为什么2 Survivor 空间可以达到要求? 

    问题很清楚了,无论 Eden Survivor 的比例怎么设置,在只有一个 Survivor 的情况下,总体上看在新生代空间满一半的时候就会触发一次 Minor GC 。那有没有提升的空间呢?比如说永远在新生代空间满 80% 的时候才触发 Minor GC  

    事实上是可以做到的:我们可以设两个 Survivor 空间( From Survivor To Survivor )。比如,我们把 Eden : From Survivor : To Survivor 空间大小设成 8 : 1 : 1 ,对象总是在 Eden 区出生, From Survivor 保存当前的幸存对象, To Survivor 为空。一次 gc 发生后: 
    1
    Eden 区活着的对象 From Survivor 存储的对象被复制到 To Survivor  
    2)
    清空 Eden From Survivor  
    3)
    颠倒 From Survivor To Survivor 的逻辑关系: From To To From  

    可以看出,只有在 Eden 空间快满的时候才会触发 Minor GC 。而 Eden 空间占新生代的绝大部分,所以 Minor GC 的频率得以降低。当然,使用两个 Survivor 这种方式我们也付出了一定的代价,如 10% 的空间浪费、复制对象的开销等。

    上面体现的垃圾回收算法有三种:

    1. 复制回收算法
    2. 标记清除
    3. 标记整理

     

    下一节讲述垃圾回收算法。

     

    展开全文
  • 主要介绍了Java内存模型与JVM运行时数据区的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • JVM运行时数据区如何划分?

    万次阅读 多人点赞 2021-05-10 23:57:37
    JVM运行时数据区可划分为,程序计数器(PC寄存器),Java虚拟机栈,本地方法栈,方法和堆。 其中方法和堆属于线程之间共享的,程序计数器(PC寄存器),Java虚拟机栈,本地方法栈属于线程私有的。 补充 程序...

    写在前面

    本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!

    本专栏目录结构和文献引用请见100个问题搞定Java虚拟机

    解答

    JVM运行时数据区可划分为,程序计数器(PC寄存器),Java虚拟机栈,本地方法栈,方法区和堆。
    其中方法区和堆属于线程之间共享的,程序计数器(PC寄存器),Java虚拟机栈,本地方法栈属于线程私有的。
    

    JVM运行时数据区划分

    补充

    程序计数器(PC寄存器)

    程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器

    PC寄存器

    程序计数器也叫PC寄存器,对于PC寄存器这个名字,可能有人会误以为这是一个寄存器,但事实上PC寄存器是对物理PC寄存器的一种抽象模拟,PC寄存器中存储的是下一条指令的地址,执行引擎(Execution Engine)根据PC寄存器将指令转化为机器语言。
    对于每个线程来说,需要执行的下一条指令的地址是不同的,在CPU快速切换的过程中,线程需要保存下一条指令的位置以便于下次CPU再次切换回来时继续执行,因此PC寄存器需要线程私有。
    这种一个CPU通过快速切换多个程序看似同时执行称为并发,与并行不同,可以将并发看作一对多的模式,将并行看作多对多的模式。

    工作原理

    在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。
    因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
    如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
    如果正在执行的是Native方法,这个计数器值则为空(Undefined)。

    异常

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

    Java虚拟机栈

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

    虚拟机栈的局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnaddress类型(指向了一条字节码指令的地址)。
    其中64位长度的long和double类型的数据会占用2个局部变量空间(Sot),其余的数据类型只占用1个。
    局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时, 这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

    在Java虚拟机规范中,对这个区域规定了两种异常状况

    1. 如果线程请求的梭深度大于虚拟机所允许的深度,将抛出StackoverflowError异常;
    2. 如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈), 如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

    本地方法栈

    本地方法栈(Native Method Stack)与虚拟机机所发挥的作用是非常相似的,它们之间的区别不过是虛拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法则为虚拟机使用到的Native方法服务。
    在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。
    甚至有的虚拟机(比如Sun Hotspot虚拟机)直接就把本地方法栈和虚拟机栈合二为ー。

    异常

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

    Java堆

    对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。

    此内存区域的唯一目的就是存放对象实例,Java 世界里几乎所有的对象实例都在这里分配内存。

    这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但是随着JT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。

    Java堆的划分

    Java堆是垃圾收集器管理的主要区域,因此很多时候世被称做“GC堆”(Garbage Collected Heap)。
    从内存回收的角度来看,由于现在收集器大部分都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代:再细致一点的有Eden空间、 From Survivor空间、 To Survivor空间等。
    从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。

    不过无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存

    Java虚拟机规范

    根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。
    在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。

    异常

    如果在堆没有内存完成实例分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError 异常。

    方法区

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

    永久代

    说到方法区,不得不提一下"永久代"这个概念,尤其是 JDK8 以前,对于习惯在 Hotspot虚拟机上开发、部署程序的开发者来说,很多人都更愿意把方法区称为“永久代”( Permanent Generation),本质上两者并不等价,仅仅是因为 Hotspot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样Hotspot的垃圾收集器可以像管理Java雄一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。
    对于其他虚拟机(如 BEA Jrockit、IBMJ9等)来说是不存在永久代的概念的。

    原则上,如何实现方法区属于虚拟机实现细节,不受虚拟机规范约束,但使用永久代来实现方法区,现在看来并不是一个好主意,因为这样更容易遇到内存溢出问题(永久代有-XX: Maxpermsize的上限,J9和 Jrockit只要没有触碰到进程可用内存的上限,例如32 位系统中的4GB,就不会出现问题),而且有极少数方法(例如 String. Intern0)会因这个原因导致不同虚拟机下有不同的表现。

    永久代到元空间

    考虑到 HotSpot 未来的发展,在 JDK6 的时候 HotSpot 开发团队就有放弃永久代,逐步改为采用本地内存来实现方法区的计划了,
    到了 JDK7 的时候,已经把原本放在永久代的字符串常量池、静态变量等移出,
    而 JDK8,终于完全废弃了永久代的概念,改用与 JRockit、J9 一样在本地内存中实现的元空间(Metaspace)来代替,把 JDK7 中永久代还剩余的内容(主要是类型信息)全部移到元空间中。

    Java虚拟机规范

    Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。

    方法区与垃圾回收

    相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说,这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是必要的。
    在Sun公司的BUG列表中,曾出现过的若干个严重的BUG就是由于低版本的 Hotspot虚拟机对此区域未完全回收而导致内存泄漏。

    异常

    根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。

    展开全文
  • 面试必问的 JVM 运行时数据区,你懂了吗?

    千次阅读 多人点赞 2021-07-03 15:27:15
    面试必问的 JVM

    前言

    Java 虚拟机的运行时数据区经常在面试中被拿来提问,很多概念在市面上有各种各样的说法,搞的不少同学应该是懵逼的。

    当我们陷入不知道哪个说法是正确的情况时,最好的参考就是源码和规范。

    在面试中,当面试官反问你:为什么某某是这样?的时候,如果你回答:因为规范是这么写的、因为源码是这么写的。

    这个回答是非常有说服力的。

    因此,本文在描述一些有争议的问题上,优先以《Java 虚拟机规范》的说法为准。

    正文

    1、运行时数据区(Run-Time Data Areas)

    Java 虚拟机定义了若干种在程序执行期间会使用到的运行时数据区域。

    其中一些数据区域在 Java 虚拟机启动时被创建,随着虚拟机退出而销毁。也就是线程间共享的区域:堆、方法区、运行时常量池。

    另外一些数据区域是按线程划分的,这些数据区域在线程创建时创建,在线程退出时销毁。也就是线程间隔离的区域:程序计数器、Java虚拟机栈、本地方法栈。

    1)程序计数器(Program Counter Register)

    Java 虚拟机可以支持多个线程同时执行,每个线程都有自己的程序计数器。在任何时刻,每个线程都只会执行一个方法的代码,这个方法称为该线程的当前方法(current method)。

    如果线程正在执行的是 Java 方法(不是 native 的),则程序计数器记录的是正在执行的 Java 虚拟机字节码指令的地址。如果正在执行的是本地(native)方法,那么计数器的值是空的(undefined)。

    2)Java虚拟机栈(Java Virtual Machine Stacks)

    每个 Java 虚拟机线程都有自己私有的 Java 虚拟机栈,它与线程同时创建,用于存储栈帧。

    Java 虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

    每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

    3)本地方法栈(Native Method Stacks)

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

    4)堆(Heap)

    堆是被各个线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。

    堆在虚拟机启动时创建,堆存储的对象不会被显示释放,而是由垃圾收集器进行统一管理和回收。

    5)方法区(Method Area)

    方法区是被各个线程共享的运行时内存区域。方法区类似于传统语言的编译代码的存储区。它存储了每一个类的结构信息,例如:运行时常量池、字段和方法数据,构造函数和普通方法的字节码内容,还包括一些用于类、实例、接口初始化用到的特殊方法。

    6)运行时常量池(Run-Time Constant Pool)

    运行时常量池是 class 文件中每一个类或接口的常量池表(constant_pool table)的运行时表示形式。

    它包含了若干种常量,从编译时已知的数值字面量到必须在运行时解析后才能获得的方法和字段引用。运行时常量池的功能类似于传统编程语言的符号表(symbol table),不过它包含的数据范围比通常意义上的符号表要更为广泛。

    2、Java 中有哪几种常量池?

    现在我们经常提到的常量池主要有三种:class 文件常量池、运行时常量池、字符串常量池。

    3、class 文件常量池

    class 文件常量池(class constant pool)属于 class 文件的其中一项,class 类文件包含:魔数、类的版本、常量池、访问标志、字段表集合、方发表等信息。

    常量池用于存放编译期间生成的各种字面量(Literal)和符号引用(Symbolic References)。

    字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为 final 的常量值等。

    符号引用则属于编译原理方面的概念。符号引用是一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可(它与直接引用区分,直接引用一般是指向方法区的本地指针,相对偏移量或是一个能间接定位到目标的句柄)。符号引用主要包括下面几类常量:

    • 被模块导出或开放的包(Package)
    • 类和接口的全限定名(Fully Qualified Name)
    • 字段的名称和描述符(Descriptor)

    常量池中每一项常量都是一个表,截至JDK 13,常量表中分别有17种不同类型的常量。17种常量类型所代表的具体含义如图所示。

    关于 class 文件常量池的更多内容可以阅读周志明的《深入理解Java虚拟机》6.3.2 章节。

    4、运行时常量池

    class 文件常量池是在类被编译成 class 文件时生成的。而当类被加载到内存中后,JVM 就会将 class 文件常量池中的内容存放到运行时常量池中。

    Java 虚拟机规范中对运行时常量池的定义如下:

    A run-time constant pool is a per-class or per-interface run-time representation of the constant_pool table in a class file.

    运行时常量池是 class 文件中每一个类或接口的常量池表(constant_pool table)的运行时表示形式。

    因此,根据规范定义,可以说运行时常量池是 class 文件常量池的运行时表示,每个类在运行时都有自己的一个独立的运行时常量池。

    5、字符串常量池

    简单来说,HotSpot VM 里的字符串常量池(StringTable)是个哈希表,全局只有一份,被所有的类共享。

    StringTable 具体存储的是 String 对象的引用,而不是 String 对象实例自身。String 对象实例在 JDK 6 及之前是在永久代里,从JDK 7 开始放在堆里。

    根据 Java 虚拟机规范的定义,堆是存储 Java 对象的地方,其他地方是不会有 Java 对象实体的,如果有的话,根据规范定义,这些地方也要算堆的一部分。

    6、字符串常量池是否属于方法区?

    我认为是不属于的。

    在读本文之前,我相信很多同学会有如下观点:因为运行时常量池属于方法区,所以很多同学认为字符串常量池也应该属于方法区。

    但是相信看了上面的内容后,会开始意识到,运行时常量池和字符串常量池其实是不同的两个东西,当然它们在字符串解析时会有关联。

    Java 虚拟机规范中对方法区的定义如下:

    The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization

    在 Java 虚拟机中,方法区是被各个线程共享的运行时内存区域。方法区类似于传统语言的编译代码的存储区,或者类似于操作系统进程中的文本段。它存储了每一个类的结构信息,例如:运行时常量池、字段和方法数据,构造函数和普通方法的字节码内容,还包括一些用于类、实例、接口初始化用到的特殊方法。

    这边的关键在于 “它存储了每一个类的结构信息”,而字符串常量池并不属于某个类,字符串常量是全局共享的,因此,根据规范定义,我们可以说字符串常量池不属于方法区。

    那字符串常量池(StringTable)究竟存在哪里了? 

    StringTable 本体是存储在 native memory(本地内存)里,不是在永久代里,不是在方法区里,当然,更不是在堆里。

    7、运行时常量池和字符串常量池的关联? 

    上面说了,运行时常量池和字符串常量池在字符串解析时会有关联,具体如下。

    类的运行时常量池中有 CONSTANT_String_info(见题3表格)类型的常量,CONSTANT_String_info 类型的常量的解析(resolve)过程如下:

    首先到字符串常量池(StringTable)中查找是否已经有了该字符串的引用,如果有,则直接返回字符串常量池的引用;如果没有,则在堆中创建 String 对象,并在字符串常量池驻留其引用,然后返回该引用。

    也就说,运行时常量池里的 CONSTANT_String_info 类型的常量,经过解析(resolve)之后,同样存的是字符串的引用,并且和 StringTable 驻留的引用的是一致的。

    8、String#intern 方法

    在 JDK 7 及之后的版本中,该方法的作用如下:如果字符串常量池中已经有这个字符串,则直接返回常量池中的引用;如果没有,则将这个字符串的引用保存一份到字符串常量池,然后返回这个引用。

    下面的例子可以进行简单的验证:

    public static void main(String args[]) {
    
        // 创建2个对象,str持有的是new创建的对象引用
        // 1)驻留(intern)在字符串常量池中的对象
        // 2)new创建的对象
        String str = new String("joonwhee");
        // 字符串常量池中已经有了,返回字符串常量池中的引用
        String str2 = "joonwhee";
        // false,str为new创建的对象引用,str2为字符创常量池中的引用
        System.out.println(str == str2);
        // str修改为字符串常量池的引用,所以下面为true
        str = str.intern();
        // true
        System.out.println(str == str2);
    }

    9、永久代(PermGen)

    永久代在 Java 8 被移除。根据官方提案的描述,移除的主要动机是:要将 JRockit 和 Hotspot 进行融合,而 JRockit 并没有永久代。

    而据我们所了解的,还有另外一个重要原因是永久代本身也存在较多的问题,经常出现OOM,还出过不少bug。

    根据官方提案的描述,永久代主要存储了三种数据: 

    1)Class metadata(类元数据),也就是方法区中包含的数据,除了编译生成的字节码被放在 native memory(本地内存)。

    2)interned Strings,也就是字符串常量池中驻留引用的字符串对象,字符串常量池只驻留引用,而实际对象是在永久代中。

    3)class static variables,类静态变量。

    移除永久代后,interned Strings 和 class static variables 被移动了堆中,Class metadata 被移动到了后来的元空间。

    10、永久代和方法区的关系?

    方法区是 Java 虚拟机规范中定义的一种逻辑概念,而永久代是对方法区的实现。但是永久代并不等同于方法区,方法区也不等同于永久代。

    永久代中的 interned Strings 并不属于方法区,按规范:堆是存储 Java 对象的地方 ,这部分应该属于堆,因此永久代并不是只用于实现方法区。

    方法区中 JIT 编译生成的代码并不是存放在永久代,而是在 native memory 中,因此可以说方法区也并不只是由永久代来实现。

    11、元空间(metaspace)

    元空间在 Java 8 移除永久代后被引入,用来代替永久代,本质和永久代类似,都是对方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存(native memory)。

    元空间主要用于存储 Class metadata(类元数据),根据其命名其实也看得出来。

    可以通过 -XX:MaxMetaspaceSize 参数来限制元空间的大小,如果没有设置该参数,则元空间默认限制为机器内存。

    12、为什么引入元空间?

    在 Java 8 之前,Java 虚拟机使用永久代来存放类元信息,通过-XX:PermSize、-XX:MaxPermSize 来控制这块内存的大小,随着动态类加载的情况越来越多,这块内存变得不太可控,到底设置多大合适是每个开发者要考虑的问题。

    如果设置小了,容易出现内存溢出;如果设置大了,又有点浪费,尽管不会实质分配这么大的物理内存。

    而元空间可以较好的解决内存设置多大的问题:当我们没有指定 -XX:MaxMetaspaceSize 时,元空间可以动态的调整使用的内存大小,以容纳不断增加的类。

    13、元空间能彻底解决内存溢出(Out Of Memory)问题吗?

    很遗憾,答案是不行的。

    元空间无法彻底解决内存溢出的问题,只能说是有所缓解。当内存使用完毕后,元空间一样会出现内存溢出的情况,最典型的场景就是出现了内存泄漏时。

    最后

    我是囧辉,一个坚持分享原创技术干货的程序员,我的目标是帮助大家拿到心仪的大厂 Offer。

    展开全文
  • 主要介绍了JVM运行时数据区原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主要介绍了JVM运行时数据区划分原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • JVM:一、运行时数据区域

    万次阅读 多人点赞 2018-02-22 23:48:58
    不同的系统有不同的JVM,但是所有的这些JVM都完美的支持Java语法,这就使得write once,run everywhere成为可能。 除此之外,JVM的内存管理机制使得不需要再为每一个新的操作去删除/免费代码,由机器代替程序员这样...
  • JVM运行时数据区1 JVM运行时数据区2 解析JVM运行时数据区2.1 方法(Method Area)2.2 Java堆(Java Heap)2.3 程序计数器(Program Counter Register)2.4 Java虚拟机栈(Java Virtual Machine Stacks)2.5 本地...
  • jvm运行时数据区一共可以划分五个区域。 Method Area(方法) (1) 方法是各个线程共享的内存区域,在虚拟机启动创建 (2) 虽然Java虚拟机规范把方法描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-...
  • Java常见面试题—JVM运行时数据区域

    千次阅读 2017-08-20 15:03:30
    JVM运行时数据区域 JVM在执行JAVA程序会把它管理的内存区域划分为若干个不同的数据区域,统称为运行时数据区,由图可见JVM程序所占的内可划分成5个部分:程序计数器、虚拟机栈(线程栈)、本地方法栈、堆(heap...
  • 下图红色区域(即堆和方法):一个进程对应一份,一个进程可以有多个线程,即是线程共享的。JVM允许一个应用有多个线程并行执行 灰色区域(即PC、栈、本地栈...每个JVM只有一个Runtime实例(即运行时数据区)。 ...
  • 众所周知Java从95年出世之后就一直处于语言排行榜的 top 前几位...其次就是Java 的跨平台性,准确来说应该是Java一次编译多处运行,是基于不同操作系统安装不同的Java运行所需的jvm才能完成如此强大的跨平台运行。 ...
  • JVM运行时数据区(详解+面试)

    千次阅读 多人点赞 2021-04-15 21:53:31
    JVM运行时数据区,不同虚拟机实现可能略微不同,但都会遵从Java虚拟机规范,Java 8虚拟机规范规定,Java虚拟机所管理的内存将会包括一下几个运行时数据区域: 程序计数器(Program Counter Register) 程序...
  • JDK,JRE,JVM的联系是啥...语言无关性是指实现了Java虚拟机规范的语言对可以在JVM运行,如Groovy,和在大数据领域比较火的语言Scala,因为JVM最终运行的是class文件,只要最终的class文件复合规范就可以在JVM运行
  • JVM运行时数据区、常见jvm异常例子

    千次阅读 2022-03-26 18:11:16
    java 运行时数据区整理
  • JVM运行时数据区

    2011-11-28 13:03:19
    JVM定义了若干个程序执行期间使用的数据区域。这个区域里的一些数据JVM启动的时候创建,在JVM退出的时候销毁。其他的数据依赖于每一个线程,在线程创建的时候创建,在线程退出的时候销毁。
  • JVM运行时数据区简介

    千次阅读 2021-10-12 18:04:49
    五、方法 5.1 定义   方法JVM所有线程共享的区域,它存储了与类结构相关的信息:运行时常量池,成员变量,方法数据,成员方法、构造方法的代码。它的大小就决定了系统可以保存多少种类。   方法在...
  • JVM是学习Java必要的学习内容,本文就JVM的基本概念以及JVM运行流程和JVM运行时数据区展开介绍,介绍了运行时数据区的五个部分:堆,方法,虚拟机栈,本地方法栈,程序计数器,最后又说明了内存布局种的异常...
  • JVM——运行时数据区域包括哪些?

    千次阅读 2019-09-09 13:49:57
    6.运行时常量池 1.程序计数器(线程私有) 程序计数器是一块较小的内存空间,可以看作是当前线程所执行字节码的行号指示器。 分支、循环、跳转、异常处理、线程处理等基础功能都需要依赖这个计数器完成。 由于...
  • JDK,JRE,JVM的联系是啥?...语言无关性是指实现了Java虚拟机规范的语言对可以在JVM运行,如Groovy,和在大数据领域比较火的语言Scala,因为JVM最终运行的是class文件,只要最终的class文件复合规范就可以在JVM上运.
  • JVM屏蔽了与具体操作系统平台相关的信息,使java程序只需生成在JVM运行的字节码,就可以在多种操作系统上不加修改的运行(一次编译,到处运行)。JVM在执行字节码,实际上还是把字节码解释成具体平台上的机器码.....
  • JDK1.8 JVM运行时数据区域划分

    万次阅读 多人点赞 2018-02-26 20:53:55
    1.8同1.7比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。 二、各...
  • JVM笔记 运行时数据区

    万次阅读 2020-07-18 17:15:57
    JVM在程序运行Java程序会将管理的内存分为几个不同的数据区域, 这些区域有不同的用途,创建和销毁时间。根据《java虚拟机规范》通常虚拟机管理的内存分为以下几个不同的运行时数据区。 程序计数器 程序计数器...
  • 2.jvm运行时数据区 程序计数器的特点及作用: 1.程序计数器是一块较小的内存空间,几乎可以忽略; 2.是当前线程所执行的字节码的行号指示器; 3.Java多线程执行,每条线程都会有一个独立的程序计数器,各条...
  • JVM是Java应用程序的运行环境,每个Java应用程序,通过main方法作为执行入口启动后,在操作系统中都会对应一个JVM进程。 Java应用程序在启动,需要加载实际执行的类的类文件.class,从而获取类的字段,方法,常量...
  • JVM运行时数据区-方法

    千次阅读 2018-10-23 11:43:00
     在hotspot jvm实现当中,在jdk8以前,方法的实现为PermGen,即永久代,主要存放类的信息,方法的信息,常量池,静态变量,符号解析;而jdk8+,则去除了PermGen,使用元空间MetaSpace代替,元空间使用的是本地...
  • 《Java虚拟机原理图解》3、JVM运行时数据区

    万次阅读 多人点赞 2014-10-13 14:50:08
    JVM运行时数据区(JVM Runtime Area) 其实就是指JVM在运行期间,其对计算机内存空间的划分和分配。本文将通过以下几个话题来讨论JVM运行时数据区。 Topic 1. JVM运行时数据区 里有什么?Topic 2. 虚拟机栈 是什么...
  • JVM虚拟机运行时数据区域的理解

    万次阅读 热门讨论 2017-10-15 16:02:55
    Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用户,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而创建...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 434,851
精华内容 173,940
关键字:

jvm运行时数据区?

友情链接: 神经网络算法.rar