运行时数据区域

Java虚拟机运行时数据区域

程序计数器

程序计数器可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条所需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各线程之间的程序计数器互不影响。这类内存区域成为“线程私有”。

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

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

Java虚拟机栈

线程私有。生命周期与线程相同。

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

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

该区域规定了两种异常:

  • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
  • OutOfMemoryError:如果虚拟机栈可以动态扩展,扩展时无法申请到足够的内存

本地方法栈

本地方法栈与虚拟机栈类似,虚拟机栈为Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它(Sun Hotsopt将其和虚拟机栈合二为一)。

同样也会抛出StackOverflowErrorOutOfMemoryError异常。

Java堆

Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。

几乎所有的对象实例以及数组都要在Java堆里分配内存

Java堆是垃圾收集器管理的主要区域,因此也被称为“GC堆”。

Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的。

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

堆=新生代+老年代,不包括永久代(方法区)。

方法区

方法去与Java堆一样是各个线程共享的内存区域。

存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。JDK7的HotSpot把存放在方法区的字符串常量池移出。(即方法区存类信息?)

和Java堆一样不需要连续的内存、大小可扩展,但方法区还可以选择不实现垃圾收集。同样无法满足内存分配需求会抛出OutOfMemoryError异常。

注意:Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有个别名Non-Heap,目的应该是跟Java堆区分开来。

运行时常量池

运行时常量池是方法区的一部分。

具备动态性,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,例如String的intern()方法。

当常量池无法再申请到内存时OutOfMemoryError异常。

直接内存

直接内存不是虚拟机运行时内存的一部分,也不是Java虚拟机规范中定义的内存区域。但这部分内存也被频繁使用,也可能导致OutOfMemoryError异常。

例如JDK1.4中加入的NIO(New Input/Output)类,引入基于通道和缓冲区的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能避免在Java堆和Native堆中来回赋值数据。

Java堆细节

对象的创建

虚拟机遇到new指令:

  1. 首先检查指令的参数是否能在常量池中定位到一个类的符号的引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过。
  2. 类加载检查通过后,虚拟机为新生对象分配内存。对象所需的内存大小在类加载完成后便可完全确定。为对象分配空间等同于把一块确定大小的内存从Java堆里划分出来,有两种分配方式:
  • 指针碰撞:要求Java堆中内存是规整的,用一个指针作为分界点指示器来区分用过的内存和空闲的内存。分配内存就把指针向空闲空间那边移动与对象大小相等的距离。
  • 空闲列表:Java堆中内存不是是规整的,虚拟机必须维护一个列表来记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。

选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

对象创建是非常频繁的,即使是仅仅修改一个指针所指向的位置,在并发情况下也不是线程安全的:可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用原来的指针来分配内存。对分配内存空间的动作有两种解决方案:

  • 同步处理:实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性
  • 按照线程划分在不同的空间中进行:即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程需要分配内存就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时才需要同步锁定。

内存分配完后,虚拟机会将分配到的内存空间初始化为零值(不包括对象头),如果使用TLAB,这个工作可以提前至TLAB分配时进行。这一步操作保证对象的实例字段在Java代码中可以不赋初始值就可以被使用,初始值为相应数据类型对应的零值。

接下去虚拟机对对象进行必要的设置,例如对象是哪个类的实例、对象的哈希码、对象的GC分代年龄等,这些信息都存在对象头中(Object Header)。

上面工作完成后,从虚拟机视角看,一个新的对象已经产生了,但从Java程序的视角看,对象创建才刚刚开始——<init>方法还没有执行,所有的字段还未零。所以一般来说执行new指令后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

对象的内存布局

Hotspot虚拟机中,对象在内存中存储的布局可分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

对象头包括两部分信息:

  • 第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志....。数据长度在32位和64位虚拟机的长度分别为32bit和64bit,官方称“Mark Word”。但是存储的数据多,已经超出了32位、64位Bitmap结构所能记录的限度。这是与对象自定义的数据无关的额外成本,考虑到虚拟机的空间效率,Mark Word被设计成非固定的数据结构,以便在绩效的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。
  • 另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。但并不是所有虚拟机实现都需要保存类型指针,即查找对象的元数据信息并不一定要经过对象本身。另外,如果对象是一个Java数组,对象头还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。

