JVM内存结构

JVM内存的运行时数据区:

线程私有(在线程启动时创建)

程序计数器Program Counter Register

一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

虚拟机栈VM Stack

描述的是Java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧[Stack Frame]用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

局部变量表

局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型(指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

局部变量表用于存放方法参数和方法内部定义的局部变量。

局部变量表的容量以变量槽(Slot)为最小单位。

虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程的,局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字this来访问到这个隐含的参数。

为了节省栈帧空间,Slot是可以重用的,但是会有一些副作用,比如会影响系统的垃圾收集行为。所以,如果遇到一个方法,其后面的代码有一些耗时很长的操作,而前面又定义了占用大量内存、实际上已经不会再使用的变量,手动将其设置为null值便不见得是一个绝对无意义的操作,这种操作可以作为一种在极特殊情形下的奇技来使用。(经过JIT编译后,设置null没有意义)

操作数栈

后入先出栈

动态链接

在运行期间将符号引用转化为直接引用。

方法返回地址

无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息。

两种异常:

  1. StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
  2. OutOfMemoryError:虚拟机规范中允许固定长度的虚拟机栈。虚拟机栈可动态扩展的前提下,扩展时无法申请到足够的内存
本地方法栈Native Method Stack

为虚拟机使用到的Native方法服务。

与虚拟机栈一样,抛出的异常有StackOverflowError和OutOfMemoryError。

线程共享(在虚拟机启动时创建)

堆Heap

对于大多数应用来说,堆是最大的一块内存空间,用来存放对象实例。

堆是垃圾收集器管理的主要区域。

从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为新生代和老年代。

新生代:一个eden,两个survivor[from和to](提高GC的效率)

在一次GC中,Eden区的对象要么被回收,要么进入survivor区,在survivor区大小够用的情况下,已在survivor区的对象并不会马上进入老年代,而是等达到一定的年龄(GC次数,可人工设置)

堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。同样既可以是固定大小,也可以可扩展。

异常:OutOfMemoryError[Java heap space]:堆中没有内存完成实例分配,并且堆也无法再扩展

方法区Method Area

用于存储已被虚拟机加载的类信息常量静态变量、即时编译器编译后的代码等数据。

和堆一样不需要连续的内存和可以选择固定大小或者可扩展,还可以选择不实现垃圾收集。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。

异常:OutOfMemoryError:方法区无法满足内存分配需求

运行时常量池Runtime Constant Pool

是方法区的一部分。

  1. 编译期:Class文件中常量池信息,用于存放编译期生成的各种字面量和符号引用。
  2. 运行期:例如String.intern(),如果池中已经包含一个等于此String对象的字符串,则返回池中的字符串,否则将此String对象添加到池中,并且返回此String对象的引用。

元空间Meta Space

JDK1.8+,使用直接内存。

运行图解

方法调用

解析和分派之间的关系并不是二选一的排他关系,它们是在不同层次上去筛选、确定目标方法的过程。

解析resolution

方法调用指令:

invokestatic 调用类(静态)方法

invokespecial 调用实例构造器<init>方法、私有方法和父方法

invokevirtual 调用所有的虚方法

invokeinterface 调用接口方法,会在运行时再确定一个实现此接口的对象

invokedynamic现在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法

只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本。非虚方法包括静态方法、私有方法、实例构造器、父类方法、final方法。它们在类加载的时候就会把符号引用解析为该方法的直接引用。[虽然final方法是用invokevirtual来调用的,但是由于它无法被覆盖,没有其他版本,所以也无须对方法接收者进行多态选择,是一种非虚方法]

分派dispatch
静态分派

Human man = new Man();,其中Human是静态类型,Man是实际类型。

静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。

所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。

静态分派的典型应用是方法重载。编译器在重载时是通过参数的静态类型而不是实际类型作为判定依据的。

动态分派

在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。

动态分派的典型应用是方法重写

invokevirtual指令的运行时解析过程:

  1. 找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。
  2. 如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验。如果通过则返回这个方法的直接引用,查找结束;否则产生异常。
  3. 否则,按照继承关系从下往上一次对C的哥哥父类进行第2步的搜索和验证过程。
  4. 如果是中没有找到合适的方法,则抛出异常。

由于invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上。这就是重写的本质。

单分派

方法的接收者与方法的参数称为方法的宗量。

单分派是根据一个宗量对目标方法进行选择。

Java的动态分派属于单分派。[根据实际类型选择]

多分派

多分派是根据多于一个宗量对目标方法进行选择。

Java的静态分派属于多分派。[根据静态类型和方法参数选择]

总结

Java是一门静态多分派,动态单分派的语言。

对象

对象的创建

  1. 虚拟机遇到一条new指令时,首先检查该指令参数是否能在常量池中定位到一个类的符号引用,并检查该符号引用代表的类是否已被加载、解析和初始化过。
  2. 在类加载检查通过后,接下来虚拟机将为新生对象分配内存。[类加载后就可以确定内存大小!]
  3. 对象头外,其余被分配的内存空间初始化为零。
  4. 虚拟机对对象进行设置(设置对象头)。
  5. 执行<init>方法,将对象初始化。[invokespecial]

对象的内存布局

对象头Object Header

第一部分:存储对象自身的运行时数据(Mark Word)

第二部分:类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

实例数据Instance Data

对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。

对齐填充Padding

非必然存在,仅仅是占位符的作用。

对象的访问定位

通过栈上的reference数据来操作堆上的具体对象。

句柄

堆中划分一块内存作为句柄池reference中存储的是对象的句柄地址,句柄中包括了对象实例数据的具体地址(堆)和对象类型数据的具体地址(方法池)。

好处:reference中存储的是稳定的句柄地址,对象被移动(GC)时只会改变句柄中的指针,而不需要改变reference本身。

直接指针

reference中存储的是对象地址,而对象到访问类型数据的指针存放在堆中。

好处:访问速度快,节省了一次指针定位的开销。

对象的存活判定

引用计数算法

给对象添加一个引用,有一个地方引用就+1;引用失效就-1。

问题:循环引用。

可达性分析算法

通过一系列GC Roots的对象作为起始点,从这些节点开始向下搜索。当一个对象到RC Roots没有任何引用链相连时,则证明此对象是不可用的。

可作为RC Roots的对象包括:

  • 虚拟机栈栈帧中的本地变量表中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象

反汇编命令javap

javap -c对代码进行反汇编

javap -v输出附加信息(包括行号、本地变量表,反汇编等详细信息)

命令获得的指令可以通过jvm指令手册查看。

jvm(1):内存结构的更多相关文章

  1. 巩固java(二)----JVM堆内存结构及垃圾回收机制

    前言:        我们在运行程序时,有时会碰到内存溢出(OutOfMemoryError)的问题,为了解决这种问题,我们有必要了解JVM的内存结构和垃圾回收机制. 正文: 1.JVM堆内存结构   ...

  2. JVM的内存结构,JVM的回收机制

    内存作为系统中重要的资源,对于系统稳定运行和高效运行起到了关键的作用,Java和C之类的语言不同,不需要开发人员来分配内存和回收内存,而是由JVM来管理对象内存的分配以及对象内存的回收(又称为垃圾回收 ...

  3. JVM 垃圾回收机制和常见算法和 JVM 的内存结构和内存分配(面试题)

    一.JVM 垃圾回收机制和常见算法 Sun 公司只定义了垃圾回收机制规则而不局限于其实现算法,因此不同厂商生产的虚拟机采用的算法也不尽相同.GC(Garbage Collector)在回收对象前首先必 ...

  4. JVM之内存结构详解

    对于开发人员来说,如果不了解Java的JVM,那真的是很难写得一手好代码,很难查得一手好bug.同时,JVM也是面试环节的中重灾区.今天开始,<JVM详解>系列开启,带大家深入了解JVM相 ...

  5. JVM的内存结构以及性能调优

    JVM的内存结构以及性能调优 发布时间: 2017-11-22 阅读数: 16675 JVM的内存结构以及性能调优1:JVM的结构主要包括三部分,堆,栈,非堆内存(方法区,驻留字符串)堆上面存储的是引 ...

  6. JVM的基本结构和JVM的内存结构

    这里概要介绍一下JVM在启动后,作为操作系统的一个进程的基本结构,以及从操作系统角度看,JVM如何管理它从操作系统里申请来的内存的,也就是JVM的内存结构或者叫JVM内存模型. 1.JVM的基本结构 ...

  7. JVM:内存结构

    JVM:内存结构 说明:这是看了 bilibili 上 黑马程序员 的课程 JVM完整教程 后做的笔记 内容 程序计数器 虚拟机栈 本地方法栈 堆 方法区 直接内存 1. 程序计数器 1.1 定义 P ...

  8. JVM之内存结构

    JVM是按照运行时数据的存储结构来划分内存结构的.JVM在运行Java程序时,将他们划分成不同格式的数据,分别存储在不同的区域,这些数据就是运行时数据.运行时数据区域包括堆,方法区,运行时常量池,程序 ...

  9. Java中的JVM的内存结构

    Java的虚拟机自身结构图: JVM内存结构主要包括两个子系统和两个组件.两个子系统分别是Classloader子系统和Executionengine(执行引擎)子系统:两个组件分别是Runtimed ...

  10. JVM(一) 内存结构

    JVM内存结构 方法区(JDK8以上叫元空间)和堆为线程共享区,虚拟机栈.本地方法栈及程序计数器为线程独占区,  还有一个没有在下图中体现的叫做直接内存(Direct Memory),不受JVM GC ...

随机推荐

  1. 如何开发自己的第一个 Serverless Component

    前言 上一篇 基于 Serverless Component 的全栈解决方案 介绍 Serverless Component 是什么和如何使用 Serverless Component 开发一个全栈应 ...

  2. Python函数进阶:闭包、装饰器、生成器、协程

    返回目录 本篇索引 (1)闭包 (2)装饰器 (3)生成器 (4)协程 (1)闭包 闭包(closure)是很多现代编程语言都有的特点,像C++.Java.JavaScript等都实现或部分实现了闭包 ...

  3. 动手实现一个同步器(AQS)

    在多线程情景下,如果不会某一共享变量采取一些同步机制,很可能发生数据不安全现象,比如购买车票时,当多个人购买时,不加锁就会产生多人买同一张票的现象,显然这是不可取的.所以要有一种同步机制,在某一时刻只 ...

  4. 【spring boot】SpringBoot初学(3)– application配置和profile隔离配置

    前言 github: https://github.com/vergilyn/SpringBootDemo 说明:我代码的结构是用profile来区分/激活要加载的配置,从而在一个project中写各 ...

  5. day6 基础总结和编码方式

    # = 赋值 == 比较值是否相等 is 比较内存地址 li1 = [1, 2, 3] li2 = li1 print(li1 is li2) print(id(li1), id(li2)) #数字, ...

  6. gulp常用插件之gulp-beautify使用

    更多gulp常用插件使用请访问:gulp常用插件汇总 gulp-beautify这是一款使用js-beautify进行资产美化插件. 更多使用文档请点击访问gulp-beautify工具官网. 安装 ...

  7. Docker构建镜像过于缓慢解决-----Docker构建服务之部署和备份jekyll网站

    参考原文链接:https://www.jianshu.com/p/e6b7e68f2ba7 来自<第一本Docker书>,我觉得很有趣,就记录一下 准备国内ubuntu镜像 每次构建Ubu ...

  8. FHQTreap刷题记录

    分裂:如图,按值分裂,u比k大则切黄色的一

  9. 03-SV过程语句和子程序

    1.过程语句 for语句.do-while语句.while语句 initial begin string cmd; int file,c; $display("=========== con ...

  10. JavaScript-事件类型

    DOM3事件类型: 1.UI事件:当用户与页面上的元素交互时触发 a.DOMActivate:元素已经被用户操作激活. b.load:(1)页面完全加载:window触发. (2)所有框架加载完毕:框 ...