第二章

Java内存区域与内存溢出异常

一、概述

对与Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每个new操作去写delete/free代码,不容易出现内存泄露和内存溢出问

题,由虚拟机管理这一切看起来很美好。但是一旦出现内存泄露和内存溢出问题,如果不了解虚拟机是怎么使用内存的,那么排查错误将会成为

一项异常艰难的工作。

二、运行时数据区域

JVM所管理的内存将会包括以下几个运行时数据区域

  1. 程序计数器

定义:

程序计数器是一块较小的内存空间,它可以看作是当前线程执行的字节码的行号指示器。

进一步了解:

1在虚拟机概念模型里(仅是概念模型,各种虚拟机可能会通过更高效的方式实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要这个功能要依赖这个计数器来完成。

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

3.如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器则为空。

溢出:

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

  1. Java虚拟机栈

定义:

虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

额外的:

1.每个方法调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中。线程私有的,它的生命周期与线程相同。

2.经常有人把Java内存分为堆内存 和 栈内存,这种分法比较粗糙,Java内存区域的划分实际上远比这复杂 。这种划分方式的流行只能说明程序员最关注的、与对象内存分配关系最密切区域是这两块。其中所指的堆即是后面将写的的Java堆,而所指的"栈"就是现在讲的虚拟机栈,或者说是虚拟机栈中的局部变量表部分。

进一步理解:

局部变量表存放了编译期可知(方法运行期间不会改变局部变量表的大小)的各种基本数据类型、对象引用(后面讲对象的访问定位会具体说) 和 returnAddress类型(指向了一条字节码指令的地址)。

溢出:

  1. 线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常
  2. 如果虚拟机栈可以动态扩展(大部分VM都可以),扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

(3)本地方法栈

定义/区别:

本地方法栈与虚拟机栈所发挥的作用非常相似,它们间的区别不过是虚拟机栈执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

额外的:

        VM规范没有没有强制规定,因此虚拟机可以自由实现它,甚至有的虚拟机(如 HotSpot VM)直接就把本地方法栈和虚拟机栈合二为一

溢出:

同Java虚拟机栈。

(4)Java堆

定义:

Java虚拟机管理的内存最大的一块,是被所有线程共享的,在虚拟机启动时创建。唯一的目的就是存放对象实例。

额外的:

几乎所有对象实例都在堆上分配内存,但随着JIT的发展与逃逸分析技术逐渐成熟,渐渐所有对象都分配堆上变得不那么"绝对"了。

进一步:

Java堆是垃圾收集器管理的主要区域,因此很多时候也称"GC堆"。从内存回收角度看,由于现在收集器基本都采用分代收集算法(分代算法把 jdk1.7及以前分代 分为 新生代、老年代、永久代,jdk1.8开始把 永久代 移除),而刚好就是Java堆被分为 新生代 和 老年代,再细致一点有Eden空间、From Survivor空间、To Survior空间等。而从内存分配角度,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。

溢出:

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

(5)方法区

定义:

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

额外的:

虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但它却有一个别名叫 None-Heap(非堆),目的应该是与Java堆区分开来。

进一步:

对于习惯在HotSpot虚拟机上开发、部署的开发者来说,很多人愿意把方法区称为"永久代",仅仅是因为HotSpot使用永久代实现了方法区,因为hotspot的垃圾收集器采用GC分代算法,并把分代范围扩展到了方法区(给方法区分为"永久代"),可以节省单独写方法区的内存管理代码。

然而,现在看来不是好主意,这样会更容易遇到内存溢出,因此jdk1.7中已经把"永久代"中的字符串常量池移出了,然后在jdk1.8中甚至直接移除了"永久代",改用元空间实现方法区且元空间不在虚拟机了而是使用本地内存。

回收:

这区域内存回收目标主要是针对常量池的回收和对类型的卸载。

溢出:

如果无法满足内存分配需求时,将会抛出OutOfMemoryError异常。

三、运行时数据区域附带的讲解

(1)运行时常量池

定义/位置:

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

额外的:

Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

进一步:

运行时常量池相对于Class文件常量池的区别:

  1. Class文件常量池得符合Java虚拟机规范(Class文件每一部分都严格要求,当然也包括常量池)。运行时常量池则不作要求,由具体VM实现。
  2. 运行时常量池具备动态性。Java语言不要求常量一定只有编译期产生(也就是并非预置入Class文件中常量池的内容才能进入运行时常量池),运行期间也可以将常量加入运行时常量池。比如String的intern()方法

溢出:

运行时常量池 是 方法区的一部分。同样,如果无法满足内存分配需求时,将会抛出OutOfMemoryError异常。

(2)直接内存(堆外内存)

介绍:

直接内存 并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是也经常使用,也会发生OutOfMemoryError,所有一起在这讲。

进一步:

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

溢出:

还是受到本级总内存大小以及处理器寻址空间的限制。当各个内存区域总和大于物理内存限制从而导致动态扩展时出现OutOfMemoryError异常。

四、HotSpot VM在Java堆中的对象分配、布局和访问

(1)对象的创建(限于普通Java类,不包括数组和Class对象)

首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号所代表的类是否已经被加载、解析、和初始化过。如果没有,那必须先执行相应的类加载过程(《深入理解Java虚拟机》第七章将会讨论这个过程)

类加载检查通过后,接下来虚拟机将会为新生对象分配内存。对象所需的内存的大小在类加载之后就可完全确定(如何确定在《深入理解Java虚拟机》2.3.2节介绍)。

第二章Java内存区域与内存溢出异常的更多相关文章

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

    第二章 Java内存区域与内存溢出异常 2.2 运行时数据区域  

  2. 虚拟机--第二章java内存区域与内存溢出异常--(抄书)

    这是本人阅读周志明老师的<深入理解Java虚拟机>第二版抄写的,有很多省略,不适合直接阅读,需要阅读请出门左转淘宝,右转京东,支持周老师(侵权请联系删除) 第二章java内存区域与内存溢出 ...

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

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

  4. 深入理解java虚拟机系列(一):java内存区域与内存溢出异常

    文章主要是阅读<深入理解java虚拟机:JVM高级特性与最佳实践>第二章:Java内存区域与内存溢出异常 的一些笔记以及概括. 好了開始.假设有什么错误或者遗漏,欢迎指出. 一.概述 先上 ...

  5. 2.1 自动内存管理机制--Java内存区域与内存溢出异常

    自动内存管理机制 第二章.Java内存区域与内存溢出异常 [虚拟机中内存如何划分,以及哪部分区域.什么样代码和操作会导致内存溢出.各区域内存溢出的原因] 一.运行时数据区域 Java虚拟机所管理的内存 ...

  6. 深入理解java虚拟机---->java内存区域与内存溢出异常

    2. java内存区域于内存溢出异常 2.1 概述: 对于C/C++而言,内存管理具有最高的权利,既拥有每一个对象的“所有权”,又担负着每一个对象生命开始到结束的维护责任. 对于java而言,则把内存 ...

  7. 深入理解Java虚拟机之Java内存区域与内存溢出异常

    Java内存区域与内存溢出异常 运行时数据区域 程序计数器 用于记录从内存执行的下一条指令的地址,线程私有的一小块内存,也是唯一不会报出OOM异常的区域 Java虚拟机栈 Java虚拟机栈(Java ...

  8. 深入理解Java虚拟机之图解Java内存区域与内存溢出异常

    Java内存区域与内存溢出异常 运行时数据区域 程序计数器 用于记录从内存执行的下一条指令的地址,线程私有的一小块内存,也是唯一不会报出OOM异常的区域 Java虚拟机栈 Java虚拟机栈(Java ...

  9. 深入了解Java虚拟机(1)java内存区域与内存溢出异常

    java内存区域与内存溢出异常 一.运行时数据区域 1.程序计数器:线程私有,用于存储当前所执行的指令位置 2.Java虚拟机栈:线程私有,描叙Java方法执行模型:执行方法时都会创建一个栈帧,存储局 ...

随机推荐

  1. [币严BIZZAN区块链]数字货币交易所钱包对接之比特币(BTC)

    在币严BIZZAN开发数字货币交易所的过程中,一共有两大难点,一个是高速撮合交易引擎,另一个是钱包对接,这两者是我们团队以前没有接触过的.这个系列的文章主要介绍数字货币交易所钱包对接实现技术.第一个要 ...

  2. Linux下Mysql启动异常排查方案

    遇到Mysql启动异常问题,可以从以下几个方面依次进行问题排查: (1)如果遇到“Can't connect to local MySQL server through socket '/tmp/my ...

  3. jquery ajax到servlet出现中文乱码(utf-8编码下)

    个人遇到的该问题有两大类: 第一类很普遍,就是jsp页面编码没有规定,servlet中接收参数没有转码,response没有使用setContentType()和setCharacterEncodin ...

  4. 【学习笔记】第三章 python3核心技术与实践--Jupyter Notebook

    可能你已经知道,Python 在 14 年后的“崛起”,得益于机器学习和数学统计应用的兴起.那为什么 Python 如此适合数学统计和机器学习呢?作为“老司机”的我可以肯定地告诉你,Jupyter N ...

  5. PHPCon 2019 第七届 PHP 开发者大会总结

    往届回顾-2018:PHPCon 2018链接: https://pan.baidu.com/s/17nfrfqk9K4vwKPAsjBVW7A——提取码:rjbr 随着PHP7的诞生,兼顾了高性能和 ...

  6. SpringAop实现公共字段填充

    一.说明 项目中经常会有一些放在缓存中的公共字段需要进行填充,我们知道mybatis-plus很方便地可以实现公共字段填充.在这里我定义了一个字段填充的注解,当我们需要进行数据填充的时候只要在方法上打 ...

  7. Elastic Stack 笔记(四)Elasticsearch5.6 索引及文档管理

    博客地址:http://www.moonxy.com 一.前言 在 Elasticsearch 中,对文档进行索引等操作时,既可以通过 RESTful 接口进行操作,也可以通过 Java 也可以通过 ...

  8. 树莓派4B安装docker-compose(64位Linux)

    准备工作 树莓派4B已装好64位Linux,并且装好了19.03.1版本的Docker,具体的安装步骤请参考<树莓派4B安装64位Linux(不用显示器键盘鼠标)> 安装docker-co ...

  9. Hadoop 之 MapReduce原理

    1.什么是MapReduce 答:简而言之,就是将一个大任务分成多个小的子任务(Map),并行执行后,合并结果(Reduce).下面举一个纸牌得栗子  2.MapReduce的运行流程  3.JobT ...

  10. SpringBoot系列——ElasticSearch

    前言 本文记录安装配置ES环境,在SpringBoot项目中使用SpringData-ElasticSearch对ES进行增删改查通用操作 ElasticSearch官网:https://www.el ...