实例数据存储程序代码中所定义的各种类型的字段内容。无论是父类继承下来的,还是在子类中定义的,都需要记录。存储顺序手虚拟机分配策略参数和字段在Java源码中定义顺序的影响。

对齐填充并不是必然存在,仅起占位符的作用。由于Hotspot自动内存管理系统要求对象起始地址必须是8字节的整数倍,即对象大小必须是8字节的整数倍。对象头正好是8字节的倍数,因此当对象实例数据部分没有对齐时,就需要对齐填充来补全。

对象的访问定位

Java程序通过栈上的reference数据来操作堆里的具体对象。而reference类型只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中对象的具体位置(因为引用只是与对象相关的位置,有可能是对象的起始地址,也有可能是代表对象的句柄),所以对象访问方式是取决于虚拟机实现。目前主流的访问方式有使用句柄和直接指针两种:

  • 使用句柄访问:Java堆会划分出一块内存作为句柄池,reference存储的就是对象的句柄地址,而句柄中包含对象实例数据与类型数据各自的具体信息

  • 使用直接指针访问:Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息

  

二者访问方式各有优势:句柄访问的好处是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改;使用直接指针访问是速度更快,节省了一次指针定位的时间开销,对象的访问在Java中是非常频繁的。HotSpot使用的是第二种。

实战:OutOfMemoryError异常

内存泄露即垃圾收集器无法自动回收这些内存。

单线程下,无论是帧栈太大还是虚拟机栈容量小,当内存无法分配时抛出的都是StackOverflowError异常。多线程就可以抛出OutOfMemoryError异常。(因为单线程的话除了方法区、堆容量外,剩下内存全给栈了,只有一个栈当然只会栈溢出。而多线程的话需要瓜分剩下的内存,如果剩下内存不够瓜分就会内存溢出。)

可通过减少最大堆和减少栈容量来换取更多的线程。即有空闲的内存来换取线程所独有的内存,例如栈内存。

运行时常量池溢出

String.intern()测试:

public static void main(String[] args) {String str1 = new StringBuilder("计算机").append("软件").toString();System.out.println(str1.intern() == str1);String str2 = new StringBuilder("ja").append("va").toString();System.out.println(str2.intern() == str2);
}

这段代码在JDK6中执行结果是两个false:JDK6中,intern会把首次遇到的字符串实例复制到永久代中(放到运行时常量池。方法区和永久代的关系很像Java中接口和类的关系,类实现了接口,而永久代就是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式。),返回的也是永久代中这个字符串的引用。而StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用。

在JDK7的执行结果是一个true和一个false:JDK7的intern()实现不会复制实例,只是在常量池记录首次出现的实例的引用。str2返回false是因为“java”字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用了,不符合首次出现的原则。(这里的意思是之前已经创建过java的字符串对象?然后intern存储相同字符串只会存储第一次所创建的实例引用?)

转载于:https://www.cnblogs.com/yjou/p/11185469.html

