文章目录

  • ​​1. Java 虚拟机内存模型​​
  • ​​2. Java 虚拟机内存分布​​
  • ​​2.1 程序计数器​​
  • ​​2.2 Java 虚拟机栈​​
  • ​​2.3 本地方法栈​​
  • ​​2.4 Java 堆​​
  • ​​2.5 方法区​​

 

1. Java 虚拟机内存模型

  • 程序计数器, 用于存放下一条运行的指令。    ​​线程私有​
  • Java 虚拟机栈, 用于存放 Java 函数调用堆栈信息。    ​​线程私有​
  • 本地方法栈, 用于存放 Native 函数调用堆栈信息。    ​​线程私有​
  • Java 堆, 用于存放 Java 程序运行时所需要的对象等数据。    ​​线程共享​
  • 方法区, 用于存放程序的类元数据信息。    ​​线程共享​

 

2. Java 虚拟机内存分布

2.1 程序计数器

  程序计数器是一块很小的内存空间, 由于 Java 是支持线程的语言, 当线程数量超出 CPU 数量时, 线程之间根据时间片轮询抢夺 CPU 资源。 对于单核 CPU 而已, 每一时刻, 只能有一个线程在运行, 而其他线程必须被切换出去。 为此, ​​每一个线程都必须用一个独立的程序计数器, 用于记录下一条要运行的指令​​​。各个线程之间的计数器互不影响, 独立工作, 是一块​​线程私有​​​的内存空间。
 
   如果当前线程正在执行一个 Java 方法, 则程序计数器记录正在执行的 ​​​Java 字节码地址​​​; 如果当前线程正在执行一个 Native 方法, 则程序计数器​​为空​​。

 

2.2 Java 虚拟机栈

  Java 虚拟机栈也是​​线程私有​​​的内存空间, 它和 Java 线程在同一时间创建, 它保存方法的局部变量、 部分结果, 并参与方法的调用和返回。
 
   如果线程在计算过程中, 请求的栈深度大于最大可用的栈深度, 则抛出 StackOverflowError; 如果 Java 栈可以动态扩展, 而在扩展栈的过程中没有足够的内存空间来支持栈的扩展, 则抛出 OutOfMemoryError。
 
   在 Hot Spot 虚拟机中, 可以使用 -Xss 参数来设置栈的大小。 栈的大小直接决定了函数调用的可达深度。
 
   虚拟机栈在运行时使用一种叫做​​​栈帧​​​的数据结构来保存上下文数据。在栈帧中存放了​​方法的局部变量表(存放方法的参数和方法内部的局部变量)、操作数栈、动态连接方法和返回地址等​​​信息。每一个​​方法调用都伴随着栈帧的入栈操作​​​。 相应的, ​​方法的返回则表示栈帧的出栈操作​​。 如果方法调用时, 方法的参数和局部变量相对较多, 那么栈帧中的局部变量表就会比较大, 栈帧会膨胀以满足方法调用所需传递的信息。 因此, 单个方法调用所需的栈空间大小也会比较多。

注意: 函数嵌套调用的次数由栈的大小决定。 栈越大, 函数嵌套调用次数越多。 ​​对一个函数而言, 它的参数越多, 内部局部变量越多, 它的栈帧就越大​​, 其嵌套调用次数就会减少。

 

2.3 本地方法栈

  ​​本地方法栈用于管理本地方法的调用。​​本地方法不是用 Java 实现的, 而是用 C 实现的。在 SUN 的 Hot Spot 虚拟机中不区分本地方法栈和虚拟机栈。 因此, 和 Java 虚拟机栈一样, 也会抛出 StackOverflowError 和 OutOfMemoryError。

 

2.4 Java 堆

  Java 堆是 Java 程序运行时内存中最重要的部分, 几乎所有的对象和数组都是在堆中分配空间的。 Java 堆分为​​新生代​​​和​​老年代​​​两个部分, 新生代用于​​存放刚刚产生的对象和年轻的对象​​​。 如果对象一直没有被回收, 生存的足够长, 新生代对象就会被移入老年代。
 
   新生代可细分为: ​​​Eden、 Survivor Space0(s0 或者 from space)、 Survivor Space1(s1 或者 to space)​​。 Eden意义为伊甸园, 即对象的出生地, 大部分(存在部分较大对象会在创建时就移入老年代, 由参数 PretenureSizeThreshold 决定)对象刚刚建立时, 通常会存放在这里。 s0 和 s1 为 Survivor 空间, 直译为幸存者, 也就是说存放其中的对象至少经历过一个垃圾回收, 并得以幸存。 如果幸存区的对象到了指定年龄仍未被回收, 则有机会进入老年代(tenured)。

 

2.5 方法区

  方法区也是 JVM 内存中非常重要的一块内存区域, 与堆空间类似, 它也是被 JVM 中所有​​线程共享​​​的。 方法区​​主要保存的信息是类的元数据​​​。
 
  方法区中最为重要的是​​​类的类型信息、常量池、域信息、方法信息​​。类型信息包括类的完整名称、 父类的完整名称、 类型修饰符和类型的直接接口类表; 常量池包括这个类方法、域等信息所引用的常量信息; 域信息包括域名称、域类型和域修饰符; 方法信息包括方法名称、 返回类型、 方法参数、 方法修饰符、 方法字节码、 操作数栈和方法栈帧的局部变量区大小以及异常表。 总之, 方法区内保存的信息大部分来自于 class 文件, 是 Java 应用程序运行必不可少的重要数据。

注意: ​​方法区也可称为永久区, 主要存放常量和类的定义信息。​

 

Hot Spot 虚拟机回收类的基本条件: ​​所有该类的实例被回收且装载该类的 ClassLoader 被回收。​