JVM系列:一、JVM内存分区及作用(JDK8)

6 篇文章 0 订阅

一、JVM 内存分区及作用:

   根据《Java虚拟机规范》的规定,JVM 所管理的内存将会包括以下几个运行时数据区域如下,但不同的 JVM 版本在实现上有所差异。
在这里插入图片描述

下图为 JDK8 的内存结构:
在这里插入图片描述

各个数据区域可以继续细分如下结构:

  1. Heap
    • Young Generation
      • Eden
      • Survivor From
      • Survivor To
    • Old Generation
    • String Constant Pool
  2. 方法区-Meta Space
    • Runtime Constant Pool
      • Literal
      • Symbolic Reference
    • 类的元数据
  3. Direct Memory
  4. VM Stack
  5. Native Method Stack
  6. Program Counter Register

二、JVM - 线程共享数据区域

2.1 堆(Heap)

    Heap 是所有线程所共享的一块内存(Eden 中 TLAB(Thread Local Allocation Buffer) 是线程独享的),在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。

    Heap 内存可以通过 -Xms 、-Xmx 设置堆初始大小、最大值,对应下图中的 min 和 max。Heap 大小可以动态调整,当 committed 值超过高水位或低于低水位时,Heap 就会进行扩容或缩容,这样的话必然要进行一次 GC,所以,在 WEB 应用场景下,一般将 -Xms 、-Xmx 设置相等,避免在应用启动时,频繁的触发 GC。

在这里插入图片描述

TLAB(Thread Local Allocation Buffer)

    总的来说,它位于 Eden,其作用,是为了对象空间的快速分配,避免加锁操作,而构建的一块线程独享内存区域,适用于小对象的内存分配。

Card Table

   卡表,或记忆集。存在于分代收集中,用于记录 OldGen 中引用 YoungGen 对象的情况。

   在 Young GC 使用可达性算法寻找存活对象时,只扫描卡表中被标记的 OldGen 对象,然后完成 GC,避免了 Young GC 对 Heap 的全部扫描

2.2 方法区(MethodArea / Non-Heap)

    方法区属于是 JVM 运行时数据区域的一块逻辑区域,是各个线程共享的内存区域。也叫作“非堆”(Non-Heap)区。

作用:
   方法区会存储已被虚拟机加载的 字符串常量、静态变量、类信息、即时编译器编译后的代码缓存等数据 等数据,但在不同的 JVM 中,存储的数据内容有一定的差异。

2.2.1 方法区的具体实现:PermanentGeneration / MetaSpace

方法区有两个实现,如下图所示:
在这里插入图片描述

PermanentGeneration / MetaSpace 在哪块内存区域?存储的数据有什么差异?

  • PG 是 JVM 的一个逻辑部分 ;而 MS 是使用本地内存空间,不在 Heap 中
  • PG中存放字符串常量池、静态、类元数据信息(类的字段和方法、编译后的代码);MS 只存放类元数据信息。字符串常量池、静态变量被移动到了 Heap内,方便进行 GC。

为什么要 JDK8 要移除 PermanentGeneration 区域?

  1. 内存限制: 用 PG 来实现方法区,导致了Java应用 更容易遇到 OOM 的问题,而且有极少数方法(例如String::intern())会因永久代的原因而导致不同虚拟机下有不同的表现。
    • PG的内存大小受限于 -XX:MaxPermSize 和 JVM 可用内存大小限制;MS 使用的是本地内存,虽然仍旧可能出现 OOM,但是比原来出现的几率会更小。
  2. 方法区实现统一: 考虑到 HotSpot 未来的发展,将 JRockit 中方法区的实现和 HotSpot 中的实现进行统一。

2.2.2 运行时常量池(Runtime Constant Pool)

作用:

  • 存放 常量池表。常量池表包含字面量(Literal)、符号引用(Symbolic Reference)、直接引用,这些内容是在类加载时生成的。
    • 字面量是源代码中的固定值的表示法,即通过字面我们就能知道其值的含义。字面量包括整数、浮点数和字符串字面量。
    • 符号引用包括类符号引用、字段符号引用、方法符号引用、接口方法符号。

