JVM 基础(1) -- Java 的内存结构

只谈情不闲聊 提交于 2020-03-16 19:54:31

以黄小斜博文为主的学习总结

1. 了解 JVM 内存有什么好处

JVM 的内存结构布局
所有的 Java 开发人员可能会遇到这样的困惑:我该为堆内存设置多大空间呢?OutOfMemoryError 的异常到底涉及到运行时数据区的哪块区域?该怎么解决呢?
其实如果你经常解决服务器性能问题,那么这些问题就会变的非常常见,了解 JVM 内存可以帮助我们在服务器出现性能问题的时候,快速的了解哪块内存区域出现了问题,以便快速的解决生产故障。

2. JVM 主要组成部分

JVM 主要组成部分
JVM 主要由 类加载器 、运行时数据区 、执行引擎和本地库接口四部分组成。
JVM 工作的流程大致就是:首先由类加载器将我们的 .class 文件加载进运行时数据区,然后 JVM 会在堆中创建一个对应类的 Class 对象,但由于我们的字节码只是 JVM 的一套指令集规范,并不能直接交给底层的操作系统去执行,所以这里就需要用到特定的字节码解析器执行引擎将我们的字节码解析成底层操作系统可以执行的机器指令,然后交由 CPU 去执行,在执行的过程中可能还需要用到其他语言的本地库接口进而完成整个程序的功能。

3. 运行时数据区的五大组成部分

1. 程序计数器

简称 PC,他是一块较小的内存区域,我们可以把它看做是一个当前线程所执行的字节码的行号指示器。在虚拟机概念模型里,我们的字节码解析器就是通过改变它的值来选取下一条需要执行的字节码指令的,当然,只是在概念模型中这么定义,不同的虚拟机中可能有更高效的实现方式。
像 分支 、跳转 、循环 、线程恢复 、异常处理等等这些基础的功能都是通过 PC 来实现的。
另外,在 Java 多线程中,为了确保每个线程在切换之后都能恢复到原来的执行位置,每个线程都需要有一个 PC ,也就是说, PC 它是线程私有的一块内存区域。
如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Natvie 方法,这个计数器的值则为空(Undefined)。
PC 是 JVM 规范中唯一一块没有定义任何 OOM 异常情况的内存区域。

2. Java 虚拟机栈

JVM Stack 也是线程私有的,它的生命周期和线程相同。它描述的是方法执行的内存模型:每一个方法执行的时候都会创建一个栈帧(Stack Frame),用来存储 局部变量表 、操作数栈 、动态链接 和 方法出口 等信息。一个方法从执行到结束就对应着一个栈帧在 JVM 栈中入栈到出栈的过程。
局部变量表用来存放编译期可知的 各种基本数据类型 、对象引用(可以是直接指针 、句柄等等可以定位到堆中对象的东西) 和 returnAddress 类型(指向一条字节码指令的地址)。其中 64 位长度的 long 和 double 类型的数据会占用 2 个局部变量空间(Slot,即 槽),其余的数据类型只占用 1 个。
局部变量表的大小在编译期就已经是完全确定好的了,并且在方法执行的过程中不会去改变它的大小。
JVM 栈存在两种内存溢出异常情况:

  1. StackOverflowError:栈溢出异常,我们每调用一个方法就会有一个栈帧加入到 JVM 栈 中,方法执行结束,对应的栈帧就会从 JVM 栈 中被移除,通常来讲,一般栈区是远小于堆区的,因为我们一般调用的函数数量不会超过上千个,即使一个函数的大小占 1 K,那么 栈区 也不过是需要 1 MB 的空间,通常我们 JVM 栈 的大小在 1~2 MB 之间。
    但是需要特别注意递归的调用,如果递归的层次太多就会出现该异常。
    解决方法:修改程序
  2. OutOfMemoryError:内存溢出异常,如果 JVM 栈可以动态扩展(当前大部分的 JVM 都是采用可以动态扩展的方式来实现 JVM 栈,只不过 Java 虚拟机规范中也允许固定长度的 JVM 栈),当 JVM 栈 扩展时无法申请到足够的内存就会抛出该异常。

3. 本地方法栈

本地方法栈和 JVM 栈一样也是线程私有个一块内存区域,只不过区别在于:本地方法栈是为 JVM 执行的 Native 方法服务的,而 JVM 栈是为 JVM 执行的 Java 原生方法服务的。
因为 JVM 规范对该区域并没有做太多强制的规定,所以不同的虚拟机可以自由的实现它,像 Sun 公司的 Hotspot 虚拟机就直接将 JVM 栈和本地方法栈合二为一了。
和 JVM 栈一样,它也有 栈溢出和内存溢出 两种异常情况。

