JVM基础之内存空间和异常(一)

“合抱之木,生于毫末;千里之行,始于足下;九层之台,起于垒土。”

一、虚拟机运行时数据区

  1. 程序计数器
  • 是一块较小的内存空间,可以看作是当前线程执行的字节码的行号指示器。根据虚拟机的概念模型,字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令(分支、循环、跳转、异常处理、线程恢复都依赖于计数器)。
  • Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻一个“处理器(多核处理器是一个内核)”只会执行一条线程中的指令。
  • 为线程切换后恢复到正确的位置,每个线程都需要一个独立的程序计数器(各线程的计数器互不影响)。
  • 线程执行Java方法时,计数器记录正在执行的虚拟机字节码指令的地址;如果是Native方法,计数器值为空(Undefined)。
  • 是Java虚拟机规范中,没有规定任何OOM情况的区域。
  1. Java虚拟机栈
  • 生命周期与线程相同,描述Java方法执行的内存模型:每个方法在执行时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
  • 局部变量表存放了基本数据类型(boolean、byte、char、short、int、float、long、double、reference对象引用,其中long和double占用2个局部变量空间Slot)。局部变量表需要的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。
  • 申请栈深度大于虚拟机允许值,抛出StackOverflowError;允许动态扩展栈深度时,如果扩展无法申请到足够的内存,会抛出OutOfMemoryError。
  • 只存储对象object的内存地址(指针或引用),不直接保存对象。
  1. 本地方法栈
  • 发挥的作用与虚拟机栈相似,本地方法栈为Native方法服务,虚拟机栈为Java方法(字节码)服务。会因与虚拟机栈同样的原因抛出SOF和OOM异常。
  • Sun HotSpot虚拟机将“本地方法栈”与“虚拟机栈”合二为一。
  1. Java堆
  • Java堆(Java Heap)是Java虚拟机管理的内存中最大的一块。
  • 被所有线程共享,在虚拟机启动时创建。
  • 此区域唯一的目的是存放对象实例(包括数组)。
  • 所有对象实例和数据都要在堆(Heap)上进行分配,但是随着JIT编译器的发展和逃逸分析技术逐渐成熟,导致了一些微妙的变化。
  • Java堆是垃圾收集器管理的主要区域,通常被称为“GC堆”。由于垃圾收集器(GC)通常采用分代收集算法,通常又细分为:新生代和老年代;再细致可分为Eden空间、From Survivor空间、To Survivor空间等。
  • 堆中可以划分出出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,即TLAB)。
  • 根据Java虚拟机规范,Java堆可以处于物理上不连续的内存空间中,只需保证逻辑上的连续。即可以为固定大小,也可以为可扩展(通过-Xmx和-Xms控制)。
  • 当堆中没有内存可以完成实例分配,且无法继续扩展,则抛出OOM异常。
  1. 方法区
  • 方法区(Method Area)是各个线程共享的内存区域,用于存储已被虚拟机加在的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 虚拟机规范把方法区描述为堆的逻辑部分,却有别名Non-Heap(为了与堆区分开)。
  • 在HotSpot虚拟机中,使用“永久代(Permanent Generation)”实现方法区(其他虚拟机不使用这种办法,如IBM J9),但两者并不等价,HotSpot中的GC作用范围扩大到永久代,省去专门的管理代码。
  • 永久代通过-XX:MaxPermSize设置上限,而其他虚拟机(如IBM J9)可以达到内存的上限(内存上限,32位系统为232字节,64位是264字节)。
  • 在HotSpot虚拟机中,逐步放弃使用永久代实现方法区,改用Native Memory实现方法区的规划,在JDK1.7的HotSpot中把原本永久代中的“字符串常量池”移出。
  • 方法区无法满足内存分配需求时抛出OOM异常。
  1. 运行时常量池
  • 运行时常量池(Runtime Constant Pool)是“方法区”的一部分。Class文件中有类的版本、字段、方法、接口等描述信息,还有一项信息是“常量池”,用于存放编译器生成的各种字面常量和符号引用,“常量池”内容在类加载后进入方法区的运行时常量池中存放。
  • Java虚拟机规范关于运行时常量池没有任何细节的要求,不同的虚拟机可以按自己的需求实现这个内存区域,通常“直接引用”也会存储在运行时常量池中。
  • 除使用String.intern()方法可以在运行期间将新的常量放入池中,其他情况都是预置入Class文件中常量池的内容才能进入方法区运行时常量池。
  • 会抛出OOM异常。
  1. 直接内存
  • 直接内存(Direct Memory)不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但这部分内存也被频繁使用,并会抛出OOM异常。
  • 使用Native函数库可以直接分配堆外内存,通过Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,因为避免了Java堆和Native堆中来回复制数据,所以显著的提高了性能。
  • 受到物理内存的限制,会在动态扩展时出现OOM异常。

