JVM内存区域

  • 前言
    这篇是JVM系列的第一篇文章,主要是介绍一下在JVM在运行期间内存区域的划分情况。

  • 先给一张图:


    jvm运行时内存结构.png

OK,图上大家简单的看一下,我们可以把JVM运行时内存结构分为三部分。第一部分称之为数据私有区域(程序计数器、虚拟机方法栈、本地方法栈),第二部分称之为数据共享区域(堆内存、方法区),第三部分称之为直接内存(直接内存本身并不受JVM虚拟机)


大体上的划分区域以后,我们再来细说各个区域的情况:

线程私有区域

  • 程序计数器

每个线程都有着属于自己的线程计数器,用来记录着当前线程执行到哪个字节码地址。可以把它当成是当前线程所执行的字节码的\color{red}{行号指示器}当程序执行的是java方法的时候,记录的是虚拟机的字节码指令地址,但如果执行的native方法,那么记录的就是个空值这个内存区域也是JVM规定不会抛出OOM的区域。当然因为程序计数器属于线程私有,所以它的生命周期是随着线程的创建而诞生,也会随着线程的结束而消亡。
再深一点,为什么要有这个计数器呢?因为多线程情况下,CPU是不会去记录线程的执行位置的,所以只能由线程自己去记录自己的执行位置。
再深一点,为什么该区域是不会抛出OOM的?因为保存的字节码指令地址的值大小是固定的,因此可以在创建之初分配一个绝对不可能溢出的内存。
再深一点,为什么执行native方法保存的值是空呢?native方法是C/C++所写,由系统调用,不会产生字节码。因此,也就没有指令偏移地址可供记录。

  • 虚拟机方法栈

用来描述java方法执行的内存模型,它会为每一个即将执行的java方法创建一个栈帧,这个栈帧是一个用来存储局部变量表、操作数栈、动态链接、方法出口等信息的数据结构。
而java方法的从执行到结束,对应的正好是一个栈帧从入栈到出栈的过程。所以栈帧的生命周期是随着方法的执行而创建,随着方法的结束而销毁。同样,我们也可以知道虚拟机方法栈是随着线程的创建而诞生,也会随着线程的结束而消亡
为了更加形象点,给个虚拟机方法栈的图吧:

虚拟机方法栈.png

对了,在虚拟机方法栈这里的话,可能会报俩种异常:StackOverFlowError和OutOfMemeryError。
栈溢出异常指的是程序请求的栈深度超过了JVM允许的最大栈深度。
内存溢出异常指的是在JVM允许栈可以动态扩展的时候(目前大部分的虚拟机都运行动态扩展),无法为栈申请到足够的内存空间,则会抛出OOM。
\color{red}{关于栈帧里存储的数据信息,后面我会在这个系列里,专门写一篇文章来探讨}

  • 本地方法栈

本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如 Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会抛出StackOverFlowError和OutOfMemoryError异常。
————————————————

线程共享区域

用来存放对象的空间,几乎所有的对象都会被分配到这里进行存储,也是JVM里内存区域最大的一块。这里也是GC作用的主要区域,在GC的概念里(由于GC采用的是分代收集算法)在堆里也会划分为新生代、老年代。而新生代也被分为了Eden区、From Survivor区和To Survivor区。

我们刚刚在上面的描述里用到了几乎所有这个词,那就是说明还是有对象是不会被分配到堆内存里的,那么是哪些对象呢?
随着JIT技术的发展以及使用逃逸分析,JVM来判断方法里的对象是否会发生逃逸(可以理解为该对象不会被该方法之外的其他方法引用),如果没有发生逃逸,则可能会为这个对象分配到栈内存上而非堆内存里(当然也不是绝对是这样的),这样的好处很明显:减少了堆的内存分配压力,同时也会减少GC发生的次数

该区域会发生OOM,导致的原因由俩个:

  1. 由于发生内存泄露导致对象无法被GC,已经分配出去的内存空间没有办法收回,导致可分配内存空间越来越小,最终的结果是就报OOM异常。
  2. 内存中的对象确实还应该存活,但由于内存不够用产生的异常。
  • 方法区

与堆内存一样,也是属于线程共享区域。主要是用来存放一些被JVM加载过的类的信息,静态变量、常量、JIT编译后的代码数据。当然方法区在JDK1.7与1.8的时候实现方式也是有所不同的。
1.7:
方法区的实现主要是以永久代的形式来实现,当然这里的永久代已经与之前的版本里的永久代有所区别了,原先永久代里的字符串常量池已经被移动到堆内存里了。
1.8:
永久代的概念已经被元空间(元数据空间)给取代了,而原先是存储在堆内存里的也变成与直接内存(堆外内存)一样存放在了本地内存里。同时原先的静态变量与运行时常量池也被移动到了堆内存里。这样元数据的分配只受本地内存的大小,就不会受到OOM的影响了

  • 运行时常量池

首先,它是方法区的一部分,在Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员用的比较多的是String类的intern()方法
既然运行时常量池是方法区的一部分,自然会受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

————————————————

直接内存

堆外内存,又被称为直接内存。它并不是虚拟机运行时数据区的一部分,也不是JAVA虚拟机规范中定义的内存区域。这部分内存不是由jvm管理和回收的。需要我们手动的回收。

堆内内存是属于jvm的,由jvm进行分配和管理,属于"用户态",而推外内存是由操作系统管理的,属于"内核态"
在JDK1.4中加入了NIO类,引入了一种基于通道(Channel)缓冲区(Buffer)的I/O方式,他可以使用Native函数库直接分配堆外内存,然后通过一个存储在JAVA堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在JAVA堆中和Native堆中来回复制数据。

NIO申请直接内存总结:
我们用NIO类申请的内存其实是由jvm进行回收的,并不像unsave那样要我们自己对内存进行管理。这时候系统是不断回收直接内存的,由NIO申请的直接内存是需要System.gc()来进行内存回收的。系统会帮助我们回收直接内存的。不过为了提高gc的利用率,我们可能会在代码中加入-XX:+DisableExplicit禁止代码中显示调用gc(System.gc)。采取并行GC,就是由jvm来自动管理内存回收,而jvm主要是管理堆内内存,也就是当对堆内对象回收的时候,才有可能回收直接内存,这种不对称性很有可能产生直接内存内存泄漏。需要注意的是当我们没有指向堆外内存的引用的时候,也会把直接内存回收

采用直接内存的优点:

  1. 对于频繁的io操作,我们需要不断把内存中的对象复制到直接内存。然后由操作系统直接写入磁盘或者读出磁盘。这时候用到直接内存就减少了堆的内外内存来回复制的操作。
  2. 我们在运行程序的过程中可能需要新建大量对象,对于一些声明周期比较短的对象,可以采用对象池的方式。但是对于一些生命周期较长的对象来说,不需要频繁调用gc,为了节省gc的开销,直接内存是必备之选。
  3. 扩大程序运行的内存,由于jvm申请的内存有限,这时候可以通过堆外内存来扩大内存。

——————————————

  • 写在最后

JVM系列文章,主要还是偏理论居多,写起来也得翻很多资料,看起来也比较难以理解。所以如果真的要好好了解这块东西,需要大家还是得耐着性子看下去。

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

推荐阅读更多精彩内容