要想深入理解java,内存模型是必不可少的一部分。下面是学习《深入理解JAVA虚拟机》的摘要笔记。
各存储区域的介绍
java虚拟机把它所管理的内存划分为不同的数据区域,这些区域有着各自的用途以及不同生命周期。
程序计数器
- 类似于CPU的程序计数器,存放着下一条字节码的地址,如果当前在执行Native方法则程序计数器内存为空。
- 线程私有
java虚拟机栈
- java虚拟机栈是线程私有,他的生命周期与线程相同。
- 它描述了Java方法执行时的内存模型:每个栈帧都存放了方法运行时的局部变量表、操作数栈、动态链接、方法返回地址等消息。
- 每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
- 局部变量表 存放了编译期间可知的各种基本数据类型、对象引用(reference类型,不是对象,可能是指向对象起始地址的指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)
- 这个区域规定了两种异常:
§ StackOverflowError,线程请求的栈深度大于虚拟机所允许的部分。
§ OutOfMemoryError,申请不到足够的内存。
本地方法栈(Native Method Stack)
- java虚拟机栈为java方法服务,本地方法栈则为虚拟机使用的Native方法服务,同样会抛出StackOverflowError和OutOfMemoryError
JAVA堆(heap)
- 堆被所有线程共享,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。JVM规范的描述是:所有的对象实例都要在堆上分配。但随着JIT编译器的发展与逃逸技术的发展,这个特点不再那么绝对。
- heap是垃圾收集器管理的主要区域,因此很多时候也被称作“GC堆”(Garbage Collected Heap,没有翻译成“垃圾堆”23333333)。由于收集器基本采用分代收集算法,因此heap中还可以细分为新生代和老年代,再细致一点的有Eden空间、From Survivor空间、To Survivor空间。
- heap中可能划分出多个线程私有的分配缓冲区(Thead Local Allocation Buffer,TLAB)。这么做的目的是为了更好、更快的回收内存,与存储的数据无关。
- Heap可以处于物理不连续的内存空间中,逻辑连续即可。既可以实现成固定大小的,也可以是可拓展的,主流是可拓展的。
- 当heap无法拓展时会抛出OutOfMemoryError
方法区(Method Area)
- 与heap一样,是各个线程共享的内存区域,他用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 尽管Method Area描述为堆的一个逻辑部分但是它有一个别名叫做Non-heap,与heap区分开。
- 使用Hot Spot JVM的开发者,把方法区称为“永久带”(permanent Generation),但是方法区和“永久代”并不等价,仅仅是因为HotSpot选择将GC分代收集拓展至方法区,这样GC就能像管理堆一样管理这部分内存,省去专门为这块区域编写内存管理代码的工作。但这样更容易遇到内存溢出问题(永久代有-XX:MaxPermSize的上限),其它虚拟机只要没接触到进程可用上限就不会出问题。在JDK1.8中,永久代被移入Native Heap中,称为元空间(Matespace)
运行时常量池(runtime contstant pool)
- 运行时常量池的实现没有做任何细节要求,不同厂家根据自己的需求来实现这个内存区域。
- 一般来说除了保存Class文件中描述的符号引用外,翻译出的直接引用也存储在运行时常量池。
直接内存(Direct Memory)
- 直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。
- NIO(new Input/Output)类
§ 基于通道和缓冲区的I/O方式,它可以使用Native函数直接分配堆外内存,通过DirectByteBuffer对象对内存进行引用操作,避免了Java类和Natice堆直接来回复制数据。