二、内存中的对象

  1. 对象的创建
  • 首先,检查new指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用的类是否已经被加载、解析和初始化过,如果没有就必须进行类加载过程。
  • 类加载检查通过后,在虚拟机的堆中为新对象分配内存。因为对象的大小在类加载完成后便可以确定,因此为对象分配空间等同于在堆中划分一块确定大小的内存。
  • 堆中划分内存分为两种方式,当堆中内存是绝对规整的(所有的用过的内存放在一边,空闲的内存放在一边,中间放着一个指针作为分界点),分配内存只需将指针挪动一段与对象大小相等的距离,称为“指针碰撞(Bump the Pointer)”;当堆中的内存不是规整的,虚拟机需要维护一个列表记录哪些内存可用,分配的时候从列表中找到一块足够大的恐惧划分给对象实例,并更新到列表的记录。
  • Java堆是否规整由垃圾收集器是否带有压缩整理功能决定。
  • 虚拟机采用CAS和失败重试的方式保证更新操作的原子性,或者把内存分配的动作按照线程划分在不同的空间进行(称为本地线程分配缓存Thread Local Allocation Buffer,TLAB,可以使用-XX:+/-UseTLAB参数设定)。使用TLAB分配时,会在分配TLAB空间时初始化空间为零值。
  • 关于对象的信息,如对象是哪个类的实例、类的元数据、对象的哈希码、对象的GC分代年龄等信息,存放在对象的对象头(Object Header)中。
  1. 对象的内存布局
  • 对象在内存中存储的布局分为三个区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
  1. 对象的访问和定位
  • Java程序需要通过栈上的reference数据来操作堆上的具体对象,目前主流的访问方式有使用句柄和直接指针两种。

三、OutOfMemoryError异常

  1. 程序计数器
    没有规定OOM异常的内存区域。
  2. 堆空间
    堆内存的OOM异常是实际应用中常见的内存溢出异常情况,通过-XX:HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump出当前内存堆转储快照以便事后进行分析,使用-Xms20m和-Xms20m设置最小和最大值。
  3. 栈空间
    栈空间在单线程时栈空间内存不足时抛出SOF异常,在多线程时建立新的线程时会因内存不足抛出OOM异常。
  4. 方法区
    方法区通常会在大量动态生成对象时(如Spring启动时)导致OOM异常,运行时常量池无限制增大同样会导致方法区OOM异常。
  5. 直接内存
    向操作系统申请分配内存时,通过计算得知内存无法分配,会抛出OOM异常。通过-XX:MaxDirectMemorySize指定大小,默认值与堆空间-Xmx值相同。

说明

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

推荐阅读更多精彩内容

  • 简介 Java与C++之间有一堆由内存动态分配与垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来...
    黄俊彬阅读 955评论 2 14
  • 一、Jvm内存区域 1.程序计数器:较小的一块内存,可看做是当前程序所执行字节码的行数。Java虚拟机的多线程...
    windfall_阅读 206评论 0 1
  • 1. JVM运行时数据区域 1.1. 程序计数器 是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示...
    王睿同学阅读 908评论 0 98
  • 1.1 概述 Java优点: 1、结构严谨,面向对象 2、摆脱硬件平台束缚,实现了“一次编写,到处运行”的理想; ...
    viciyforever阅读 1,140评论 1 9