jvm内存模型

Java虚拟机内存模型

计划发布3篇博客, 这是第一篇:jvm内存模型

  • jvm内存模型
  • 对象创建和内存分配
  • OOM异常

问题

java虚拟机管理内存,无需由程序员进行内存的分配和释放。
但是如果出现内存泄漏或内存溢出,如何去排查问题?

这就需要我们去了解java虚拟机内存模型

虚拟机内存的各个区域

了解内存模型的了解虚拟机内存的各个区域

要点

  1. 各个区域的概念
  2. 各个区域的作用
  3. 各个区域的服务对象
  4. 各个区域可能会出现的问题
  5. 生命周期

运行时数据区域

  1. 程序计数器(线程私有)
  2. java虚拟机栈(线程私有)
  3. 本地方法栈(线程私有)
  4. java堆(线程共享)
  5. 方法区(线程共享)

运行时常量池:方法区一部分

直接内存:不属于jvm内存模型,但也被频繁使用,可能导致内存溢出异常

1. 程序计数器

程序计数器是什么

java虚拟机中较小的一块内存空间,是当前线程所执行的行号指示器。

每个线程都有自己的程序计数器,存储程序下一条指令的指针。

程序计数器的作用是什么

字节码解释器通过改变程序计数器中的值,来控制程序中的循环,跳转,异常处理和线程的中断和恢复。

程序计数器会发生内存溢出吗

程序计数器是java运行数据区中唯一不会发生内存溢出的区域

原因:

  1. 程序计数器存放的是每个线程程序的下一条指令指针,消耗内存小。
  2. 在程序运行时,只需改变程序计数器中指针,所以不需要额外申请内存空间。

程序计数器是线程私有的还是共有的

程序计数器是线程私有的内存

原因:

在多线程程序执行时,一个线程的程序计数器不可能被其他线程更改,否则当线程中断再恢复时无法准确的执行下一条命令。

线程私有,即生命周期与线程一致

注意

如果程序正在执行的是java方法,程序计数器记录的是:正在执行的虚拟机字节码指令的地址;

如果程序正在执行的是native方法,程序计数器的值为空:undefined

native方法:本地方法,由其他语言实现的可以在本机器执行的方法。

2. java虚拟机栈

java虚拟机栈是什么

java虚拟机栈是java方法执行的内存模型,每个方法执行时都会创建一个栈帧,栈帧存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,都对应着一个栈帧在java虚拟机栈从入栈到出栈的过程。

可以这样理解:

java虚拟机栈是每个线程开始时开辟的一个栈内存空间,用于存储正在执行的方法信息

方法调用:

  1. 创建方法栈帧
  2. 栈帧入java虚拟机栈
  3. 方法执行
  4. 方法返回
  5. 栈帧出java虚拟机栈

java虚拟机栈和我们通常理解的java栈内存有什么关联

一般把java内存分为堆内存和栈内存,前者是存放java对象实例,后者存放基本数据类型信息。

实际上java栈内存指的是java虚拟机栈中,每个方法栈帧中的局部变量表部分。

什么是局部变量表

局部变量表存放的是编译期可知的各种基本数据类型和对象引用类型。

局部变量表存放各种基本类型和对象引用类型能理解,就和我们常说的java栈内存一样,什么是编译器可知?

java类在编译期时,一个方法中有多少基本数据类型和对象引用类型是可知的,而这些基本数据类型和对象引用类型的所占空间也是已知的,除了64位长度的long和double类型占用两个局部变量空间,其他的都只占用一个局部变量空间。

所以局部变量表在编译期即可确定需要分配多少内存空间。在运行期间,进入一个方法时,直接分配指定大小的内存空间,而不会改变局部变量表的大小。

即局部变量表在编译期可知,在运行期直接分配。

栈帧中除了局部变量表还有其他类型吧

栈帧是一种特殊的数据结构,存储:

  1. 局部变量表
  2. 操作数栈
  3. 动态链接
  4. 方法出口

java虚拟机栈会出现内存溢出吗

会,而且分两种情况:

  1. java虚拟机栈大小不可动态扩展,当前线程请求的栈深度 > 虚拟机所允许的深度, 抛出内存溢出异常。
  2. java虚拟机栈大小可动态扩展,扩展时无法申请到足够的内存, 抛出内存溢出异常。

java虚拟机栈默认可动态扩展,可以通过参数设置。

实际案例中有因为java虚拟机栈导致的内存溢出吗

最常见的就是:死递归时,java虚拟机栈内存空间耗尽,抛出内存溢出异常。

