Java内存区域

2018-05-16  本文已影响0人  肚皮怪_Sun

程序计数器

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高的效率的方式),字节码解释器工作时就是通过改变了这个计算器的值来选去下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确认的时刻,一个处理器都只会执行一条线程中的指令、因此为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,个线程之间计数器互不影响,独立存储,我们陈这类内存区域为“线程私有”的内存。

Java虚拟机栈

与程序计数器一样,Java虚拟机栈也是线程私有的,他的生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

本地方法栈

和虚拟机栈的作用类似,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

Java堆

Java堆是Java虚拟机所管理的内存中最大的一块,Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配。Java堆是垃圾收集管理的主要区域。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中可以细分为:新生代和老年代;再细致一点的有Eden空间、From survivor空间、To survivor空间等。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread local allocation buffer,TLAB)。不过无论如何划分,都与存放内容无关,无论哪个区域,存储的都任然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快的分配内存。

方法区

方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量,即时编译器后的代码等数据。

运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

对象的创建

类加载检查通过以后,接下来虚拟机将来为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确认,为对象分配空间的任务等同于把一块确认大小的内存从Java堆中划分出来。假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”。如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上那些内存块是可用的,这种分配方式称为“空闲列表”。选择那种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
频繁的创建对象,在并发情况下也不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案,一种是对分配内存空间的动作进行同步处理——实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;另一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一个小块内存,称为本地线程分配缓冲(TLTAB)。那个线程要分配内存,就在那个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。

对象的内存布局

对象的内存中存储的布局可以分为3块区域;对象头、实例数据和对齐填充
对象头
包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标示、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit官方称它为MarkWord。另一部分是类型指针,即对象指向它的类原数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,查找对象的元数据并不一定要经过对象本身。如果对象是Java数组,那在对象头中必须有一块用于记录数组长度的数据,因为虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组的大小。)
实例数据
对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。
对齐填充
并不是必然存在的,也没有特别的含义,它仅仅是起着占位符的作用。

对象的访问定位

建立对象是为了使用对象,我们Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范中规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象的访问方式也是取决于虚拟机实现来定的。

目前主要有两种方式访问使用句柄和直接指针

如果使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息,如下图所示


句柄访问对象.png

如果使用指针访问,那么Java堆对象的布局就必须考虑如何访问类型数据的相关信息,而reference中存储的直接就是对象地址,如下图所示


通过直接指针访问对象

风后面是风,天空上面是天空,而你的生活可以与众不同

上一篇下一篇

猜你喜欢

热点阅读