Java内存区域与常见内存溢出异常
目录
1.运行时数据区域
1.1 程序计数器
特征:线程私有,唯一一个没有outofmemoryerror的区域。
作用:字节码解释器运行时通过改变计数器的值来选取下一条需要执行的字节码指令
1.2 堆/heap
结构:新生代、老年代;再细致一点的有Eden区、From Survivor、To Survivor空间等。另TLAB的作用和如何使用?
作用:存放对象实例
1.3 栈/Java虚拟机栈
特征:线程私有,生命周期同线程。
作用:每个方法都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口。方法开始则压栈,方法结束则出栈。局部变量表存放了基本数据类型、对象引用(指向代表对象的句柄,或者指向对象起始地址的引用指针)、returnAddress类型(指向一条字节码指令的地址)。
1.4 本地方法栈
同java虚拟机栈,给native方法使用的。
1.5 方法区
作用:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
实现:Hotspot使用永久代来实现方法区,方便垃圾收集器同样作用于方法区。JDK1.7之后引入Mata Space代替永久代。
1.5.1 运行时常量池
属于方法区的一部分,并非所有常量都是随类编译产生的,可以通过String类的intern()方法增加常量。
1.6 直接内存
常见的使用场景:java NIO(New Input/Output)。引入了一种基于通道和缓冲区的I/O方式。它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
2.虚拟机对象
2.1 创建对象
步骤:检查类加载;分配内存;
内存分配的方式有:指针碰撞、空闲列表。选择何种分配方式由垃圾回收机制决定,前者配合Serial、ParNew等待Compact过程的收集器,后者配合基于Mark-Sweep算法的CMS收集器。
保证分配过程的线程安全问题:使用cas,使用本地线程分配缓冲TLAB。
2.2 对象的内存布局
对象头、实例数据、对齐填充。待补充
2.3 对象的访问定位
直接指针、句柄,各自优缺点:前者访问速度快,少一次指针寻址的过程。后者栈中reference存储的是稳定的句柄地址,在gc过程中对象实例数据的地址会发生改变,只需要移动句柄中指向实例数据的指针即可。Hotspot采用前者实现方式。
3.OutOfMemorryError异常
堆溢出、方法栈和本地方法栈溢出(单线程下的表现,多线程的表现)、方法区和常量池溢出(由String.intern()方法动态添加常量,并且由此引申出一个问题,详细见https://blog.csdn.net/u013215289/article/details/51260589)、本机直接内存溢出(由-XX:MaxDirectMemorySize指定,默认是最大堆内存。)