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

1、运行时数据区域

① 程序计数器(Program Counter Register)

是一块较小的内存区域,可以看作是当前线程所执行的字节码的行号指示器

如果线程正在执行一个java方法,那么这个计数器记录的是正在执行的虚拟机字节码的指令地址

如果正在执行的是Native方法,则这个计数器的值为空(Undefined)

此区域是唯一一个在java虚拟机中没有规定任何OutOfMemoryError情况的区域

②java虚拟机栈

线程私有的,生命周期与线程相同

描述java方法执行的内存模型:每个方法执行的同时,回创建一个栈帧(Stack Frame),用于存储局部变量表、操作数、动态链接和方法出口等信息

局部变量表存放了编译期可知的各种基本数据类型、对象的引用和returnAddress类型(指向一条字节码指令的地址)

64为长度的long和double类型的数据会占用2个局部变量空间,其他的占用1个。

局部变量表所需内存空间在编译期完成分配,运行期不会改变局部变量表的大小

对虚拟机栈规定了两种异常状况:1. 如果线程请求的深度大于虚拟机所允许的深度,则抛出StackOverflowError;2. 如果栈动态扩展时无法申请到足够的内存,就会抛出OutOfMemoryError

③本地方法栈

与虚拟机栈作用相似。

虚拟机栈为虚拟机执行的java方法服务,本地方法栈是为虚拟机使用到的Native方法服务(HotSpot将两个栈合二为一)

会抛出的异常与虚拟机栈一致

④Java堆

Java Heap是java虚拟机所管理的内存中最大的一块,是被所有线程共享的,在虚拟机启动时创建

唯一目的是存放对象实例,几乎所有的对象实例都在这里分配内存

随着JIT和逃逸分析技术的发展,栈上分配、标量替换等优化技术的原因,所有的对象实例分配在堆上并不是那么绝对了‘

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

垃圾收集器大多使用分代算法,可以将Java堆分为:新生代和老年代。再细致一些可以分为Eden空间、From Survivor空间、To Survivor空间等

内存分配的角度:线程共享的Java堆可以划分出多个线程私有的分配缓冲区

目的是为了更好地进行内存回收和内存分配

java堆可以处于物理上连续的空间,也可以处于逻辑上连续的空间

可以固定大小,也可以是可扩展的,当前主流虚拟机都是按照可扩展实现的(通过-Xmx和-Xms控制)

当堆中没有内存完成实例分配,也不能扩展时,会抛出OutOfMemoryError异常

⑤方法区

与堆一样,所有线程共享的内存区域,存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

HotSpot使用永久代实现方法区,可以向管理Java堆一样管理这部分内存

使用永久代更容易遇到内存溢出的问题,永久代有-XX:MaxPermSize的上限,在J9和JRockit虚拟机中只要没有达到进程可用上限就不会出现问题

除了向Java堆一样可以不需要连续的内存,可以选择固定大小或者可扩展,还可以选择不实现垃圾收集

当方法区无法满足内存分配时,或抛出OutOfMemoryError异常

⑥运行时常量池(Runtime Constant Pool)

是方法区的一部分。

Class文件中有类的版本、字段、方法、接口等描述信息外,还有一个常量池,用于存储编译期生成的各种字面量和符号引用,类加载后进入方法区常量池中存放

一般来说,虚拟机把翻译出来的直接引用也存放在运行时常量池中

运行时常量池相对于Class文件中的常量池的另一个重要特性具备动态性:Java不要求常量一定只有编译期才能产生,并非预置入Class文件中常量池的内容才能进入运行时常量池,运行期,也可能有新的常量放入池中,这种用的比较多的是String类的intern()方法

作为内存区的一部分,会受到方法区的内存限制,当无法再申请到内存时抛出OutOfMemoryError异常

⑦直接内存

不是java虚拟机运行时数据区域的一部分,也不是虚拟机规范的定义的内存区域,但是这部分频繁使用,也可能导致OutOfMemoryError异常

2、HotSpot虚拟机对象探秘

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

①对象的创建

java对象规整,即已分配都放在一块,未分配的内存都放在一块,以一个指针作为分界点,当需要为对象分配内存时,将指针向空间区域移动对象需要内存的大小,这种分配方式为“指针碰撞(Bump the Pointer)”

java对象非规整,用一个列表来记录那些块是可用的,那些事不可用的,分配时将对象需要的内存分配给它,然后修改分配表,这种分配方式为“空闲列表(Free List)”

虚拟机分配方式由垃圾收集器是否带有压缩功能决定的

使用Serial、ParNew等带Compact过程的收集器,使用指针碰撞,使用CMS这种基于Mark-Swap算法的收集器,使用空闲列表

内存分配的同步:使用CAS和失败重试保证更新操作的原子性,还有一种是每个线程分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),虚拟机使用使用TLAB使用-XX:+/-UseTLAB

