摘要: JVM内存的划分,导致内存溢出异常的可能区域。

1. JVM运行时内存区域

  JVM在执行Java程序的过程中会把它所管理的内存划分为以下几个区域:

1.1 程序计数器

  程序计数器是一块较小的内存空间,在Java虚拟机规范中是唯一一个未规定OutOfMemoryError的内存区域。

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

  每个线程均有一个独立的程序计数器。若线程执行的是一个Java方法,计数器记录的值是正在执行虚拟机字节码地址;若执行的是Native方法,计数器值为空(Undefined)。

1.2 Java虚拟机栈

  Java虚拟机栈也就是我们经常说的"栈"(其实这个说法不够严谨<^_^>),它是线程私有的,生命周期与线程相同。

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

  局部变量表存放了编译期可知的8种基本数据类型、对象引用(reference类型,指向对象起始地址的引用指针或代表对象的句柄或其他与此对象相关的位置)、returnAddress类型(指向一条字节码指令的地址)。

  Java虚拟机规范对这块区域规定了两种异常情况:

    • StackOverflowError

      线程请求的栈深度大于虚拟机所允许的深度,将抛出此异常;

    • OutOfMemoryError

      若虚拟机栈可以动态扩展,但在扩展时无法申请到足够的内存,将抛出此异常;

1.3 本地方法栈

  与虚拟机栈类似,但它执行的Native方法服务。

1.4 Java

  "GC"堆,虚拟机启动时创建,为所有线程共享的一块区域。在Java虚拟机规范描述中,所有对象实例和数组都要在堆上分配(随着JIT编译器的发展和逃逸技术的逐渐成熟,所有对象在堆上分配也变得不是这么绝对)。

  如果堆中剩余内存不足以完成实例的分配,且无法再扩展,将抛出OutOfMemoryError。

1.5 方法区

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

  根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError。

1.6 运行时常量池

  方法区的一部分,在类加载后进入方法区时,存放编译期生成的各种字面量和符号引用。

  在运行期间,也可能将新的常量放入池中,如String常用的intern()方法。

  当无法满足内存分配需求时,将抛出OutOfMemoryError。

1.7 直接内存

  这部分区域并不是虚拟机运行时数据区的一部分,也非Java虚拟机规范中定义的内存区域,但也被频繁使用,亦可能抛出OutOfMemoryError。

  直接内存主要为NIO(JDK1.4 加入的类)使用,通过存储在Java堆中的DirectByteBuffer对象对这块内存的引用进行操作。在某些场合能显著提高性能,避免了Java堆和Native堆中来回复制数据。

2. HotSpot虚拟机对象

  讨论Java堆中普通对象分配、布局和访问的过程。

2.1 对象的创建

    • 虚拟机遇到一条new命令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过,若没有,则先执行相应的类加载过程;
    • 在类加载检查通过后,虚拟机将为新生对象分配内存:
      • 指针碰撞

        若Java堆中的内存是绝对规整的,所有用过的和空闲的内存都各放一边,中间放着一个指针作为分界点的指示器,那分配内存仅仅是将指针向空闲空间方向移动一段与对象大小相等的距离;

    • 空闲列表

      Java堆中的内存并不规整,此时虚拟机必须维护一个列表,记录哪些内存块是可用的,在分配的时候从列表上找到一块足够大的空间划分给对象实例,并更新列表上的记录。

      并发情况下,在堆上分配内存,可能出现内存分配的同步问题,解决方案有两个,一个就是同步内存(CAS)分配动作;另一个就是采用TLAB(本地线程分配缓冲),即在Java堆中针对每个线程先预先分配一小块内存。这样当线程需要分配内存时就在自己的TLAB上进行,从而避免同步的开销。但是当TLAB分配满重新分配TLAB时仍需要同步。在内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。

    • 虚拟机将要对对象进行必要的设置,如这个对象是哪个类的实例,如何找到类的元数据信息、对象的hashCode和GC分代年龄等信息。这些信息存放在对象的对象头中。

最后执行init方法,从而产生真正可用的对象。

2.2 对象的内存布局

在HotSpot虚拟机中,对象在内存的存储的布局可分为3块区域:对象头、实例数据、对齐填充。

    • 对象头
      • Mark Word

          存储对象自身的运行时数据,如hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;

      • 类型指针

          对象指向它的类元数据的指针,虚拟机通过这个指针确定这个对象是哪个类的实例。

    • 实例数据

      程序代码中所定义的各种类型的字段内容,包括从父类继承下来的。

    • 对齐填充

      并不是必然存在,仅仅起着占位符作用,以满足HotSpot VM 自动内存管理系统对对象起始地址必须是8字节的整数倍要求。

2.3 对象的访问定位

  Java程序通过栈上的reference数据来操作堆上的具体对象。主流的访问方式:

    * 句柄

      Java堆中将划分一块内存用作句柄池,reference中存储的就是对象的句柄地址,而句柄包含了对象实例数据与类型数据各自的具体地址信息。

      优点:当对象移动时,只需要改变句柄的指针即可,而相应的reference则不需要做变动;

  • 直接指针

  reference存储的是对象地址,通过reference就能访问数据。

  优点:访问速度快,相对句柄的方式而言少了一次指针定位的开销。HotSpot VM使用的是此种方式。

  

参考文献

深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)

