02. JVM内存区域

本文详细介绍了JVM内存的各个区域,包括虚拟机栈、程序计数器、本地方法栈、方法区(JDK8前的永久代与JDK8后的元空间)、堆和直接内存。讲解了各区域的作用、异常情况以及相关参数调整,特别强调了堆和方法区的内存溢出问题以及JDK8中元空间取代永久代的原因。
摘要由CSDN通过智能技术生成

1. JVM内存区域

JVM内存区域由Java运行时数据区和直接内存构成,如图:

JVM内存区域

官方文档JDK8参数:

https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

2. 虚拟机栈

虚拟机栈描述的是Java方法执行的内存模型。

Java每个方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口信息。每一个方法从调用直到执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。

虚拟机栈

局部变量表存放编译期可知的各种基本数据类型(int、short等)、对象引用、returnAddress类型(指向一条字节码指令的地址)。局部变量表是32位的长度,如果是存放64位数据,就使用高低位占用两个存放。

局部变量表所需的内存空间在编译期间完成分配。当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

操作数栈:JVM中执行引擎是基于栈的引擎,这个栈就是操作数栈。

方法出口:存放进入方法被调用时主调方法的字节码指令地址,如:

main(){
    a();
    b(); // 主调方法的字节码指令地址xxxx
    c();
}

b(){
    //
}

b()的栈帧会存放主调方法的字节码指令xxxx。

Java 方法有两种返回方式,不管哪种返回方式都会导致栈帧被弹出:

  1. return 语句。
  2. 抛出异常。

虚拟机栈类比操作系统

虚拟机类比于操作系统:

  1. 执行引擎 = CPU
  2. 操作数栈 = 高速缓存
  3. 内存 = 局部变量表、堆

虚拟机栈区域会发生的异常:

  1. 如果线程请求的栈深度大于虚拟机栈所允许的深度,抛出StackOverflowError异常。
  2. 大部分的JVM允许虚拟机栈动态扩展,如果扩展时无法申请到足够的内存,抛出OutOfMemoryError异常。

虚拟机栈的大小可用参数 –Xss 调整大小,例如-Xss256k。

参见官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html, 可知虚拟机栈的大小一般缺省为 1M。

Xss默认大小

3. 程序计数器

如果线程正在执行一个Java方法,程序计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行Native方法,程序计数器的值为空(Undefined)。

此区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

每个线程都有一个程序计数器的原因:

  1. 该线程的CPU时间片用完后,记录该线程的字节码指令地址,为该线程再次获得CPU执行指令时使用。
  2. 类比于计算机中CPU + 高速缓存 + 内存 + 程序计数器,JVM中使用执行引擎 + 操作数栈 + 堆、局部变量表 + 程序计算器。

4. 本地方法栈

本地方法栈为虚拟机使用到的Native方法服务。

本地方法被执行时,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。

HotSpot虚拟机直接将本地方法栈和虚拟机栈合二为一。

与虚拟机栈一样,本地方法栈会抛StackOverflowError、OutOfMemoryError异常。

5. 方法区

方法区主要用来存放已被虚拟机加载的类相关信息,包括类信息、静态变量、常量、常量池等。

HotSpot虚拟机在JDK1.7之前为了使垃圾收集器可以像管理Java堆一样管理方法区内存,省去专门为方法区编写内存管理代码的工作,使用永久代实现方法区,所以方法区也被称为永久代(Permanent Generation)。HotSpot虚拟机在 JDK1.8 及以后使用了元空间来实现方法区。对于其他虚拟机,不存在永久代的概念。也就是说虚拟机规范中有方法区,没有永久代。

常量池 (Constant Pool Table),用于存放编译期间生成的各种字面量和符号引用。

字面量包括字符串(String a=“b”)、基本类型的常量(final 修饰的变量),符号引用则包括类和方法的全限定名(例如 String 这个类,它的全限定名就是 Java/lang/String)、字段的名称和描述符以及方法的名称和描述符。

什么是符号引用?