内存分配完后,虚拟机需要将分配的内存呢空间都初始化为零值(不包括对象头)。如果使用TLAB,则这一过程可以提前至TLAB分配时进行

分配后虚拟机要对对象做必要设置,设置类型的信息(属于哪个类,类的元数据信息)、对象哈希码、对象GC分代年龄,存放在对象头中。不同虚拟机设置的方式不同

②对象的内存布局

对象在内存中的布局可以分为3块区域:对象头、实例数据和对齐填充

HotSpot的对象头包括两部分信息:

(1) 用于存储对象自身运行时数据:哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,在32为和64为虚拟机中分别为32bit和64bit,称为Mark Word,由于此部分存储的是与对象自定义数据无关的信息,Mark Word 被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。

(2) 对象头的另一部分是类型指针(Klass Pointer),指向它的类元数据指针,通过该指针确定是哪个类的实例。并不是所有虚拟机必须在对象上保留类型指针,对象的元数据信息不一定经过对象本身。

如果是Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通 Java 对象的元数据信息确定 Java 对象的大小,但是从数组的元数据中无法确定数组的大小。

对象真正的存储的有效信息,程序中定义的各种字段(继承自父类和子类自定义的都需要记录)

分配的顺序会受虚拟机分配策略参数(FieldsAllocatationStyle)和字段定义的顺序影响

HotSpot的默认分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),相同字段总是被分配到一起

满足上述分配情况下,父类中定义的变量会出现在子类之前。如果CompactFields参数值为true(默认为true),那子类之中较窄的变量也可能会插入到父类变量的空隙之中。

第三部分对齐不是必然存在的,仅仅作为占位符

由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。对象头部分正好似8字节的倍数(1倍或者2倍),因此当对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

③对象的访问定位

取决于虚拟机的实现

(1)使用句柄访问,java堆中会分出一块内存,作为句柄池。reference中存储的是对象句柄,对象句柄中包含了对象实例数据与类型数据这两个的具体地址。

(2)使用直接指针访问,java堆的对象布局中必须考虑如何访问访问类型数据的相关信息,refere中存储的对象地址

两种对象访问方式优点

  • 使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
  • 使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销

虚拟机HotSpot而言,它是使用第二种方式进行对象访问。

4 OUtOfMemoryError异常

除程序计数器外,java虚拟机其他的运行时数据区域都可能发生该异常(简称OOM)。

①Java堆溢出

控制堆不可扩展:设置堆的最小值-Xms参数与最大值-Xmx参数为一样的值

通过参数-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析。

②虚拟机栈和本地方法栈溢出

HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此,对于HotSpot来说,虽然-Xoss参数(设置本地方法栈大小)存在,但实际上是无效的,栈容量只由-Xss参数设定。

在Java虚拟机规范中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

在单线程中,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。使用-Xss参数减少栈内容容量和定义大量本地变量增大本地变量表长度均抛出StackOverflowError异常。

在不断创建线程的情况下,每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽。

建立过多线程导致的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。

在Windows平台的虚拟机中,Java的线程是映射到操作系统的内核线程上的

③方法区和运行时常量

在JDK 1.6及之前的版本中,由于常量池分配在永久代内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区大小,从而间接限制其中常量池的容量

String.intern()返回引用的测试

  1. public class RuntimeConstantPoolOOM {
  2. public static void main(String[] args) {
  3. public static void main(String[] args) {
  4. String str1 = new StringBuilder("计算机").append("软件").toString();
  5. System.out.println(str1.intern() == str1);
  6. String str2 = new StringBuilder("ja").append("va").toString();
  7. System.out.println(str2.intern() == str2);
  8. } }
  9. }

JDK1.6 两个false ;JDK1.7 ture和false

在JDK 1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用,而由StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用,将返回false。而JDK 1.7(以及部分其他虚拟机,例如JRockit)的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。

对str2比较返回false是因为“java”这个字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用了,不符合“首次出现”的原则(JDK1.6和1.7均是),返回的是常量池的中引用,而“计算机软件”这个字符串则是首次出现的,因此返回true。

方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

④直接本机内存溢出

DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样。

由DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见明显的异常,如果读者发现OOM之后Dump文件很小,而程序中又直接或间接使用了NIO,那就可以考虑检查一下是不是这方面的原因。

来自为知笔记(Wiz)

时间: 2024-08-09 23:12:25

002 Java内存区域与内存溢出异常的相关文章

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

文章主要是阅读<深入理解java虚拟机:JVM高级特性与最佳实践>第二章:Java内存区域与内存溢出异常 的一些笔记以及概括. 好了开始.如果有什么错误或者遗漏,欢迎指出. 一.概述 先上一张图 这张图主要列出了Java虚拟机管理的内存的几个区域. 常有人把Java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分实际上远比这复杂,从上图就可以看出了.堆栈分法中所指的"栈"实际上只是虚拟机栈,或者说是虚拟机栈中的局部变量表部分.接下

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