4. Java 堆

Java 堆是 JVM 管理的内存中最大的一块内存区域,它是所有线程共享的,唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
由于 Java 堆是垃圾收集器主要管理的一块内存区域,所以它也叫 GC 堆,从内存回收的角度来看,Java 堆 可以分为新生代和老年代,新生代又可以细分成 Eden 空间和两个 Survivor 空间。另外还有一个永久代,逻辑上被划分成堆的一部分,但实际上处于堆之外,并且 jdk 1.8 的时候已经被废弃了。
Java 堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可,并且它的实现有两种方式:一种是 固定大小,一种是 可扩展,当然,目前主流的 JVM 都是按照 可扩展 的方式来实现 Java 堆的。
它存在一种内存溢出异常情况:

  1. OutOfMemoryError–Java heap space:堆溢出异常,JVM 在启动的时候会自动设置 Java 堆的大小,初始空间是物理内存的 1/64,通过 -Xms 来设置,最大空间不可超过物理内存,通过 -Xmx 来设置。
    在 JVM 中,如果 98% 的时间是用于 GC,且可用的 堆 的大小不足 2% 的时候就会抛出该异常。
    解决方法:通过参数手动设置堆的大小

5. 方法区

方法区也是所有线程共享的内存区域,它和 Java 一样,可以处于物理上不连续的内存空间中,只要逻辑上连续即可,实现的方式也有两种:固定大小和可扩展,另外,它还可以选择不实现垃圾收集。
这块区域主要用来存储类加载后的类信息 、静态字段 、常量 及 JIT 编译后的代码等数据。
它也存在一种内存溢出异常情况:

  1. OutOfMemoryError–PermGen space:永久代溢出异常,jdk 1.7 及以前版本,方法区是由永久代实现的,它和存放对象实例的堆不同,sun 的 GC 不会在主程序运行的期间对方法区进行清理,所以当我们多加载了几个类或者 jar 包的时候,就可能会抛出该异常。
    解决方法:手动设置 MaxPermSize 的大小

因为永久代溢出异常很容易就会产生,所以 jdk 1.8 的时候就直接用元空间替换掉永久代,元空间并不在虚拟机中,而是直接使用本地内存,本地内存的大小取决于 32/64 位机器可虚拟化的内存大小,这样子,JVM 就不会因为多加载了几个类或 jar 包就出现 OOM 异常情况了。
当然,这只是 jdk 1.8 使用元空间的原因之一,另一个原因是为了兼容 Hotspot JVM 和 JRockit VM (JRockit VM 没有永久代)

4. 控制各区域大小的参数

在这里插入图片描述
控制参数

  • -Xms:设置堆的最小空间大小
  • -Xmx:设置堆的最大空间大小
  • -XX:NewSize:设置新生代最小空间大小
  • -XX:MaxNewSize:设置新生代最大空间大小
  • -XX:PermSize:设置永久代最小空间大小
  • -XX:MaxPermSize:设置永久代最大空间大小
  • -Xss:设置每个线程的堆栈大小(堆栈 = JVM 栈 + 本地方法栈)

元空间的配置参数

  • MetaspaceSize:初始化的 Metaspace 大小,控制元空间发生 GC 的阈值。GC 后,动态增加或降低 MetaspaceSize。在默认情况下,这个值大小根据不同的平台在 12M 到 20M 之间浮动。

    使用 Java -XX:+PrintFlagsInitial 命令查看本机的初始化参数

  • MaxMetaspaceSize:限制 Metaspace 增长的上限,防止因为某些情况导致 Metaspace 无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为 4294967295B(大约 4096MB,即 4G)

  • MinMetaspaceFreeRatio:当进行过 Metaspace GC 之后,会计算当前 Metaspace 的空闲空间比,如果空闲比小于这个参数(即实际非空闲占比过大,内存不够用),那么虚拟机将增大 Metaspace 的大小。

    默认值为 40,也就是 40%。通过设置该参数可以控制 Metaspace 的增长速度,值太小的话会导致 Metaspace 增长缓慢,并且会导致 Metaspace 的使用逐渐趋于饱和,可能会影响之后类的加载。而值太大的话会导致 Metaspace 增长过快,浪费内存

  • MaxMetasaceFreeRatio:当进行过 Metaspace GC 之后, 会计算当前 Metaspace 的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放 Metaspace 的部分空间。默认值为70,也就是70%

  • MaxMetaspaceExpansion:Metaspace 增长时的最大幅度。在本机上该参数的默认值为 5452592B(大约为5MB)

  • MinMetaspaceExpansion:Metaspace 增长时的最小幅度。在本机上该参数的默认值为 340784B(大约330KB)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!