JVM自动内存管理-Java内存区域与内存溢出异常的更多相关文章

  1. ios内存管理2-对象之间的内存管理

    同之前一样,新建一个基于命令行的工程,在新建一个Student类和一个Book类 编写如下代码: Student.h // // Student.h // 内存管理2-对象之间的内存管理 // // ...

  2. 从内存管理原理,窥探OS内存管理机制

    摘要:本文将从最简单的内存管理原理说起,带大家一起窥探OS的内存管理机制,由此熟悉底层的内存管理机制,写出高效的应用程序. 本文分享自华为云社区<探索OS的内存管理原理>,作者:元闰子 . ...

  3. C++内存管理5-处理new分配内存失败情况(转)

    C++内存管理5-处理new分配内存失败情况(转) endl; 参考博客: https://www.cnblogs.com/findumars/p/9905195.html

  4. JVM内存管理------JAVA语言的内存管理概述

    引言 内存管理一直是JAVA语言自豪与骄傲的资本,它让JAVA程序员基本上可以彻底忽略与内存管理相关的细节,只专注于业务逻辑.不过世界上不存在十全十美的好事,在带来了便利的同时,也因此引入了很多令人抓 ...

  5. JVM学习笔记(一):Java虚拟机和虚拟机内存区域

    为什么Java程序需要运行在虚拟机上 因为Java在设计之初的跨平台特性,我们知道Java程序是运行在Java虚拟机上的.如果你要问为什么Java程序要运行在虚拟机上,我可以反问你几个问题. 为什么买 ...

  6. 你必须了解的java内存管理机制(二)-内存分配

    前言 在上一篇文章中,我们花了较大的篇幅去介绍了JVM的运行时数据区,并且重点介绍了栈区的结构及作用,相关内容请猛戳!在本文中,我们将主要介绍对象的创建过程及在堆中的分配方式. 相关链接(注:文章讲解 ...

  7. 内存管理 垃圾回收 C语言内存分配 垃圾回收3大算法 引用计数3个缺点

    小结: 1.垃圾回收的本质:找到并回收不再被使用的内存空间: 2.标记清除方式和复制收集方式的对比: 3.复制收集方式的局部性优点: https://en.wikipedia.org/wiki/C_( ...

  8. 死磕内存篇 --- JAVA进程和linux内存间的大小关系

    运行个JAVA 用sleep去hold住 package org.hjb.test; public class TestOnly { public static void main(String[] ...

  9. 启动期间的内存管理之build_zonelists初始化备用内存域列表zonelists--Linux内存管理(十三)

    1. 今日内容(第二阶段(二)–初始化备用内存域列表zonelists) 我们之前讲了在memblock完成之后, 内存初始化开始进入第二阶段, 第二阶段是一个漫长的过程, 它执行了一系列复杂的操作, ...

随机推荐

  1. web组件开发入门

    本文是学习慕课网阿当大话西游之WEB组件后的一个总结. 组件的分类 1 框架组件:依赖于某种框架的组件 2 定制组件:根据公司业务定制的组件 3 独立组件:不依赖框架的组件 定义和加载组件 解决css ...

  2. 关于VC++中virtual ~的含义

    我知道virtual 的虚函数定义,~CMainFrame( )是析构函数,用来释放内存.C++的继承和派生内容.所有可以被用作基类的类一般都用虚析构函数当基类对象的指针或引用调用派生类对象时,如果基 ...

  3. Windows和Linux如何使用Java代码实现关闭进程

    在用selenium做自动化测试时,由于各种不明原因,有时Chrome浏览器会出现假死的情况,也就是整个浏览器响应超时,本人脚本主要部署在Windows机器上,所以主要以Windows为主,浏览器为C ...

  4. SQL 优化经验总结34条(转)

    (1) 选择最有效率的表名顺序(只在基于规则的优化器中有效): ORACLE 的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最 ...

  5. Servlet--Servlet接口

    servlet主要数据结构 Servlet 接口:主要定义了servlet的生命周期方法 ServletConfig接口:为servlet提供了使用容器服务的若干重要对象和方法. ServletCon ...

  6. ios 判断屏幕显示是@2x还是@3x来调用字体大小

    传统font大小适配可能会根据屏幕宽度与iphone5或iphone6宽度的一个比例来适配.但如果有这样一个需求,在显示@2x图片的手机上显示一种字体,在显示@3x图片的手机上显示另一个固定大小的字体 ...

  7. 锋利的jQuery笔记

    首先分清jQuery对象和DOM对象,这两者可相互转化,如: var $cr=$("#cr"); //jquery对象 var cr=$cr[0] ; //DOM对象 var cr ...

  8. 【转】GPS定位原理

    一.距离测定原理 1.伪距测量 伪距测量是利用全球卫星定位系统进行导航定位的最基本的方法,其基本原理是:在某一瞬间利用GPS接收机同时测定至少四颗卫星的伪距,根据已知的卫星位置 和伪距观测值,采用距离 ...

  9. linux修改TCP最大连接数

    环境 操作系统: oracle-linux7.3 修改系统支持的最大TCP连接 最大tcp连接数和系统允许打开的最大文件数,用户允许打开的最大文件数,TCP网络连接可用的端口范围有关,取上述的最小值: ...

  10. PowerMockito使用详解

    一.PowerMock概述 现如今比较流行的Mock工具如jMock,EasyMock,Mockito等都有一个共同的缺点:不能mock静态.final.私有方法等.而PowerMock能够完美的弥补 ...