JVM-第二章.Java内存区域与内存溢出异常
概述
对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题
运行时数据区域
Java虚拟机在执行Java程序过程中会把它所管理的内存划分为若干个不同的数据区域。如下:
程序计数器
程序计数器(Program Counter Register):是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器
在线程切换中,为了保证线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类区域为“线程私有”内存。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
Java虚拟机栈
Java虚拟机栈(Java Virtual Machine Stacks):线程私有,它的生命周期与线程相同,虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。
Java内存去划分为堆内存(Heap)和栈内存(Stack),比较粗糙。这里的“栈”指虚拟机栈,或是指虚拟机栈中局部变量表部分。
局部变量表存放了编译期克制的各种基本数据类型和引用类型
如果线程请求的栈深度大于虚拟机所允许的深度,则将抛出StackOverflowError异常
若虚拟机栈动态扩展过程中无法申请到足够的内存,则会抛出OutOfMemoryError异常
本地方法栈
本地方法栈(Native Method Stack):与虚拟机栈发挥的作用十分相似,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务
与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常
Java堆
Java堆(Java Heap):是Java虚拟机所管理的内存中最大的一块,Java堆是被所有线程共享的一个内存区域,在虚拟机启动创建时,此内存的唯一目的就是存放实例对象,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的主要区域,被称为“GC堆”
采用分代收集算法:新生代和老年代
通过-Xmx和-Xms控制
方法区
方法区(Method Area):与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。也称为Non-Heap“非堆”,目的是与Java堆区分开来。
使用“永久代”来实现方法区
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
运行时常量池
运行时常量池(Runtime Constant Pool):是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述信息之外,还有一项信息是常量池,用于存放编译时期生成的各种字面量和符号引用,这部分在类加载后进入方法区的运行时常量池存放。
动态性:运行期间也可能将新的常量放入池中。
运行时常量池是方法区的一部分,故当常量池无法申请到内存时会抛出OutOfMemoryError异常
直接内存
直接内存(Direct Memory):并不是虚拟机运行时数据区的一部分也不是Java虚拟机规范中定义的内存区域,但是这部分被频繁的使用,也可能导致OutOfMemoryError异常
NIO类可以使用Native直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
对象的创建
① 当虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
② 在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来(使用“指针碰撞”和“空闲列表”两种方式)
③ 内存分配完成后,虚拟机需要将分配到的内存空间都进行初始化为零值(不包括对象头)
④ 对对象进行必要的设置,包括这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息。这些信息存放在对象头(Object Header)之中。
⑤ 从虚拟机视角看,一个新的对象已经产生。从Java视角看,对象创建才刚刚开始,<init>方法
对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局可以划分为3块区域:对象头(Header),实例数据(Instance Date)和对齐补充(Padding)
对象头:包括两部分信息:用于存储对象自身的运行时数据;类型指针
Java堆溢出
Java堆用于存储对象实例,只要不断创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常
限制Java堆的大小为20MB,不可扩展(将堆的最小值-Xms参数与最大值-Xmx参数设置为一样的即可避免堆自动扩展),通过参数-XX:+HeapDumpOnOutOfMemeryError可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析。
[code]package jvm; import java.util.ArrayList; import java.util.List; /** Java堆溢出 * VM args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * @author Jiayuan * @version 1.0 * @description: * @time 8/24/2018 10:26 PM */ public class HeapOOM { static class OOMObject{ } public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); int i = 0; while(true){ list.add(new OOMObject()); System.out.println(i++); } } }
运行结果:
虚拟机栈和本地方法栈溢出
如果线程请求的栈深度大于虚拟机栈所允许的最大深度,将抛出StackOverflowError异常
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
方式:使用-Xss参数减少栈内存容量;定义大量的本地变量
[code]package jvm; /**栈溢出 * @author Jiayuan * @version 1.0 * @description: * @time 8/24/2018 10:36 PM */ public class JavaVMStackSOF { private int stackLength = 1; public void stackLeak(){ stackLength++; stackLeak(); } public static void main(String[] args) { JavaVMStackSOF oom = new JavaVMStackSOF(); try{ oom.stackLeak(); }catch(Throwable e){ System.out.println("stack length:"+oom.stackLength); e.printStackTrace(); } } }
运行结果:
方法区和运行时常量溢出
通过-XX:PermSize和-XX:MaxPermSize限制方法区大小
本机直接内存溢出
[code]package jvm; import sun.misc.Unsafe; import java.lang.reflect.Field; /** 使用unsafe分配本机内存 * -Xmx20M -XX:MaxDirectMemorySize=10M * @author Jiayuan * @version 1.0 * @description: * @time 8/24/2018 10:47 PM */ public class DirectMemoryOOM { private static final int _1MB = 1024*1024; public static void main(String[] args) throws Exception { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe)unsafeField.get(null); while(true){ unsafe.allocateMemory(_1MB); } } }
运行结果:
[code]参考《深入理解Java虚拟机》-周志明著阅读更多
- 深入理解JVM—第二章:Java内存区域与内存溢出异常
- 【深入理解JVM虚拟机】第2章 java内存区域与内存溢出异常
- JVM 学习笔记1 JAVA内存区域与溢出异常
- Java基础--jvm(内存区域与内存溢出异常--运行时数据区域)
- 深入理解JVM虚拟机 第二章笔记 Java内存区域与内存溢出异常
- jvm-----java内存区域及内存溢出异常
- (JVM1)Java内存区域与内存溢出异常之二
- 深入理解Java虚拟机JVM高级特性与最佳实践阅读总结——第二章 Java内存区域与内存溢出异常
- java---《深入理解java虚拟机》第二章【java内存区域与内存溢出异常】阅读笔记
- JVM-java内存区域与内存溢出异常
- 第二章(java内存区域与内存溢出异常)
- jvm(1)----java内存区域与内存溢出异常
- Android 精进之简述 JVM 基础(二):Java内存区域与内存溢出异常
- Java内存区域与内存溢出异常与Eclipse运行速度调优
- 深入java虚拟机 1 java内存区域与内存溢出的异常
- java内存区域与内存异常(jvm学习)
- Java内存区域与内存溢出异常-HotSpot虚拟机对象探秘
- JVM学习-java内存区域与异常
- 学习JVM之java内存区域与异常
- 第二章--java内存区域与内存溢出