Java内存区域——JVM读书笔记

Java虚拟机运行时数据区

运行时数据区主要包括:方法区、堆、虚拟机栈、本地方法栈、程序计数器。

其中方法区和栈是线程共享的区域,另外三块区域是每个线程私有的区域。各个数据区的功能简单说明如下:

程序计数器:当前线程所执行的字节码的行号指示器。

虚拟机栈:描述Java方法执行的内存模型——每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。如果栈的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。如果栈扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常(下述的本地方法栈类同)。

本地方法栈:与虚拟机栈的作用类似。虚拟机栈为Java方法服务,本地方法栈为Native方法服务。

Java堆:在虚拟机启动的时候创建,作用是存放对象实例。通过-Xmx和-Xms控制堆的大小。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将抛出OutOfMemoryError异常。

方法区:作用是存储已被虚拟机加载的类信息、常量、静态变量等(在HotSpot中称此区域为Permanent Generation)。




对象的创建

对象的创建包括四个步骤:类加载检查、为新生对象分配内存、初始化为零值、设置对象头。

一、类加载检查

虚拟机遇到一条new指令时,收件将检查该指令的参数是否能在常量池(运行时常量池是方法区的一部分。Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。)中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

二、为新生对象分配内存

对象所需内存大小在类加载完成后便完全确定,则分配内存等同于把一块确定大小的内存从Java堆中划分开来。分两种情况:Java堆中内存是绝对规整的(称为指针碰撞,Bump the pointer)、内存不规整(称为空闲列表,Free List)。在规整的Java堆中,用过的内存放一边,空闲的内存放一边,中间放一个指针作为分界点。在不规整的Java堆中,虚拟机维护一个列表记录哪些块是可用的。选择那种分配方式由Java堆是否规整决定,而Java堆是否规整由采用的垃圾收集器是否带有压缩整理功能决定。如:在使用Serial、ParNew等带Compact过程的收集器时,采用的分配算法是指针碰撞;而使用CMS基于Mark-Sweep算法的收集器时,采用空闲列表。

三、分配内存过程需要保证线程安全

对分配内存空间的动作进行同步处理;或者是把内存分配的动作按照线程划分在不同的空间进行(即本地线程分配缓冲Thread Local Allocation Buffer)。

四、对象头的设置

说明是哪个类的实例、如何找到类的元数据信息、对象的哈希码、对象的GC年龄。


对象的访问定位

Java程序通过栈上得reference数据操作堆上得具体对象。有两种访问方式:使用句柄和直接指针。

一、使用句柄

reference中存储的是稳定的句柄地址,在对象实例数据被移动时(垃圾回收时)只会改变句柄中的实例数据指针,而reference本身不需要改变。

二、使用直接指针

速度快,因为它节省了一次指针定位的时间开销。


说明:本文的内容参考书籍《深入理解Java虚拟机(第2版)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章