JVM基本框架
首先是JVM的整图结构图:
围绕JVM内存空间有三个重要的组成部分(绿色框):
- 类加载器子系统(Class类文件内部结构怎样?JVM何时加载类?如何加载类?)
- 字节码执行子引擎 (程序运行时帧栈结构?方法调用时如何支持多态?执行引擎如何工作?)
- 垃圾回收器GC(如何确定对象可回收?哪些垃圾回收方法?何时进行垃圾回收?)
另外的内容还包括:
- Javac编译器(.java源程序如何如何编译成类文件?java的语法糖?)
- 程序的并发执行
JVM内存空间
三个重要组成部分都是围绕JVM运行时的内存模型展开,因此先简单介绍这部分。
虚拟机栈:虚拟机栈描述的是方法Method执行的内存模型:每个方法被调用时都会创建一个栈帧,存放方法局部变量表、操作数栈和动态链接,方法出口地址。栈帧结构如下:
- 每个线程都有私有的栈帧空间,相互独立。
- 当前执行方法拥有顶部当前栈帧,方法的调用和返回过程就是栈帧的入栈和出栈过程。
- 局部变量表:基本数据类型和对象引用局部变量、方法参数和隐含的this指针。
- 操作数栈:JVM字节码指令集是基于操作数栈的(就像x86硬件指令集基于通用寄存器一样)。
- 虚拟机栈可通过VM参数-Xss(每个线程栈尺寸)设置栈空间大小,如果当前线程申请的栈深度大于虚拟机允许的深度,StackOverFlowError异常。
本地方法栈:与虚拟机栈类似,只是服务对象是Native方法。
- 程序计数器:当前线程代码执行指示器,每个线程独立。
- 堆:几乎所有对象实例在堆中分配空间(栈上只是堆中实例的引用),为所有线程共享。
- 堆是垃圾收集器GC的主要管理区域(在讲到GC时会有堆上空间的细分)
- JIT编辑器的逃逸技术等,使某些对象也能够在栈上分配空间。
方法区:存放被加载的类信息、常量(final类型常量,常量池)、静态变量、即时编译器编译后的本地代码。
- HotSpot JVM用持久代(Permanet Generation)来存放方法区。
- 方法去垃圾回收主要是常量池回收和类型卸载。
[运行时常量池](Runtime Constant Pool)
- 是方法区一部分,与Class文件中[常量池](Constant Pool Table)相对应,当类加载后,Class文件的[常量池]将存放在[运行时常量池]中。
- [常量池]存放编译期生成的字面量(字符串”abc”,声明为final常量等)和符号引用(类和接口全限定名、字段名、方法名等)。
对象的内存布局和创建
1)堆是对象存储区域,那么一个对象内存布局是怎样的?
下面是IBM关于对象内存的例子。
详见:http://www.slideshare.net/cnbailey/memory-efficient-java
2)虚拟机如何能够解读对象实例数据?即虚拟机如何知道对象中各字段具体偏移?
将类信息指针置于对象头内,如上图。klass pointer指向方法区内对象类型元数据。这也是HotSpot的做法。
通过句柄。
3)对象如何创建?
Point originOne = new Point(0,0);
关于对象的初始化,文章解析 Java 类和对象的初始化过程是这么讲的:
在类被装载、连接和初始化,这个类就随时都可能使用了。对象实例化和初始化是就是对象生命的起始阶段的活动,在这里我们主要讨论对象的初始化工作的相关特点。
Java 编译器在编译每个类时都会为该类至少生成一个实例初始化方法–即 “<init>()
” 方法。此方法与源代码中的每个构造方法相对应,如果类没有明确地声明任何构造方法,编译器则为该类生成一个默认的无参构造方法,这个默认的构造器仅仅调用父类的无参构造器,与此同时也会生成一个与默认构造方法对应的 “<init>()
” 方法.
通常来说,<init>()
方法内包括的代码内容大概为:调用另一个<init>()
方法;对实例变量初始化;与其对应的构造方法内的代码。
如果构造方法是明确地从调用同一个类中的另一个构造方法开始,那它对应的<init>()
方法体内包括的内容为:一个对本类的<init>()
方法的调用;对应用构造方法内的所有字节码。
如果构造方法不是通过调用自身类的其它构造方法开始,并且该对象不是 Object 对象,那<init>()
法内则包括的内容为:一个对父类<init>()
方法的调用;对实例变量初始化方法的字节码;最后是对应构造子的方法体字节码。
如果这个类是 Object,那么它的<init>()
方法则不包括对父类<init>()
方法的调用。