1.1概述
Java与C++有一堵由内存分配和垃圾回收技术所构成的“高墙”
1.2 运行时数据区域
1.2.1 程序计数器
程序计数器是一小块的内存空间,它可以看作是当前线程执行字节码的行号指示器。通过改变这个计数器可以去选取下一条指令
在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。每个线程都有其独立的计数器,各个线程互不干扰,称为“线程私有”的内存。
1.2.2 Java虚拟机栈
Java虚拟机栈也是私有的,它的生命周期与线程相同,每个方法在执行的时候都会创建一个栈,又称为栈帧,每个方法从调用直至执行完成的过程,就对应着一个帧栈在虚拟机入栈和出栈的过程
局部变量表存放了编译期可知的基本数据类型(byte,int 等),对象应用类型。其中double和long 64位占2个局部空间,其余32位占1个。在方法运行期间不会改变栈的大小,同时其大小决定了方法的深度
1.2.3 本地方法栈
与虚拟机栈相似,它是为虚拟机使用Native方法服务,也会抛出OOM和SOF异常
1.2.4 Java堆
对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块,它是线程共享的。几乎所有的对象实例都是在堆上分配的,随着JIT等技术的引用,栈上分配,标量替换会产生一些微妙的变化
Java堆是垃圾回收器管理的区域,所以又称GC堆,现在的垃圾回收器都是采用分代回收的算法。Java堆可以在物理上不连续,只要在逻辑上连续即可
1.2.5 方法区
方法区与Java堆一样,是各个线程共享的,它存储已经加载的类信息,常量,静态变量,即使编译器等数据
1.2.6 运行时常量池
它是方法区的一部分,它相对于Class文件常量池的另一个重要的特征是具备动态性,也就是并非预置入Class文件中才能放入方法区的常量池,运行期间也可以
它既然是方法区的一部分,那么自然受到方法区的限制,也会抛出OOM异常
1.2.7 直接内存
它不是虚拟机数据区的一部分,也不是Java虚拟机规定中的区域,在JDK1.4中引入了NIO类,一种基于通道与缓冲区的IO方式,它可以使用Native函数库直接分配堆外内存
本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存大小以及处理器寻址空间的限制
1.3 HotSpot 虚拟机
1.3.1 对象的创建
虚拟机遇到new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号代表的类是否已经加载,解析和初始化过
之后为新生对象分配内存。如果堆内存是规整的,那么分配时只是将空闲空间的指针挪动一段与对象大小相等的距离,称之为“指针碰撞”,如果不是规整的,虚拟机就要去维护一个内存使用表,这种分配方式称为“空闲列表”。所以再使用Serial,ParNew等带有Compact过程的收集器时,采用的是“指针碰撞”,而使用CMS这种基于Mark-Sweep算法时采用的是“空闲列表”。每个线程在Java堆中预先分配一小块的内存,称为本地线程分配缓冲(TLAB) 可以通过-XX:+/-UseTLAB参数来设定
接下来,虚拟机要对对象进行相应的设置,把其相关信息放入对象头中
完成上面的过程后,从虚拟机的角度来看,对象已经创建好了,但是还有一个<init>方法没有执行,通常会在new之后接着<init>,按照我们的意思去初始化
1.3.2 对象的内存布局
对象在内存中可以分为3个区域:对象头,实例数据和对齐填充。
HotSpot第一部分用于存储对象自身运行的数据,如哈希码,GC分代年龄,线程持有的锁,线程ID等数据。对象头信息是对象自身定义的数据无关额外的存储成本
对象头中另一部分是类型指针,指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,另外,如果对象是个数组的话,对象头中还包含有数组的长度,因为虚拟机虽然可以确定对象的大小,但却无法确定数组的长度
实例数据部分是对象真正有效的信息,它的存储顺序会受到虚拟机分配策略和字段在Java源码中的顺序所影响
1.3.3 对象的定位访问
我们的Java程序需要通过栈上的reference数据,但是它没有定义该用何种方式去定位,所以,对象访问取决于虚拟实现而定的
如果使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象句柄的地址,对象移动时,reference不需要改变
如果是直接访问,jav、象的布局就必须考虑如何放置访问类型数据的相关信息,而reference中的数据就是对象的地址,速度快