《深入理解java虚拟机》读书笔记:java内存区域

来源:互联网 发布:旅行社地接计调软件 编辑:程序博客网 时间:2024/06/05 04:24

书本简介

书名:《深入理解java虚拟机 JVM高级特性与最佳实践》

作者:周志明

版本:第二版

pdf下载链接:csdn下载该电子书


运行时数据区域

关键词:运行时

即在从java虚拟机(JVM)启动到其停止的一段时间内,JVM用来存放必要数据(类信息,对象,常量等等)

画了这么一副图:



程序计数器(Program Counter Register)

与相应线程关联的一个较小的内存空间,用作存放程序下一条指定的位置。属于线程私有的空间,每个线程都需要一个独立的程序计数器。


java虚拟机栈(Java Virtual Machine Stack)

虚拟机栈描述的是 : java方法执行的内存模型(书中原话),我对这句话的理解是:

虚拟机栈,故名思意,它是一个栈结构(先进后出),它里面的元素是一个个的栈桢(Stack Frame),栈桢中存放的是一个方法中的运行必要信息(局部变量表,操作数栈,动态链接,方法出口等)。

想想方法的执行过程:在一个线程中,方法的执行是从上到下执行每一行代码(在虚拟机中是字节码),当遇到一个方法时,将会进入这个方法,执行这个方法的代码,当方法结束时,回到本方法的调用方法的调用点的下一行代码(即程序计数器指向这行代码)。看下面的代码:

public class Test {public static void main(){System.out.println("main:Hello World"); // 1                print();                                // 2                                                        // 3}public static void print(){System.out.println("print:Hello World"); //4}}

JVM加载完Test类后,执行main方法。从方法开头向下执行 , 从 1 到 2 ,2 是一个print()方法,进入这个方法执行4,这个方法执行完之后返回到 3 。

java虚拟机栈中会有两个栈桢进出,分别代表main方法和print方法。


上图,表示了上面示例代码虚拟机栈中栈桢的进出情况。橙色代表栈顶栈桢(正在执行的方法)。


方法区(Method Area)

方法区用于存放已被虚拟机加载的类的类信息,常量,静态变量(类变量)等数据。


运行时常量池(Runtime Constant Pool)

运行时常量池是方法区的一部分,是存放各种字面值常量和符号引用的内存区域。


Java堆

主要用于存放对象实例和数组对象的内存区域。在虚拟机启动时创建(但是书中没有提到,方法区是不是也是这个时候启动,而讲了方法区被实现为“永久代”)


对象的创建

public class Student {

private String name;

private char gender;

public Student(String name,char gender){

this.name = name;

this.gender = gender;

}

}

public class Main{public static void main(String[] args){Student student = new Student("Paul",'m');}}

以上代码创建了一个Student类的实例。但在java虚拟机的内部是怎样的一个过程呢?

使用一个命令将Main中的main方法的字节码(可以看懂的形式,并不是01代码串)得到。

javap -verbose Main


Code:      stack=4, locals=2, args_size=1         0: new           #2                  // class Student         3: dup         4: ldc           #3                  // String Paul         6: bipush        109         8: invokespecial #4                  // Method Student."<init>":(Ljava/lang/String;C)V        11: astore_1        12: return

抓主要矛盾,创建对象实例的关键是new指令(这里的new是java字节码中的new)

  1. 检查其参数#2(代表常量池中的2号常量,这里是一个类常量,代表Student)在常量池中是否可以找到对应的常量
  2. 检查该对应类是否已经被加载解析初始化过,如果没有则需要先进行这些操作
  3. 按照对象的大小(在类加载完成后可以确定)从堆中划分出一块内存空间
  4. 设置对象的一些必要信息(如对象是哪个类的实例,如何找到类的元信息,对象哈希码等信息),这些信息保存在对象的对象头(Object Header)中
  5. 将新开辟的这块空间(也就是这个对象)的首地址压入操作数栈的栈顶

下面看一看详细每一个字节码都干了什么

 字节码 参数 隐含参数 目标 备注 dup  操作数栈顶的一个元素 操作数栈顶将操作数栈顶的元素复制一份再将其压入栈顶(不知道是什么作用) ldc#3 常量池中的第三个常量  操作数栈顶将#3这个常量的值压入栈顶,即常量Paul bipush109 代表字符'm'  操作数栈顶将109压入栈顶 invokespecial#4 常量池中的第四个常量 栈顶的三个元素(这里是三个,对象地址,第一个参数Paul,第二个'm') 操作数栈顶 执行该对象的<init>方法进行初始化,把初始化后的对象地址压入栈中 astore_1  栈顶元素,即已经初始化的对象的地址 局部变量表将栈顶元素保存到本地变量表的Slot 1(理解为第一个空间)中


画了个图来帮助理解new指令


图中3 , 4 类加载如果方法区中已经有Student类信息的话会跳过。


对象的内存布局

对象在内存中的存储布局可以分为三个部分:

  1. 对象头(Header)
  2. 实例数据(Instance Data)
  3. 对齐填充(Padding)

对象头中又包含两个部分:第一部分:对象自身的运行时数据(Hash Code,GC分代年龄,锁状态标志等数据),第二部分:指向方法区中类元数据地址的指针。

实例数据是程序员所用到的对象数据。包括该类本身的数据和从父类继承的数据

对齐填充:一个java对象实例的大小必须是8字节的整数倍,不够的就要进行对齐填充。

对象的访问定位

如何通过栈桢的局部变量表中的引用类型变量或者对象实例成员的引用类型变量来找到对象数据以及类数据的呢?

主要可以分为两种方式

优点:访问速度快


优点:当对象实例数据被移动时,只需要修改pointer2,而不需要修改pointer1

以上就是我对这一章的笔记,有些是摘录原文的话,但更多的是对其内容的理解。


0 0
原创粉丝点击