熟悉JVM内存区域
Java运行时数据区
如下内容如未说明均以JDK1.7版本为准
JVM内存主要分为5个部分:
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 堆
- 方法区
而JDK1.8和JDK1.7比,主要是将方法区(永久代) 被 元数据区取代,而且不再在在虚拟机内存中,而是使用的本地内存。
程序计数器
- 程序计数器是一块较小内存空间,可以看做是当前线程的行号指示器,是线程私有的。
- 字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令,来完成分支、循环、跳转、异常处理、线程恢复等基础功能。
- 当前线程正在执行Java方法时,计数器记录的是虚拟机字节码指令地址;正在执行的是Native方法时,计数器值为空(Undefined)。
- 唯一一个没有任何OOM异常的区域。
虚拟机栈
Java是以方法做为最基本的执行单元,而栈帧则是虚拟机进行方法调用和执行的数据结构,也是虚拟机栈中的元素。
每一个栈帧都包含局部变量表、操作数栈、动态连接、返回地址和一些其他附加信息。
- 会有StackOverflowError和OutOfMemoryError异常抛出。
本地方法栈
本地方法栈和虚拟机栈作用类似,只不过一个服务于本地方法(native的c实现的),一个是Java方法(字节码方法)。 在HotSpot虚拟机中,直接将虚拟机栈和本地方法栈合二为一。
- 和虚拟机栈一样,会有StackOverflowError和OutOfMemoryError异常抛出。
堆
- Java堆是虚拟机中最大的一块区域,是线程共享的,按垃圾的分代收集算法细分为老年代和新生代,新生代又分为Eden区和幸存区,幸存区又分为From和to区。
- 线程本地缓存区(TLAB)位于Eden区,是虚拟机保证对象的内存分配的线程安全的技术。每个线程都会有一个很小的TLAB空间,对象分配内存空间时,优先在自己的TLAB中分配,再在老年代中分配。所以堆里的TLAB区域是线程独享的。
- 所有对象实例及数组都要在堆上分配,除一些优化情况外(JIT编译器优化和逃逸技术,出现栈上分配、标量替换优化)。
- 当内存不足时,会抛出OutOfMemoryError异常。
方法区
- 虚拟机规范中定义方法区是堆的一个逻辑部分,不需要连续内存或者可扩展外,还可以不实现垃圾回收。
- 方法区用于存放已加载的类信息、常量、静态变量和即时编译器编译后的代码。
- 方法区内的回收主要针对常量池的回收和类的卸载。
- 运行时常量池是方法区的一部分,存放类加载后class文件的常量信息以及String.inter()产生的新的常量。
- 如果方法区无法满足新的内存分配需求时,将抛出 OutOfMemoryError异常。
直接内存
- 直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。
- JDK1.4之后新加入了NIO,可以通过DirectByteBuffer类来创建和使用堆外内存,因为避免了在Java堆和Native堆中来回复制数据,从而显著提高性能。
- 虽然是堆外内存,但是也会受到物理内存大小的限制,所以在动态扩展时可能会出现OOM异常。
参考资料
- 周志明 * 《深入理解Java虚拟机》 第二版和第三版