注意:

  1. 运行时常量池是方法区的一部分,受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OOM 异常。
    • 运行期间也可以将新的常量放入池中(如String#intern),因此RCP内存具备动态性

RuntimeConstantPool和StringConstantPool分别存储什么数据?存在JVM那块区域?

  • RuntimeConstantPool主要存储常量池表;StringConstantPool是针对字符串(String 类)专门开辟的一块区域,来提高字符串相关的操作效率。
  • RuntimeConstantPool 在 JDK8 之前,存储在 JVM 的方法区-PermanentGeneration 中;JDK8 及之后存在本地内存的方法区-MetaSpace中。
  • StringConstantPool,在JDK7之前,存储在 JVM 的方法区-PermanentGeneration 中;JDK7 及之后存在JVM 的 Heap 中。(与之一起迁移的还有静态变量)

2.2.3 Code Cache

    JVM生成的native code存放的内存空间称之为Code Cache;JIT编译、JNI等都会编译代码到native code,其中JIT生成的native code占用了Code Cache的绝大部分空间
在这里插入图片描述

Code Cache更多内容

三、JVM - 线程私有数据区域

   线程私有数据区域中,数据的生命周期与线程相同,线程结束时,其中的数据会被移除。

虚拟机栈(栈内存/Java Virtual Machine Stacks):

用途:

  1. 它为 java 方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息,方法执行完成后再出栈。
  2. 局部变量表里存储的是 八大基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用
    • 对象引用:reference类型,它并不等同于对象本身。可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置

注意 :

  • 在《Java虚拟机规范》中,对这个内存区域规定了两类异常状况
    • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError;
    • 如果 Java 虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。
      • HotSpot虚拟机的栈容量是不可以动态扩展的,所以是不会由于虚拟机栈无法扩展而导致 OOM 异常的。

本地方法栈(Native Method Stacks)

本地方法栈和虚拟机栈类似,只不过本地方法栈为 Native 方法服务

Hot-Spot虚拟机中,直接把本地方法栈和虚拟机栈合二为一。

程序计数器(Program Counter Register)

程序计数器是一块较小的内存空间,主要有两个作用:

  • 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:分支、循环、跳转、异常处理、线程恢复等。
  • 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了,属于“线程私有”的一块内存

注意 :
   由于 PCR 的 大小是固定的(不同JVM实现有所差异,32位系统上为4字节,64位系统上为8字节),不会动态分配内存,并且不用于存储对象实例、数组或方法调用,因此不会出现 OOM 异常。所以此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何 OOM 情况的区域。

问题&思考:

1. Java 中,不同类型变量到底存储在那个区域的问题讨论:

Java中有 基本数据类型 和 引用数据类型 两大类型,当一个变量由引用和实际对象(引用指向指向实际对象)组成。

  1. 八大基础数据类型:

当其为方法中的局部变量时,变量的引用和实际对象存储在 JVM 自动为方法分配的帧栈中;

当其为全局变量(即类的成员变量)时,变量的引用和实际对象存储在堆中;

  1. 引用类型:

当其为方法中的局部变量时,变量的引用存储在 JVM 自动为方法分配的帧栈中,实际对象存储在堆中;

当其为全局变量(即类的成员变量)时,变量的引用和实际对象存储在堆中;

2. StackOverFlow 发生的原因,在JVM的那个区域?

   如果某个线程中,方法调用层级过深,那么使用的栈空间可能会大于 JVM 所允许的最大值,就会抛出 StackOverflowError。 “JVM 所允许的栈空间最大值” 取决不同的平台,在 64位Linux上为1M,更多默认值查看下面的地址。JDK8 Unix VM OptionsJDK8 Win VM Options

   StackOverFlow 是因为入栈的信息太多而导致的,所以其发生的区域主要在 VM Stack 和 Native Stack。

3. MetaSpace空间满后,会触发对直接内存的GC吗?和 Heap 中的 GC 有什么差异?

   当内存使用达到一定阈值时,垃圾回收器可以触发元数据回收(Metadata GC)以释放不再使用的类的元数据。它与堆内存的垃圾回收是相互独立的。更多 MetaSpace参数调优


参考资料:

  • 《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》
  • JavaGuide-JVM
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值