java内存区域与内存溢出异常 一.运行时数据区域 1.程序计数器:线程私有,用于存储当前所执行的指令位置 2.Java虚拟机栈:线程私有,描叙Java方法执行模型:执行方法时都会创建一个栈帧,存储局部变量,基本类型变量,引用等信息 3.Java本地方法栈:线程私有,为虚拟机使用到的Native方法服务 4.Java堆:线程共享,是垃圾收集器的主要工作地方:存储对象实例等 5.方法区:线程共享:存储类信息,常量,静态变量等 运行时常量:存放编译时生成的各种字面量和符号引用 6.直接内存:机器的内

《深入理解Java虚拟机》读书笔记---第二章 Java内存区域与内存溢出异常

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来.这一章就是给大家介绍Java虚拟机内存的各个区域,讲解这些区域的作用,服务对象以及其中可能产生的问题. 1.运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域. 1.1程序计数器 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型中里,字

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

Java内存区域与内存溢出异常 概述 对于 C 和 C++程序开发的开发人员来说,在内存管理领域,程序员对内存拥有绝对的使用权,但是也要主要到正确的使用和清理内存,这就要求程序员有较高的水平. 而对于 Java 程序员来说,在虚拟机的自动内存管理机制的帮助下,不再需要为每一个 new 操作去写配对的 delete/free 代码,而且不容易出现内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切都很美好.不过,也正是因为 Java 程序员把内存控制的权力交给了 Java 虚拟机,一旦出现内存泄漏和

图解Java内存区域及内存溢出异常

图解 Java 内存区域及内存溢出异常 在阅读 <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版) >后,为了加深对 Java 内存区域的印象及理解,特意做成了思维导图. 名词解释 线程共享数据区域 直接内存 并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区.NIO 中使用 Native 函数直接分配堆外内存 方法区 Method Area 用于存放已被虚拟机加载的类信息.常量.静态变量.JIT 编译后的代码等 也称作 永久代,在这块容易遇到 OOM 问题

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

自动内存管理机制 第二章.Java内存区域与内存溢出异常 [虚拟机中内存如何划分,以及哪部分区域.什么样代码和操作会导致内存溢出.各区域内存溢出的原因] 一.运行时数据区域 Java虚拟机所管理的内存包括以下几个运行时数据区域[虚拟机内存模型]: 1.程序计数器: 可以看作是当前线程所执行的字节码的行号指示器.在虚拟机中,字节码解释器工作时就是通过程序计数器的值来选择下一条需要执行的字节码指令.Java虚拟机中多线程是通过线程轮流切换并分配处理机执行时间的方式实现的,在任何一个确定的时刻,一个处

【深入Java虚拟机】之一:Java内存区域与内存溢出

[深入Java虚拟机]之:Java内存区域与内存溢出 内存区域 Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域.Java虚拟机规范将JVM所管理的内存分为以下几个运行时数据区:程序计数器.Java虚拟机栈.本地方法栈.Java堆.方法区.下面详细阐述各数据区所存储的数据类型. 程序计数器(Program Counter Register) 一块较小的内存空间,它是当前线程所执行的字节码的行号指示器,字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的

Java内存区域的划分和异常

Java内存区域的划分和异常 运行时数据区域 JVM在运行Java程序时候会将内存划分为若干个不同的数据区域. 打开百度App,看更多美图 程序计数器 线程私有.可看作是当前线程所执行的字节码的行号指示器,字节码解释器的工作是通过改变这个计数值来读取下一条要执行的字节码指令. 多线程是通过线程轮流切换并分配处理器执行时间来实现的,任何一个时刻,一个内核只能执行一条线程中的指令.为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器.这就是一开始说的"线程私有".如果线

深入理解jvm之内存区域与内存溢出

文章目录 1. Java内存区域与内存溢出异常 1.1. 运行时数据区域 1.1.1. 程序计数器 1.1.2. java虚拟机栈 1.1.3. 本地方法栈 1.1.4. Java堆(Java Heap) 1.1.5. 方法区 1.1.6. 运行时常量池 1.1.7. 直接内存 1.2. HotSpot虚拟机 1.2.1. 对象的创建 1.2.2. 对象的访问定位 1.3. OOM异常的解决思路 1.4. 参考 Java内存区域与内存溢出异常 运行时数据区域 程序计数器 当前线程所执行的字节码的

深入理解JVM之JVM内存区域与内存分配

深入理解JVM之JVM内存区域与内存分配 在学习jvm的内存分配的时候,看到的这篇博客,该博客对jvm的内存分配总结的很好,同时也利用jvm的内存模型解释了java程序中有关参数传递的问题. 博客出处: http://www.cnblogs.com/hellocsl/p/3969768.html?utm_source=tuicool&utm_medium=referral 看了此博客后,发现应该去深入学习下jvm的内存模型,就是去认真学习下<深入理解Java虚拟机>,其内容可能会<