一、JVM的概述
java语言的跨平台性就是靠着jvm的产生而实现的,jvm是Java Virtual Machine的缩写,所谓虚拟机就是虚拟一台计算机,在真实的计算机上仿真模拟各种计算机功能实现的。java是跨平台的,但是jvm不是,就是因为jvm根据不同平台的规范进行编写,才屏蔽了平台相关性因素。
二、虚拟机的运行时数据区
图中的各个区域就是jvm运行时使用的内存区,不同区域所属的功能不同。
1.程序计数器
线程通过程序计数器的值获取下一条要执行的代码指令,可以理解为是一个行号记录器,分支,循环,跳转,异常处理,线程切换等都是靠这个程序计数器保持正确性的,所以程序计数器是线程私有的内存,是线程间不共享的。
2.虚拟机栈
虚拟机栈是线程执行方法的地方,每一个java方法都是以栈帧的方式进入虚拟机栈,栈帧的入栈出栈就代表一个方法的开始和结束。栈结构先入后出的特点,可以让方法的嵌套更好的实现。也是线程私有的内存。
栈帧这个概念有点抽象,其实它就是一块内存空间,里面存着一些数据。都存在些什么数据呢?
局部变量表:名字一听就知道它是存放局部变量的,还有八大基本类型。
操作数栈:方法中字节码指令的入栈出栈的地方。
动态链接:在字节码文件中,都是通过符号引用的方式来调用方法的,这些符号有一些在类加载时直接变成直接引用,这叫静态解析,另外的部分在运行时才会转化为直接引用,这种叫做动态链接。
返回地址:方法成功时,返回的就是程序计数器中的指令地址,否则,就需要查找异常处理表来确认了。
3.本地方法栈
线程调用本地方法的栈结构,它是专门为调用本地方法提供的。也是线程私有的内存空间。
4.堆
堆内存是用于存放对象的,后面讲垃圾回收机制的时候再去讨论他的结构。它是被所有线程所共享的。
堆内存分配:
一种采用指针碰撞的方式来分配内存,这个分配方式有个前提条件,就是需要堆内存是规整的。还有一种是空闲列表的方式。
指针碰撞:当堆内存是规整的,所谓规整就是使用的堆内存放在一边,未使用的内存放在另一边,中间放一个指针作为分界点的指示器,那所分配的内存就是将指针向空闲空间移动一段与所存对象大小相同的距离即可,这种分配方式就是指针碰撞。
空闲列表:当堆内存不是规整的,需要虚拟机自己维护一个空闲内存的地址列表,当分配对象时,从列表中找到一块足够大的空闲内存进行分配。
由于堆内存是线程共享的,所以对象分配这个操作就有可能出现同步问题。比如线程A分配了一个空闲空间C来存储对象,但是还未更新到虚拟机维护的列表中,这是线程B也被分配了一个空闲空间C来存储对象,这样就出现了同步问题。虚拟机采取的是一种类似于乐观锁的机制,当线程A使用了空闲空间C,会在该内存空间加上锁,当线程B也被分到空闲空间C时,就会获取锁失败,请求重试,但是采用这种失败重试的方式分配对象效率很低。所以引入另一个概念:
本地线程分配缓冲:(Thread Local Allocation Buffer),缩写TLAB,为每一个线程预分配一个堆空间(并未有其他线程使用的堆空间),这个堆空间称为本地线程分配缓冲。
它会给每一个线程都预先分配一块未使用的堆内存,由于预分配的内存都是线程独立使用的,不会有线程安全问题,但是TLAB分配完时,就会采用失败重试的方式向所有空闲内存申请新的TLAB。
5.方法区
6.直接内存
直接内存:并不是虚拟机运行时区的一部分,NIO中引入了通道技术,直接使用native函数库分配堆外内存,然后在堆内存中存一个DirectByteBuffer作为这块内存的引用,这就相当于建立了一个通道,不需要再在这两块内存区域复制数据,大大节省了时间。三、对象
对象在内存中存储的内存分为3个区域:对象头(Header),实例数据(Instance Data),对齐填充(Padding)。
对象头:1.用于存贮对象自身的运行时数据。如:哈希吗(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
2.存储对象的另一部分是类型指针,即对象指向它的类元数据指针,虚拟机通过这个指针指向的数据来确定它是那个类的实例,查找对象的元数据信息不一定要经过对象本身。
注:如果对象是数组,那么对象头中还要存储数据长度数据,这是因为普通对象可以通过元数据获取对象大小,但是数组无法通过元数据获取大小。
实例数据:对象真正存储的有效信息,也就是程序代码中定义的各种类型字段内容。
对齐填充:并不是必然存在的,它仅仅起着占位符的作用,vm的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,换句话说,对象的大小必须是8字节的整数倍,而对象头部分正好是8自己的整数倍(1倍或者2倍),所以当实例数据没有对齐时,就需要通过对齐填充来进行补充。
对象的访问方式:
句柄访问:java堆中会划分出一块内存作为句柄池,reference存放的就是句柄(句柄其实就是一个指向指针的指针)地址,句柄中又存放实例数据和类型数据的具体地址信息。好处就是对象移动时,reference里的地址是不改变的,只是改变句柄里的实例数据指针即可。
直接访问:reference里直接存放的是对象地址,在对象数据中会存储相关的类型数据的指针。好处是访问快,但是每次移动对象的开销大。