Java虚拟机初探

简介

就在上周,我完成了自毕业以来的第一次跳槽。期间经历了一些笔试和面试,考察内容无外乎是Java基础知识,Java高级特性,Java虚拟机,MySQL数据库和缓存相关的问题。这其中最让我手足无措的是Java虚拟机这部分内容,在此之前我并没有系统的学习过,也没有主动去了解它,基本上是随查随用。可这种方式不能长久的记忆,随通过《深入理解Java虚拟机:JVM高级特性与最佳实践(第三版)》进行系统的学习,并将此书包含的知识点记录如下,如有错误,请指出。

主体

第一部分 Java内存区域与溢出异常

运行时数据区域

Java虚拟机初探_第1张图片

图中绿色部分为线程共享区域,白色部分为线程私有

线程私有区域
  • 程序计数器:当前线程所执行的字节码的行号指示器,通过改变这个计数器选取下一条需要执行的指令。分支、循环、跳转、异常处理和线程恢复等功能都需要以来程序计数器来完成。此区域不会出现OutOfMemoryError

  • Java虚拟机栈:每个方法执行的时候,Java虚拟机栈都会创建一个栈帧用于存储局部变量表,操作数栈、动态连接、方法出口信息等。每个方法经调用直至结束,对应一个栈帧在虚拟机栈的入栈和出栈过程。该区域会出现StackOverFlowError(线程请求的栈深度大于虚拟机所允许的深度)和OutOfMemoryError(如果虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存)

    • 局部变量表:该区域存放基本数据类型、对象引用和returnAddress类型。该区域所需的内存空间在编译期间完成分配。
  • 本地方法栈:该区域服务于Native方法,常见的的Native方法是:Object类中的clone(),notify(),notifyAll(),wait()等方法,该区域也会出现StackOverFlowErrorOutOfMemoryError

线程共享区域
  • 方法区:用于存储已被编译机制加载的类型信息、常量、静态变量、即时编译器编码后的代码缓存等数据。该区域有一个别名“非堆(Non-Heap)”,目的时与Java堆区分开来。如果方法去无法满足新的内存分配需求时,将抛出OutOfMemoryError
  • 堆:堆(Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块区域,在虚拟机启动时创建。Java堆是垃圾回收器管理的内存区域,被称为“GC堆”。Java堆可以处于物理上不连续的内存空间中,但是逻辑上它应该被视为连续的。Java堆可以被实现成固定大小的,也可以是可扩展的,通过-Xmx(最大堆容量)和-Xms(最小堆容量)设定;例如:-Xmx3g -Xms3g。如果在Java堆中没有内存完成实例分配,且无法再扩展时,会抛出OutOfMemoryError
  • 运行时常量池:该区域是方法区的一部分,用于存放编译期生成的各种自变量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中,受方法区内存的限制,当常量池无法申请到内存时会抛出OutOfMemoryError

HotSpot虚拟机对象

对象在Java堆中的布局

Java虚拟机初探_第2张图片
对象在堆内存中存储布局划分为三个部分:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)

  • 对象头包括两类信息:
    • 第一类用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向锁ID,偏向时间戳等。
    • 另一部分是类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定对象是哪个类的实例。
  • 实例数据
    • 这部分是对象真正存储的有效信息,即我们在程序代码里面定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。
  • 对象填充:
    • 这部分仅仅起着占位符的作用。由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,所有任何对象的大小都必须是8字节的整数倍。因此,如果对象实例数据部分没有对齐的话,就需要通过Padding来补全。
对象的访问定位

对象访问方式是有虚拟机实现而定的,主流的访问方式有使用句柄和直接指针两种:

  • 使用句柄

  • 直接指针

OutOfMemoryError异常的出现与常见解决方法

堆(Heap)溢出
虚拟机栈和本地方法栈溢出
方法区和运行时常量池溢出
本机直接内存溢出

原文地址:Java虚拟机学习笔记

你可能感兴趣的:(java)