一个类(假设为 People 类)被编译成一个 class 文件时,如果 People 类引用了 Tool 类,但是在编译时 People 类并不知道引用类的实际内存地址,因此只能使用符号引用来代替。

而在类装载器装载 People 类时,此时可以通过虚拟机获取 Tool 类的实际内存地址,因此便可以将符号 org.simple.Tool 替换为 Tool 类的实际内存地址,即直接引用地址。

在编译时用符号引用来代替引用类,在加载时再通过虚拟机获取该引用类的实际地址。以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局是无关的,引用的目标不一定已经加载到内存中。

常量池包括静态常量池(Class文件常量池)、运行时常量池、字符串常量池。

运行时常量池是全局共享的,多个类共用一个运行时常量池。

Class文件常量池(Constant Pool Table,用于存放编译期生成的各种字面量和符号引用,是静态常量池),在类加载后进入方法区的运行时常量池(Runtime Constant Pool)。

类中的一个字符串常量在 class 文件中时,存放在 class 文件常量池中的;在 JVM 加载完类之后,JVM 会将这个字符串常量放到运行时常量池中,并在解析阶段,指定该字符串对象的索引值。

在 HotSpot 虚拟机中,Java7 已经将永久代的静态变量和运行时常量池转移到了堆中,其余部分则存储在 JVM 的非堆内存中,Java8 版本已经将方法区中实现的永久代去掉了,并用元空间(class metadata)代替了之前的永久代,元空间的存储位置是本地内存。

方法区大小参数:

  1. jdk7 及以前(初始和最大值):-XX:PermSize;-XX:MaxPermSize。
  2. jdk8 以后(初始和最大值):-XX:MetaspaceSize; -XX:MaxMetaspaceSize,如果不设置参数的话,jdk8 以后大小就只受本机总内存的限制。

Java8 为什么使用元空间替代永久代,这样做有什么好处呢?

官方给出的解释是:

  1. 移除永久代是为了融合 HotSpot JVM 与 JRockit VM 而做出的努力,因为 JRockit 没有永久代,所以不需要配置永久代。
  2. 永久代内存经常不够用或发生内存溢出,抛出异常 java.lang.OutOfMemoryError: PermGen。这是因为在 JDK7 版本中,指定的 PermGen 区大小为8M,由于 PermGen 中类的元数据信息在每次 FullGC 时都可能被收集,回收率都偏低,成绩很难令人满意;为 PermGen 分配多大的空间很难确定,PermSize 的大小依赖于很多因素,比如,JVM 加载的 class 总数、常量池的大小和方法的大小等。整个永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 MaxPermSize(永久代控制参数) 控制了,而由系统的实际可用空间来控制,这样能加载的类就更多了。

永久代溢出时会得到如下错误:java.lang.OutOfMemoryError: PermGen
元空间溢出时会得到如下错误:java.lang.OutOfMemoryError: MetaSpace

方法区的内存回收目标主要是对常量池的回收、对类型的卸载。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。

6. 堆

堆是 JVM 上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收,操作的对象就是堆。

堆空间一般是程序启动时,就申请了,但是并不一定会全部使用。堆一般设置成可伸缩的。

堆大小参数:
-Xms:堆的最小值
-Xmx:堆的最大值
-Xmn:新生代的大小
-XX:NewSize:新生代最小值
-XX:MaxNewSize:新生代最大值

在堆中没有内存完成实例分配,并且堆无法再扩展时,抛出OutOfMemoryError异常。

7. 直接内存(堆外内存)

JVM 在运行时,会从操作系统申请大块的堆内存,进行数据的存储;同时还有虚拟机栈、本地方法栈和程序计数器,这块称之为栈区。操作系统剩余的内存也就是堆外内存。

它不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域;如果使用了 NIO,这块区域会被频繁使用,在 java 堆内可以用DirectByteBuffer 对象直接引用并操作。

这块内存不受 java 堆大小限制,但受本机总内存的限制,可以通过-XX:MaxDirectMemorySize 来设置(默认与堆内存最大值一样),所以也会出现 OOM 异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值