0%

JVM的内存区域

  • JVM的内存区域
    • 程序计数器
    • 方法区

1.5 ★JVM的内存区域

  • Java 虚拟机在运行 Java 程序过程中管理的内存区域,称之为运行时数据区

1.5.1 程序计数器

  • 程序计数器(Program Counter Register)也叫 PC 寄存器,每个线程会通过程序计数器记录当前要执行的的字节码指令的地址
    • 在加载阶段,虚拟机将字节码文件中的指令读取到内存之后,会将原文件中的偏移量转换成内存地址,每一条字节码指令都会拥有一个内存地址。这些内存地址会被一条条放在程序计数器中,由程序计数器来告诉解释器下一条要执行的字节码指令是什么
  • 程序计数器不会出现内存溢出 OOM 的问题。

1.5.2 栈

Java虚拟机栈
  • Java 虚拟机栈(Java Virtual Machine Stack)采用栈的数据结构来管理方法调用中的基本数据,先进后出(First In Last Out),每一个方法的调用使用一个栈帧(Stack Frame)来保存。

    • Java 虚拟机栈随着线程的创建而创建,而回收则会在线程的销毁时进行。由于方法可能会在不同线程中执行,所以每个线程都会包含一个自己的虚拟机栈。
  • 栈帧里面又存放了不同的数据:

    • 局部变量表:局部变量表的作用是在运行过程中存放所有的局部变量。
      • 栈帧中的局部变量表是一个数组,数组中每一个位置称之为槽(slot),long 和 double 类型占用两个槽,其他类型占用一个槽。
      • 局部变量表保存的内容有:实例方法的 this 对象,方法的参数,方法体中声明的局部变量。
    • 操作数栈:操作数栈是栈帧中虚拟机在执行指令过程中用来存放临时数据的一块区域。
    • 帧数据:帧数据主要包含动态链接、方法出口、异常表的引用。
  • 栈内存溢出:Java 虚拟机栈如果栈帧过多,占用内存超过栈内存可以分配的最大大小时就会出现内存溢出。Java 虚拟机栈内存溢出时会出现 StackOverflowError 的错误。

    • 可以使用虚拟机参数 -Xss 来修改 Java 虚拟机栈的大小。

      • 语法:-Xss 栈大小
      • 单位:字节(默认,必须是 1024 的倍数)、k或者K(KB)、m或者M(MB)、g或者G(GB)

      Windows(64位) 下的 JDK8 的栈的最小值为 180k,最大值为 1024m。

本地方法栈
  • Java 虚拟机栈存储了 Java 方法调用时的栈帧,而本地方法栈存储的是 native 本地方法的栈帧。
    • 在 Hotspot 虚拟机中,Java 虚拟机栈和本地方法栈实现上使用了同一个栈空间。

1.5.3 堆

  • 一般 Java 程序中堆内存是空间最大的一块内存区域,创建出来的对象都存在于堆上

    • 栈上的局部变量表中,可以存放堆上对象的引用。
    • 静态变量也可以存放堆对象的引用,通过静态变量就可以实现对象在线程之间共享。
  • 堆空间有三个需要关注的值:usedtotalmax

    • used 指的是当前已使用的堆内存。
    • total 是 java 虚拟机已经分配的可用堆内存。
    • max 是 java 虚拟机可以分配的最大堆内存。

    随着堆中的对象增多,当 total 可以使用的内存即将不足时,java 虚拟机会继续分配内存给堆,total 值会变大,total 最多只能与 max 相等。

  • 堆内存溢出:堆内存大小是有上限的,当一直向堆中放入对象达到上限之后,就会抛出 OutOfMemoryError

    • 但是不是当 used = max = total 的时候,堆内存才溢出,在 total < max 的时候,堆内存就有可能溢出,堆内存溢出的判断条件比较复杂,在《垃圾回收器》中会详细介绍。

1.5.4 方法区

类的元信息
运行时常量池
  • 方法区是存放基础信息的位置,线程共享,主要包含三部分内容:

    • 类的元信息:保存了所有类的基本信息。
      • 方法区存储了每个类的基本信息(元信息),一般称之为 InstanceKlass 对象,在类的加载阶段完成,前面有介绍过。
    • 运行时常量池:保存了字节码文件中的常量池内容。
      • 方法区还存放了运行时常量池,常量池中存放的是字节码中的常量池内容。
        • 字节码文件中通过编号查表的方式找到常量,这种常量池称为静态常量池。当常量池加载到内存中之后,可以通过内存地址快速的定位到常量池中的内容,这种常量池称为运行时常量池。
    • 字符串常量池:保存了字符串常量。
  • JDK7 和 JDK8 在方法区的存放上采用了不同的设计:

    • JDK7 将方法区存放在堆区域中的永久代空间,堆的大小由虚拟机参数 -XX:MaxPermSize=值 来控制。
    • JDK8 将方法区存放在元空间中,元空间位于操作系统维护的直接内存中,默认情况下只要不超过操作系统承受的上限,可以一直分配。可以使用 -XX:MaxMetaspacesize=值 将元空间最大大小进行限制。

    方法区是一个虚拟的概念,并不代表真实的内存空间。

  • 方法区也可能存在内存溢出问题

  • 静态变量的存储

    • JDK6 及之前的版本中,静态变量是存放在方法区中的,也就是永久代。
    • JDK7 及之后的版本中,静态变量是存放在堆的 Class 对象中,脱离了永久代。
字符串常量池
  • 字符串常量池存储在代码中定义的字符串常量内容,比如 “123” 这个 123 就会被放入字符串常量池。
    • 早期设计时,字符串常量池是属于运行时常量池的一部分,他们存储的位置也是一致的。后续做出了调整,将字符串常量池和运行时常量池做了拆分。
      • 因为在 JDK8,方法区从堆区域中的永久代空间转移到了元空间,而字符串常量池和运行时常量池做了拆分,所以在 JDK8,字符串常量池是在堆上,而运行时常量池是在元空间中

1.5.5 直接内存

  • 直接内存 (Direct Memory) 并不在《Java虚拟机规范》中存在,所以并不属于 Java 运行时的内存区域。
---------------The End---------------