java虚拟机栈是线程私有的,即生命周期与线程一致

3. 本地方法栈

本地方法栈是什么

本地方法栈与虚拟机栈很相似,区别就是:

  1. Java虚拟机栈是为虚拟机执行Java方法提供服务
  2. 本地方法栈是虚拟机执行native方法提供服务

这么说本地方法栈也会抛出栈溢出和内存溢出的异常?

是的,本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常

注意

虚拟机规范中并没有对本地方法栈中方法所用的语言、使用方式和数据结构进行强制规定,虚拟机可以自由实现。
所以Sun HotSpot虚拟机直接把本地方法栈和虚拟机栈合而为一

4. java堆

堆我知道,就是Java存放所有对象实例的内存空间

是的,Java堆是Java虚拟机管理的最大一块内存空间,被所有线程共享,在虚拟机启用时创建。
几乎所有的对象实例都在堆上存放。

为什么说几乎

因为随着jit编译器的发展和逃逸分析技术逐渐成熟,也可能对象实例使用栈上分配和标量替换

简单说下对象实例的栈上分配和标量替换的优化技术

简单说下自己了解的,Java堆上可以分配多个线程私有的线程本地分配缓冲区(TLAB)
对象实例由对应的基本类型组成,使用逃逸分析判断是否对象是否逃逸,如果不逃逸,直接将对象所对应的基本类型在TLAB上存储。当线程结束后,TLAB上对象清理。

虚拟机栈是栈空间,那堆空间是什么样的呢

堆空间在物理上可以是不连续的,只需要在逻辑上是连续的即可,和我们本地磁盘一样。
堆空间可以实现成固定大小,也可以动态扩展,主流虚拟机都动态扩展的。

堆会发生内存溢出吗

会。如果堆中没有内存完成实例分配,而且堆无法再扩展时,就会抛出内存溢出异常

堆和垃圾收集器有什么关系

堆是Java垃圾收集器管理的主要区域,因此很多时候被称为GC堆

关于Java垃圾收集器在第三章讲解

5. 方法区

什么是方法区

和Java堆一样,是各个线程共享的内存区域,在虚拟机启动时创建。
主要存储:

  1. 被虚拟机加载的类信息
  2. 常量
  3. 静态变量
  4. 即时编译器编译后的代码

方法区会发生内存溢出吗

会。如果方法区无法满足内存分配需求时,就会抛出内存溢出异常。

而且,在低版本的虚拟机中,方法区发生内存溢出的问题更常见。

这时为什么呢

Java在1.8版本之前,使用了堆中永生代来实现了方法区。
优势:Java虚拟机可以像管理堆那样管理方法区,省去了专门为方法区编写内存管理代码的工作。
劣势:这样更容易发生内存溢出异常。因为永生代有一个内存上限,加上整个方法区占用空间,内存溢出风险大大提高。

java在1.8版本中完全移除永久代的实现,采用了metaspace(元空间)代替。元空间不在虚拟机中,而是使用本地内存。

运行时常量池

运行时常量池是什么

运行时常量池是方法区的一部分。
方法区存储被虚拟机加载的类信息,而Class文件中有:

  1. 类的版本号
  2. 字段
  3. 方法
  4. 接口
  5. 常量池

常量池存放:编译期生成的各种字面量和符号引用

而常量池在运行时会被放到运行时常量池中。

什么是字面量和符号引用

字面量:String a = "hello"; 字符串"hello" 就是字面量
符号引用:存在于class文件中,运行时经过动态链接变成对象的直接引用。因为java的多态性,在运行时确定具体的引用对象,所以编译期的是符号引用,被虚拟机严格规定的对象引用字面量。

运行时常量池会发生内存溢出吗

运行时常量池是方法区的一部分,所以和方法区一致,当常量池无法再申请到内存时,抛出内存溢出异常

常量池、运行时常量池、字符串常量池这三个都弄糊涂了

  1. 常量池:即class文件常量池,是class文件的一部分,用于保存编译时确定的数据。
  • 保存的内容:
      1. 字面量
        1. 文本字符串
        1. 被声明为final的常量值
        1. 基本数据类型的值
        1. 其他
      1. 符号引用
        1. 类和结构的完全限定名
        1. 字段名称和描述符
        1. 方法名称和描述符
  1. 运行时常量池:

      1. java不一定在编译期产生,运行期也可能产生常量,例如使用String.intern()方法。这些常量被放到运行时常量池中。
      1. 类加载后,常量池中数据也会在运行时常量池中存放
  2. 字符串常量池:在JDK1.7之前的HotSpot JVM中,记录interned String的一个全局表 StringTable, 本质是HashSet<String>。他只存储Java.lang.String的引用,而不记录String的内容。

  • 全局共享,只有一份。1.8之后从永生代移到java堆中

