第二章

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. Python数据库小程序

    源代码: # dict1 是 字典 , 用来对应相应元素的下标,我们将文件转成列表,对应的也就是文件的下标,通过下标来找文件元素 dict1 = {'sort':0 , 'name':1 ,'age' ...

  2. JAVA MAP转实体

    public static <T> T map2Object(Map<String, Object> map, Class<T> clazz) { SimpleDa ...

  3. Nancy获取Request.Form的所有参数

    Nancy是一个轻量级的webapi框架,在用Nancy框架的时候,我们经常要获取到接口的所有动态参数值, 但是Nancy的Request.Form不能够直接转成NameValueCollection ...

  4. C# DATETIME格式转换汇总 根据日期过期星期

    C# DateTime.Now.Year --2019(年) DateTime.Now.Month --9(月) DateTime.Now.Day   --19(日) DateTime.Now.Hou ...

  5. 简易数据分析 12 | Web Scraper 翻页——抓取分页器翻页的网页

    这是简易数据分析系列的第 12 篇文章. 前面几篇文章我们介绍了 Web Scraper 应对各种翻页的解决方法,比如说修改网页链接加载数据.点击"更多按钮"加载数据和下拉自动加载 ...

  6. Node.js之异步编程

    > 文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. ![file](https://img2018.cnblogs.com/blog/830272/20 ...

  7. charles 高亮Hosts

    本文参考:charles 高亮Hosts Focus Host是焦点域名的:这里配置好的可以在结构视图中,单独拎出来显示: 如下图,在把zhubangbang.com设为焦点域名,在视图中是下图这么展 ...

  8. 启动第二个activity,然后返回数据给第一个数据

    第一个activity启动的代码: intent = new Intent(MainActivity.this, Main2Activity.class); startActivityForResul ...

  9. django开发后台接口error 10053/10054

    初学Django,开发完接口之后访问post请求的接口遇到error10053和10054,查阅很多资料没有找到具体的原因. 在这里记录下我遇到这两个报错的原因和解决方案: get请求取请求参数:su ...

  10. 第六届蓝桥杯java b组第十题

    10.压缩变换(程序设计) 小明最近在研究压缩算法. 他知道,压缩的时候如果能够使得数值很小,就能通过熵编码得到较高的压缩比. 然而,要使数值很小是一个挑战. 最近,小明需要压缩一些正整数的序列,这些 ...