JVM运行时数据区域
JVM运行时数据区域被分为
堆(heap)、方法区(non-heap)、虚拟机栈、本地方法栈、程序计数器
- 堆用来存放对象实例
- 方法区在HotSpot VM中被实现在堆中,作为永久代,存放常量、静态变量和类型信息等等
堆和方法区被所有线程共享,以下几个区都是线程独有的 - 虚拟机栈当java方法被调用时,存放方法的调用信息。
- 本地方法栈当本地方法被调用时,存放本地方法的调用信息。
虚拟机栈和本地方法栈在HotSpot VM中被实现在了一起。每一个方法调用时,都会创建
一个栈帧,放入栈顶。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等。栈
帧的大小可以通过设置VM参数调整。 - 程序计数器用来记录线程执行到字节码的哪一行了。每一个线程执行到哪里都不一样,所
以程序计数器需要线程独有。
以上的分区只有程序计数器中不可能出现OOM异常(OutOfMemoryError),下面分析出现OOM的情况。
堆:
- 直接内存(Direct Memory)不是JVM运行时数据库的一部分,没有被JVM规范定义,但在NIO中引入了基于通道(Channel)和缓存(Buffer)的I/O方式,会使用native方法创建堆外内存,所以使用也比较频繁。我们把堆外内存叫做直接内存。分配直接内存时候,DirectByteBuffer()和Unsafe类的allocateMemory()方法有区别。
当堆外内存和JVM运行时分配的内存总和大于计算机能够分配的内存时,会出现OOM。 - 堆在动态扩展时,达到了-Xmm(最小为-Xms)设置可扩展内存上限,仍然无法完成实例分配时,也就是仍然不够用时,会出现OOM。
- 如果在VM中设置-Xmm和-Xms参数值相同,那么堆将会是不可动态扩展的,当堆内存不够用时,会出现OOM。
PS:大部分参数设置中,若设置为上限和下限相同,那么该模块不能扩展。
- 直接内存(Direct Memory)不是JVM运行时数据库的一部分,没有被JVM规范定义,但在NIO中引入了基于通道(Channel)和缓存(Buffer)的I/O方式,会使用native方法创建堆外内存,所以使用也比较频繁。我们把堆外内存叫做直接内存。分配直接内存时候,DirectByteBuffer()和Unsafe类的allocateMemory()方法有区别。
方法区:
- 方法区中存放Class和常量,试图通过创建常量来造成OOM显然不科学。而向方法区中新增.class经常会遇到,比如Spring等框架对类进行增强,动态生成大量的Class放入方法区中。
虚拟机栈和本地方法栈
- 通过实验发现,单线程情况下,无论是栈帧太大或是虚拟机栈容量太小,当内存无法分配时,都会抛出StackOverFlowException。
- 多线程情况下,当每一个栈帧过大时,更容易引起OOM。
不同的环境和虚拟机可能实验的结果不同。对于StackOverFlowException和OOM,无论是栈太小还是内存太小,本质上都是对用一件事情的不同描述。
内存泄漏和内存溢出
内存泄漏(Memory Leak):GC Roots,垃圾收集器的对象。GC会收集那些不是GC Roots且没有被GC Roots引用的对象。如果发生内存泄漏,可以使用工具找到泄漏对象到GC Roots的引用链,找出与GC Roots关联的路径,确定类型信息和引用链信息后定位出泄露的位置,然后解决问题。
内存溢出(Memory overflow):发生内存溢出,应该去代码中排查生存周期过长的对象或占用资源过多的对象,增大内存,从代码中优化。
对象的初始化过程
- 从常量池中取出这个类的符号引用,然后再检查这个类是否已经被加载、解析和初始化过,如果没有就做类加载(关于类加载参见第七章)。
- 为对象准备内存。在堆去中为对象分配内存,有两种方式——指针碰撞(Bump the pointer)和空闲列表(Free List),不同收集器算法才用的方式不同。实例域会被附初值。
- 在对象头中记录对象的哈希码,对象分代年龄等,一般是16位或者32位。
从虚拟机的角度看,一个对象的初始化已经完成了,但从java的角度看,还没有调用init方法,这个对象还不可用。 - 调用init方法,完成对象的初始化。
对象及其访问定位
对象分为对象头、实例数据、对象填充三个部分。实例数据才是“有效信息”,包括从父类继承的各种数据。对象填充不是必要的,HotSpot VM规定对象的大小要是8的整数倍,所以它相当于占位符的作用。
对象通过本地变量表里的reference访问,有两种方式,第一种是通过句柄,reference记录该对象的句柄,通过句柄查找该对象的放在方法区常量池中的类型数据和放在堆中的实例数据;第二种是通过对象的引用,直接指向该对象的实例数据,再通过放在其中的指向方法区的指针,访问类型数据。
句柄的优点是:
* reference不用修改,只需要修改句柄的内容。
缺点是:
* 效率比较低。
引用的优点是:
* 直接指向对象,访问效率比较高。HotSpot VM使用的是这种方法。
缺点是:
* 在堆中的对象经常因为垃圾回收而移动,需要频繁的修改reference的值。