题记:六年的程序员生涯,都没好好总结过,直到面试时问到基础,才发现自己好多东西都忘了,刚好公司倒闭,重新温习一下JVM,尝试着写博客,当做一些笔记吧,本文是对《深入理解JAVA虚拟机》的一个总结,目录结构基本相同,可能后期会做一些系统的整理,敬请期待吧,如果想了解更多,建议还是花点时间阅读这本书,很不错。
基础概念:
1、程序计数器(Program Counter Register) : 线程私有的小内存空间(每个线程都会有一个程序计数器),可以看做是当前程序锁执行的字节码的行号指示器,用于标记线程下一条需要执行的字节码指令。 如果是Native方法的的程序计数器,那么计数器的值为空,且是Java虚拟机中唯一没有规范任何OutOfMemoryError情况的区域。
2、Java虚拟机栈:线程私有的内存空间,描述的是Java方法执行的内存模型,且每个方法执行的同时都会创建一个栈帧(Stack Frame),用于存储局部变量表(基本数据类型和对象引用(句柄或其他对象的相关位置)),操作数栈,动态链表,方法出口等信息。当进入一个方法时,这个方法在帧中需要分配多大的局部变量空间是完全确定的,在方法运行期间不会改变。如果线程请求的栈深度大于虚拟机允许的深度,将抛出*Error异常,如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,则会抛出OutOfMemoryError异常。
3、本地方法栈:本地方法栈和虚拟机栈十分相似,只不过虚拟机栈为Java方法服务,本地方法为Native方法服务。有些虚拟机(Sun HotSpot虚拟机)直接把本地方法栈和Java虚拟机栈合二为一。于虚拟机栈一样同样会抛出*Error和OutOfMemoryError异常。
4、Java堆:共享内存区域,用于存放对象实例,所有的对象实例和数组都存放在Java堆中。Java堆是垃圾回收管理器的主要区域,因此也可以称为“GC堆(Garbage Collected Heap)”,采用分代收集算法,可以细分为:新生代和老年代。在细致一点可分为Eden空间,From Survivor空间和To Survivor空间等。Java堆中可以划分多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。Java堆可以处于不同的物理空间中,只要逻辑连续就行了。
5、方法区(Non-Heap):为堆的一个逻辑部分,所以也是共享的,它用于存放已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
6、运行时常量池(Runtime Constant Pool):方法区的一部分,用于存放编译期生成的各种字面和符号引用,这部分内容将在类加载后进入方法去的运行时常量池中存放。
7、直接内存(Direct Memory):直接内存不收Java堆大小的限制,但是受设备内存限制。
HotSpot虚拟机对象总结:
1、对象创建: 当程序调用new关键字后,JVM会进行以下操作:
a、查询常量池,是否为类的符号引用和检查这个符号引用代表的类是否已经加载,解析和初始化过
b、通过检查后,虚拟机为新生代对象分配内存,内存大小在类加载完后可以完全确定,进行内存分配:
内存分配方式:指针碰撞(Bump the Pointer)和空闲列表(Free List),同时采用同步和本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)的方式进行内存分配
c、对对象进行必要的设置,例如对这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码、对象的GC分代年龄等信息,这些信息存放在对象头(Object Header)中。
d、执行init方法,初始化对象。
2、对象的内存布局
HotSpot虚拟机的对象头包括两部分,第一部分用于存储对象自身的运行时数据(如:哈希码,GC分带年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等),这部分在32位和64位的虚拟机中分别为32bit和64bit,第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例(如果对象是数组,还必须保存数组长度)。
3、对象的定位访问
建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。对象访问方式取决于虚拟机视线,目前主流的访问方式有句柄和直接指针两种。