java1.8方法区变化

  1. 移除了永生代,使用元空间实现方法区。
  2. 永久代的class metadata,即被虚拟机加载的类信息,移到元空间
  3. 永久代的字符串常量池和静态变量,移到java堆中
  4. 永久代参数 -> 元空间参数

关于java1.8的内存模型,参考:

java8内存模型-永久代和元空间

直接内存

直接内存是什么,不在java虚拟机五个数据区域里呀

是的,直接内存并不属于java虚拟机管理的五个数据区域。但也是经常用到的,也可能会发生内存溢出异常。

在jdk1.4中引入了NIO类,使用基于通道和缓冲区的I/O方式。
它是使用Native函数库直接分配堆外内存,然后通过存储在堆中的DirectByteBuffer对象,作为这块内存的引用进行操作。
这样在一些场景中可以显著的提高性能,因为避免了在Java堆和Native堆中来回复制数据。

既然直接内存是堆外内存,不受java堆大小的限制,为什么还会发生内存溢出呢?

因为它还是内存空间,会受到本机系统的内存大小限制。
如果虚拟机各个内存区域总和 > 物理内存限制,就很可能导致直接内存动态扩展时出现内存溢出异常。

总结

java虚拟机内存模型

1. java虚拟机5个内存区域

  1. 程序计数器
  • 生命周期:线程私有,随线程存在
  • 存储内容:存储当前线程下一条指令的引用
  • 内存溢出异常:没有内存溢出异常
  1. java虚拟机栈
  • 生命周期:线程私有,随线程存在
  • 存储内容:存储当前线程正在执行方法的栈帧
  • 内存溢出异常:
      1. 虚拟机栈不可扩展,栈请求的深度 > 虚拟机允许的栈深度
      1. 虚拟机栈可扩展,扩展时无法申请到足够的内存
  1. 本地方法栈
  • 生命周期:线程私有,随线程存在
  • 存储内容:存储当前线程正在执行的本地方法的栈帧
  • 内存溢出异常:
      1. 本地方法栈不可扩展,栈请求的深度 > 虚拟机允许的栈深度
      1. 本地方法栈可扩展,扩展时无法申请到足够的内存
  1. java堆
  • 生命周期:线程共享,虚拟机启动时创建
  • 存储内容:几乎所有的java对象实例
  • 内存溢出异常:堆中没有内存空间完成对象内存分配,且扩展时无法申请到足够的内存
  1. 方法区
  • 生命周期:线程共享,虚拟机启动时创建
  • 存储内容:被虚拟机加载的类信息、静态变量、常量
  • 内存溢出异常:无法申请到足够的内存

java虚拟机管理之外的内存区域

直接内存

  • 生命周期::线程共享,虚拟机启动时创建
  • 存储内容:Java NIO的Channel和Buffer
  • 内存区域:使用Native函数库直接分配堆外内存
  • 内存溢出异常:本地内存用完

java1.8方法区变化

  1. 移除了永生代,使用元空间实现方法区。
  2. 永久代的class metadata,即被虚拟机加载的类信息,移到元空间
  3. 永久代的字符串常量池和静态变量,移到java堆中
  4. 永久代参数 -> 元空间参数

想共同学习jvm的可以加我微信:1832162841,或者进QQ群:982523529

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 秒针在表盘上转的飞快,一转眼时间就溜走了,去年她到乡下支教的时候朋友大伙都阻止她,这一年下来没少吃苦头。 村里人文...
    左锦兰可乐阅读 343评论 2 4
  • “砰——” 秦艽被一辆车撞到在地,蛋糕打翻了,奶油撒了一地,洁白的奶油上全是血,触目惊心。...
    蕲安阅读 1,466评论 11 8
  • 青春如棋 落子无悔 往事如风 来了又走 走了又来 回忆里有你有我 还有年少放肆的轻狂 你走了以后 我明白了什么都会...
    张小仙吖阅读 579评论 4 11
  • 同理心回应总结——由铠睿2019.1.21 通过本次的学习,我让有了更深层的思考,自己的困惑点究竟在哪里?我真的意...
    铠睿阅读 360评论 0 0
  • 引子:父div添加监听事件,子div设置监听事件,点击子元素,会出现什么结果 事件传播的三个过程,事件捕获阶段、处...
    胡思乱想的Alice阅读 869评论 0 1