Java内存区域和内存溢出异常
前言
对于java程序员来说,在虚拟机自动内存管理的机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出的问题,由虚拟机管理内存,这一切看起来都很美好。不过,也正是因为java程序员把内存控制的权利交给了java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会成为一项艰难的工作。所以本篇博文主要介绍java虚拟机内存的各个区域。
运行时数据区域
运行时数据区域就是指虚拟机在运行java程序时,把虚拟机自己管理的内存划分为若干个不同的数据区,如下图:
程序计数器
程序计数器是线程私有;
程序计数器是较小的一块内存,用于记录当前线程执行到哪一步;
如果执行的是java方法,则程序计数器记录的是正在执行的虚拟机字节码指令的地址;
如果执行的是native方法,则程序计数器为空;
此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域;
java虚拟机栈
线程私有;
描述java方法执行的内存模型,每个方法在执行时,都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链表、方法出口等信息;
每一个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中的入栈出栈的过程;
此内存区域会出现两种异常状况:
如果线程请求的栈深度大于虚拟机栈允许的深度,则抛出StackOverflowError异常;
如果虚拟机栈可动态扩展,且在扩展时无法申请到足够的内存时,则抛出OutOfMemoryError;
本地方法栈
线程私有;
与虚拟机栈基本一致,唯一不同的地方在于:虚拟机栈中执行的是java方法,而本地方法栈中执行的是native方法
java堆
线程共享;
java堆是虚拟机内存管理中最大的一块区域,虚拟机启动时创建;
几乎所有的对象实例都在堆内存中分配;
java堆可以处于物理上不连续的空间中,只要逻辑上是连续的即可;
如果堆中没有内存分配去完成实例化,且堆无法扩展时,将会抛出OutOfMemoryError;
方法区
线程共享;
用于存储虚拟机加载的类信息、常量、静态变量、字节码文件等;
当方法区无法分配内存时,则抛出OutOfMemoryError;
运行时常量池
运行时常量池是方法区的一部分;
用于存储编译时期产生的字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放;
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError;
直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。但是这部分内存被频繁使用,而且也可能导致OutOfMemoryError;
JDK1.4中新加入的NIO(New Input/Output)类,引入一种基于通道(Channel)与缓冲区(buffer)的I/O方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在java堆中DirectByteBuffer对象作为这块内存的引用进行操作;
直接内存不会受java堆大小的限制,但是,受本机总内存大小以及处理器寻址空间的限制;
服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各区域内存总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError;
HotSpot虚拟机中对象
对象的创建
检查类加载是否完成:虚拟机遇到new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引号,并且检查这个符号引用代表的类是否已被虚拟机加载、解析、初始化过,如果没有,那必须先执行相应的类加载过程;
为新生对象分配内存:
- 为对象分配内存,相当于把一块确定大小的内存从java堆中划分出来。假设java堆内存时规整的,所有已用内存一边,未用内存一边,中间放置一个指针作为分界点指示器,那分配内存就是指针想空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”;如果java堆不规整,已使用和未使用内存相互交错,这时就不能指针碰撞来分配内存,而是需要维护一张列表,记录哪些内存可用,在分配时从列表中找出一块足够大的空间划分给对象实例,并更新列表的记录,这种分配方式叫做“空闲列表”,至于选择哪一种分配方式,取决于java堆内存是否规整,而这又取决于所采用的垃圾收集器是否带有压缩整理功能决定的。
- 解决分配内存时的并发问题:一种方法是对分配内存的动作进行同步处理——实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;另一种是把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓存(Thread Local Allocation Buffer,TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来决定
初始化对象分配到的内存空间
虚拟机设置对象:例如对象是哪个类的实例、如何找到类的元数据信息、对象的额哈希码、对象的GC分代年龄等信息;
对象的内存形态
对象在内存中的布局可以分为三个部分:对象头、实例数据、对齐填充
对象头:对象头包含两部分数据——
- 第一部分用于存储对象运行时的数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别位32bit和64bit,官方称为“Mark Word”。
- 另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据:对象真正存储的有效信息,也是程序中定义的各种类型的字段内容;
对齐填充:不是必然存在的,也没有特别的含义,他仅仅起着占位符的作用。由于HotSpot VM 的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象大小必须是8字节的倍数。而对象头部分正好是8字节的整数倍,因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全;
对象的访问定位
java程序需要通过栈上的refrence数据来操作堆上的具体对象,由于refrence类型在java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象的访问方式也是取决于虚拟机实现而定的。目前主流的访问方式有使用句柄和直接指针两种
使用句柄
使用句柄访问的话,java堆将会划分出一块内存来作为句柄池,refrence中存放的是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息,这样做的好处是,无论对象怎么被移动(垃圾收集时经常发生移动),refrence本身不会变化,只需要修改句柄中的实例数据地址即可,如下图
直接指针访问
这种情况需要考虑的是,在java堆中对象布局如何放置对象的类型数据信息,而refrence中存储的直接就是对象的实例数据地址信息,这样做的好处是,少了一次指针定位的开销,如下图:
Java内存区域和内存溢出异常的更多相关文章
- 深入理解java虚拟机系列(一):java内存区域与内存溢出异常
文章主要是阅读<深入理解java虚拟机:JVM高级特性与最佳实践>第二章:Java内存区域与内存溢出异常 的一些笔记以及概括. 好了開始.假设有什么错误或者遗漏,欢迎指出. 一.概述 先上 ...
- 《深入理解java虚拟机》第二章 Java内存区域与内存溢出异常
第二章 Java内存区域与内存溢出异常 2.2 运行时数据区域
- 深入了解Java虚拟机(1)java内存区域与内存溢出异常
java内存区域与内存溢出异常 一.运行时数据区域 1.程序计数器:线程私有,用于存储当前所执行的指令位置 2.Java虚拟机栈:线程私有,描叙Java方法执行模型:执行方法时都会创建一个栈帧,存储局 ...
- 深入理解java虚拟机---->java内存区域与内存溢出异常
2. java内存区域于内存溢出异常 2.1 概述: 对于C/C++而言,内存管理具有最高的权利,既拥有每一个对象的“所有权”,又担负着每一个对象生命开始到结束的维护责任. 对于java而言,则把内存 ...
- 第二章Java内存区域与内存溢出异常
第二章 Java内存区域与内存溢出异常 一.概述 对与Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每个new操作去写delete/free代码,不容易出现内存泄露和内存溢出问 题, ...
- 2.1 自动内存管理机制--Java内存区域与内存溢出异常
自动内存管理机制 第二章.Java内存区域与内存溢出异常 [虚拟机中内存如何划分,以及哪部分区域.什么样代码和操作会导致内存溢出.各区域内存溢出的原因] 一.运行时数据区域 Java虚拟机所管理的内存 ...
- 虚拟机--第二章java内存区域与内存溢出异常--(抄书)
这是本人阅读周志明老师的<深入理解Java虚拟机>第二版抄写的,有很多省略,不适合直接阅读,需要阅读请出门左转淘宝,右转京东,支持周老师(侵权请联系删除) 第二章java内存区域与内存溢出 ...
- 深入理解Java虚拟机之Java内存区域与内存溢出异常
Java内存区域与内存溢出异常 运行时数据区域 程序计数器 用于记录从内存执行的下一条指令的地址,线程私有的一小块内存,也是唯一不会报出OOM异常的区域 Java虚拟机栈 Java虚拟机栈(Java ...
- 深入理解Java虚拟机之图解Java内存区域与内存溢出异常
Java内存区域与内存溢出异常 运行时数据区域 程序计数器 用于记录从内存执行的下一条指令的地址,线程私有的一小块内存,也是唯一不会报出OOM异常的区域 Java虚拟机栈 Java虚拟机栈(Java ...
- JVM内存区域与内存溢出异常
Java虚拟机在执行java程序时会把它所管理的内存会分为若干个不同的数据区域,不同的区域在内存不足时会抛出不同的异常. >>运行时数据区域的划分 (1)程序计数器程序计数器(Progra ...
随机推荐
- 【STM32H7教程】第9章 STM32H7重要知识点数据类型,变量和堆栈
完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第9章 STM32H7重要知识点数据类型,变量和堆栈 ...
- Python练习:哥德巴赫猜想
哥德巴赫猜想 哥德巴赫 1742 年给欧拉的信中哥德巴赫提出了以下猜想:任一大于 2 的偶数都可写成两个质数之和.但是哥德巴赫自己无法证明它,于是就写信请教赫赫有名的大数学家欧拉帮忙证明,但是一直到死 ...
- #Java学习之路——基础阶段二(第九篇)
我的学习阶段是跟着CZBK黑马的双源课程,学习目标以及博客是为了审查自己的学习情况,毕竟看一遍,敲一遍,和自己归纳总结一遍有着很大的区别,在此期间我会参杂Java疯狂讲义(第四版)里面的内容. 前言: ...
- HttpServletRequest 接口、HttpServletResponse 接口、请求转发与重定向
上篇文章我们讲了servlet的基本原理,这章将讲一下剩余的部分. HttpServletRequest 接口 该接口是 ServletRequest 接口的子接口,封装了 HTTP 请求的相关信息, ...
- 【Android Studio安装部署系列】目录
概述 从刚开始使用Android Studio到现在,下面所有目录下的操作,当时习惯性的把每一个整理成一个文档(其实就是简单文字描述+截图):有些地方当时是一知半解,现在会稍微明白一些.正好赶上现在有 ...
- Netty基础系列(2) --彻底理解阻塞非阻塞与同步异步的区别
引言 在进行I/O学习的时候,阻塞和非阻塞,同步和异步这几个概念常常被提及,但是很多人对这几个概念一直很模糊.要想学好Netty,这几个概念必须要掌握清楚. 同步和异步 同步与异步的区别在于,异步基于 ...
- Synchronized锁在Spring事务管理下,为啥还线程不安全?
前言 只有光头才能变强. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y 大年初二,朋友问了我一个技术的问题(朋友实在是好学, ...
- Docker最全教程之使用Docker搭建Java开发环境(十七)
前言 Java是一门面向对象的优秀编程语言,市场占有率极高,但是在容器化实践过程中,发现官方支持并不友好,同时与其他编程语言的基础镜像相比(具体见各语言镜像比较),确实是非常臃肿. 本篇仅作探索,希望 ...
- jQuery --- 第四期 (jQuery动效)
学习笔记 1.jQuery动画的淡入淡出 <!doctype html> <html> <head> <meta charset="utf-8&qu ...
- SAP MM 预留单据的历史修改记录?
SAP MM 预留单据的历史修改记录? 在笔者眼里,SAP系统是一个高度严谨的软件系统.用户在SAP系统里的相关操作,系统都会做记录.用户对于系统的相关单据的增删改,SAP系统都有保留change h ...