深入理解java虚拟机之自动内存管理机制(一)

(一)java中的内存区域

  在java虚拟机执行java程序的时候,会将虚拟机所管理的内存划分为以下几个区域:

  其中,浅颜色的是线程私有的内存区域,即java程序中每个线程都有的独立区域;深色的是共有的区域。

  一、线程私有区域

  1. java虚拟机栈

    虚拟机栈中描述得是java方法执行的内存模型,虚拟机栈为每一个执行的方法创建一个栈帧,栈帧用来存储方法的局部变量表和操作数,方法出口等信息。方法调用和结束就是栈帧入栈和出栈的过程。局部变量表中存放着      方法中定义的各种变量的引用。其大小在编译期就已知。

  2. 本地方法栈

    本地方法栈中存放的是本地方法的信息,所谓的本地方法,就是其他语言写的,和处理器相关的机器代码,这一点要和java方法(字节码),即.class文件定义的方法区分开来。其实这两者非常相似,hotspot中直接合并了两 者。

  3. 程序计数器

    当线程正在执行java方法的时候,程序计数器中存放了当前线程所执行到的字节码的行号,用来选取下一条将要执行的字节码的指令;当正在执行Native方法时,计数器的值为空。

  二、共享区域

  1. 堆

    堆中存放的是对象的实例。每一个对象被new出来了,就存放在堆中,包括被new出来的字符串对象。

  2. 方法区

    方法区中存放着class文件的信息和动态常量池,class文件的信息包括类信息和静态常量池。可以将类的信息是对class文件内容的一个框架,里面具体的内容通过常量池来存储。静态常量池存储的是当class文件被java虚拟机    加载进来后存放在方法区的一些字面量和符号引用,字面量包括字符串,基本类型的常量,符号引用其实引用的就是常量池里面的字符串,但符号引用不是直接存储字符串,而是存储字符串在常量池里的索引的地址。如下图所示:

 

 

 

下图是常量池的内容:

    

  而运行时常量池是当class文件被加载完成后,java虚拟机会将静态常量池里的内容转移到动态常量池里,在静态常量池的符号引用有一部分是会被转变为直接引用的,比如说类的静态方法或私有方法,实例构造方法,父类方         法,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。

  运行时常量池相对于class文件常量池来说,最大的区别就是它的动态性,class文件常量池(静态常量池)在加载类的时候就已经定下来,不可再改变,但是运行时常量池可以在运行期间将新的常量放入池中,比如String类的intern()方法。

  三、直接内存

  直接内存不是虚拟机运行时数据区的一部分,最典型的用到直接内存的地方就是NIO,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。

(二)对象的诞生

  一、创建对象

    创建对象分为5个步骤。

    1. 加载类。

    2. 分配内存。

    3. 初始化零值。

    4. 设置对象头。

    5. 执行<init>。

    首先,虚拟机加载对应的类信息,明确了对象所占空间大小,再给对象在堆中分配内存。

    分配内存有两种方式,取决于堆中内存是否规整。是否规整又取决于垃圾收集器使用的算法,这点稍后谈到。如果内存规整,就会在已使用过得内存和未使用过得内存之间得分解点放一个指针,当分配内存时指针就移动一段距离,该方法称为“指针碰撞”,使用复制算法和标记-整理算法的收集器使用该方法分配内存。指针碰撞对于多线程来说是不安全的,可以通过本地线程分配缓冲池去解决,该池在堆中分配,功能与线程私有类似。第二种方法是使用“空闲列表。空闲列表记录了哪些内存是可用的,有多大,当要分配的时候就从该列表上找到一个符合大小的内存区域。空闲列表应用于内存不规整的情况,收集器使用标记-清理方法时就会令到内存不规整。

    初始化零值,对于jvm来说,就是对象的初始化,它把内存全部置零。这一步骤使得对象的实例字段亦即属性完成了初始化,不用赋值也能使用。但该步骤从java程序的角度看并不是真正的初始化。

    接下来,虚拟机会将该对象的一些信息存放到对象的对象头中,这些信息指明了这个对象是哪个类的实例、对象的哈希码等等。

    init是对象构造器方法,也就是说在程序执行 new 一个对象调用该对象类的 constructor 方法时才会执行init方法。执行完init方法,对于程序来说,对象才是真正完成了初始化。

  二、对象的内存布局

    对象的内存布局有3个区域。分别是对象头,实例数据和对齐填充。

    1. 对象头。对象头本身又包含有两部分信息,第一部分用来存储对象本身的运行时数据,如哈希吗,gc分代年龄等。第二部分就是类型指针,有没有这部分取决于虚拟机的实现,类型指针用来指示该对象是哪个类的实例。

    2. 实例数据。该区域存放对象真正有效的信息,也就是各字段的内容,包括该对象的父类的内容。该区域中,相同宽度的字段被分到一起,在此前提上,父类的变量会出现在子类前面。

    3. 对齐填充。由于hotspot要求的对象起始地址要为8字节的整数,所以如果对象的大小不足8字节整数的话,会有填充,填充的内容是无意义的。

  三、对象的访问定位

    java程序使用栈上的引用来操作堆上的具体对象。有两种访问方式。

    1. 句柄池。其实算是间接引用。虚拟机会在堆中开辟一个句柄池,其保存着到对象的实例数据和对象类型数据的指针。在对象被移动(垃圾收集时经常会发生移动)只用改变句柄中的实例数据指针,栈中的引用本神不需要更改。

    2. 直接指针。最大好处就是访问速度快,不用两次指针定位。

 

猜你喜欢

转载自www.cnblogs.com/one-code/p/10056070.html