深入理解Java虚拟机——第二章——Java内存区域与内存溢出异常相关推荐

  1. 深入Java虚拟机-第二章-Java内存区域-学习笔记

    Java运行时内存区域 Java虚拟机在运行Java程序的时候会将它所管理的内存区域划分为多个不同的区域.每个区域都有自己的用途,创建以及销毁的时间.有的随着虚拟机的启动而存在,有的则是依赖用户线程来 ...

  2. java虚拟机 第二章Java内存区域与内存溢出异常

    1.运行时数据区域 1.1.程序计数器 线程私有,当前线程所执行的字节码行号指示器, Java虚拟机的多线程是通过线程轮流切换处理器执行时间的方式来分配 1.2.Java虚拟机栈 线程私有,,虚拟机的 ...

  3. java 3D 第二章 java 3D基本概念

    java 3D 第二章 java 3D基本概念 java 3D基本概念 java 3D的包及其功能 java 3D 高分辨率大尺度坐标 Java 3D场景图(Scene Graph) VirtualU ...

  4. java方法区内存泄露_深入理解java虚拟机-第二章:java内存区域与内存泄露异常...

    2.1概述: java将内存的管理(主要是回收工作),交由jvm管理,确实很省事,但是一点jvm因内存出现问题,排查起来将会很困难,为了能够成为独当一面的大牛呢,自然要了解vm是怎么去使用内存的. 2 ...

  5. 【深入理解Java虚拟机学习笔记】第二章 Java 内存区域与内存溢出异常

    最近想好好复习一下java虚拟机,我想通过深读 [理解Java虚拟机 jvm 高级特性与最佳实践] (作者 周志明) 并且通过写一些博客总结来将该书读薄读透,这里文章内容仅仅是个人阅读后简短总结,加强 ...

  6. 《深入理解JAVA虚拟机》详细解读(第二章 ):JAVA内存区域与内存溢出异常

    目录 一.JAVA内存区域与内存溢出异常 1. 概述 2. 运行时数据区域 2.1 程序计数器 2.2 Java虚拟机栈 2.3本地方法栈 2.4 堆 2.5 方法区 2.6 运行时常量池 2.7直接 ...

  7. 《深入理解JAVA虚拟机》周志明 第三版 - 第二章 JAVA内存区域与内存溢出异常

    一. 概述 在虚拟机自动内存管理机制下,不容易出现内存泄漏和内存溢出问题,但是一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误.修正问题将会成为一项异常艰难的工作. 二. ...

  8. 《深入理解java虚拟机》第2章 Java内存区域与内存溢出异常

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的"高墙",墙外面的人想进去,墙里面的人却想出来. 2.1 概述 https://blog.csdn.net/q5706 ...

  9. JVM篇:《深入理解Java虚拟机第二版.SUN技术》——笔记

    深入理解Java虚拟机第二版.SUN技术 第1章 Java体系结构介绍 1.1 Java体系结构包括四个独立但相关的技术 1.2 虚拟机 第2章 平台无关 2.1为什么要平台无关 2.2Java体系结 ...

最新文章

  1. Unity + SQL数据库创建管理玩家排行榜学习教程
  2. Blend Tree Type
  3. 实验5:配置通过静态工厂方法创建的bean、实例工厂方法创建的bean、(FactoryBean测试)★
  4. KNN与K-Means
  5. Pytorch教程(十六):FashionMNIST数据集DataSet DataLoader
  6. 原型设计+用户规格说明书
  7. WPS重复数据高亮显示
  8. powerdesigner生成表sql语句时,统一添加默认字段(生成时间、生成人等)
  9. java线程的5个使用技巧
  10. 拜托!程序员的工作不能用时间来衡量
  11. 19 FI配置-财务会计-定义销售/采购税代码
  12. Linux select/poll/epoll
  13. 液晶拼接处理器_创新维OLED拼接屏施工单位操作说明
  14. C++ boost共享锁 unique_lock shared_lock
  15. o蓝屏之死---stop:0X000000c5(0x000000c4等系列)---Mr.Zhang
  16. js 获取到number的length
  17. 重庆医科大学赵浏阳教授招收博士、招聘博士后
  18. 台式电脑卸载了wifi精灵之后能够联网但是无法上网
  19. 彩色空间HSV|RGB|灰度图的理解与OpenCV中的转换【DataWhale琐碎知识点】
  20. 客户端设置超时,max_fails失效----问题分析

热门文章

  1. 2021-2028年中国阻燃装饰行业市场需求与投资规划分析报告
  2. 2021-2027年中国一体化预制泵站行业研究及前瞻分析报告
  3. 2022-2028中国曝光机市场现状及未来发展趋势
  4. 2022-2028年中国玫瑰花行业市场研究及前瞻分析报告
  5. 详细通俗重点CRF层讲解
  6. 数据类型转换pytorch
  7. BERT的通俗理解 预训练模型 微调
  8. Docsify个人网站搭建详细教程
  9. 自动驾驶与汽车安全电子技术
  10. OneFlow 并行特色