Java内存区域

kolbe 2021年09月14日 90次浏览

1 方法区

方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。在Java虚拟机规范中该区是堆的一个逻辑部分,但是为了与堆区分开,也称之为Non-Heap(非堆)。
方法区的回收一般针对常量池的回收和类型的卸载,回收的效果一般不佳,但也确实有必要。

2 堆

Java堆用于存储所有的对象实例及数组。在分代垃圾回收器中,Java堆可分为新生代(Eden、From Survivor、To Survivor)和老年代

2.1 对象的创建过程

当虚拟机遇到一条new的指令时

  • 检查指令的参数是否在常量池中定位到一个类的符号引用,并判断是否加载、解析、初始化,如果没有则先执行类加载过程
  • 类加载检查通过后,虚拟机为新生对象分配内存,如果内存是规整的(Serial,ParNew),则使用指针碰撞,如果是不规整的,则使用空闲列表(CMS)
  • 分配完内存后,虚拟需将分配到的内存空间进行初始化为零值
  • 虚拟机对对象进行必要的设置:对象所属类,类的元数据信息,对象的哈希码,对象的GC分代等,这些信息都存在对象头中
  • 虚拟机将调用invokespecial指令执行对象的方法(构造器)

2.2 对象的内存布局

对象在内存中的布局分为3块区域:

  • 对象头(Header):对象头包含两部分,第一部分存储对象运行时数据(Mark Word),如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;第二部分为类型指针,指向它的类元数据的指针,通过这个来定位对象属于哪个类的实例。
  • 实例数据(Instance Data):对象真正存储的有效信息,主要是定义的各种类型的字段信息,包含从父类继承下来的。
  • 对齐填充(Padding):非必要存在,起到占位符作用,为了保证对象的大小满足指定大小(HotSpot中对象起始地址必须是8的整数倍)

2.3 对象的访问定位

  • 句柄访问:reference中存放对象的句柄地址,句柄中包含对象实例数据和类型数据的具体地址信息。这种方式的优势在于reference中存的是稳定句柄地址,对象被移动只需改变句柄的实例数据指针,reference无需改变。
  • 直接指针访问:refrence中直接存储对象地址,而对象的实例数据需要考虑放置访问类型数据的相关信息。这种方式的优势在于访问速度更快。

3 虚拟机栈

虚拟机栈描述的是Java方法执行的模型,当方法执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
方法从调用到执行完成,对应着一个栈帧在虚拟机栈入栈到出栈的过程。

4 本地方法栈

本地方法栈与虚拟机栈功能类似,区别在于虚拟机栈执行的是Java方法,本地方法栈执行的是Native方法。

5 程序计数器

程序计数器是当前线程执行的字节码行号指示器,在多线程中,为了线程切换后能恢复到正确的位置,每个线程都维护着一个独立的计数器,该内存区域为线程私有。
如果线程正在执行是的Java方法,则为正在执行的虚拟机字节码指令的地址。如果执行的是Native方法,则这个计数器值为空(Undefined)