JVM总结(一)Java内存区域划分

时间:2022-08-17 15:12:16

JVM总结(一)Java内存区域划分


Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。

作为一名java程序员,开发的时候,我们常常不用管JVM的具体内容,我们甚至认为系统调优往往是专业的运维团队该做的事情。但在实际的工作过程中,这些工作大部分都是我们苦逼的程序员来做。而要真正做好系统调优工作,JVM的知识必不可少!

为了方便记忆和查找,我将自己阅读的《深入理解Java虚拟机》和相关博客做一个系统的总结,并写成博客分享给大家。

JVM内存划分

要弄清楚JVM自动内存的管理机制,首先就要弄清楚JVM的内存划分。JVM在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域。
JVM总结(一)Java内存区域划分

一.程序计数器(PC寄存器)

  1. 程序计数器是一块较小的内存,可以看做是当前线程所执行的字节码的行号指示器。简单来说就是当前线程的程序执行顺序依赖这个程序计数器。
  2. 每个线程都需要一个独立的程序计数器,各个线程之间的计数器互不影响,独立存储。
  3. 目前这块不会涉及到调优。

二.Java虚拟机栈(Stack)

  1. 与程序计数器一样也是线程私有(各个线程之间的计数器互不影响,独立存储)的,它的生命周期和线程相同。
  2. 简单理解,就是一个线程在执行的时候,这个虚拟机栈用来存放该线程的局部变量,方法返回值的基本数据类型和引用(这是比较浅显的理解,真正的栈帧包含了局部变量表、操作数栈、动态链接等内容,但我们重点关注的是局部变量表)。
  3. 局部变量表所需的内存空间在编译时期完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
  4. 该区域抛出的常见异常是*Error,也就是我们说的栈溢出。实际上这个溢出的原因就是因为当前线程请求的栈的深度大于了JVM所允许的深度。而这个是我们可以调优的一个模块。(栈的深度通俗理解:方法内调用方法所嵌入的深度,最常见的如递归调用。)

三.本地方法栈

  1. 本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法服务(也就是字节码),而本地方法栈为虚拟机使用到的Native方法服务。
  2. Java虚拟机规范对本地方法栈使用的语言、使用方法与数据结构并没有强制规定,因此可以由虚拟机*实现。
  3. 目前这块不会涉及到调优。

四.Java堆

  1. JVM所管理内存最大的一块,被所有的线程共享的一块内存区域,在虚拟机启动时创建。
  2. Java堆的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。(之所以这么说是因为后面会给大家新的分配方式,涉及到逃逸分析与标量替换
  3. Java堆是垃圾回收器管理的主要区域,它可以继续细分。Java堆可以细分为:新生代、老生代;新生代又可以分为Eden、From Survivor、To Survivor区域。Eden区又有一小块线程私有的区域,称作TLAB;
    不论如何划分,都与存放的内容无关,无论哪个区域,存储的仍然是对象实例。
  4. 如果在堆上没有内存完成实例分配,并且堆上也无法再扩展时,将会抛出OutOfMemoryError异常。
  5. 请记住它,它是我们JVM调优的重点。

五.方法区

  1. 和Java一样,被所有的线程共享的一块内存区域。
  2. 它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  3. 方法区是否包含常量池?
    包含,但是注意:JDK1.7中JVM把String常量区从方法区中移除了;JDK1.8中JVM把String常量池移入了堆中。
  4. 方法区调优?
    JDK1.7之前是可以的那时的方法区又叫永久代,JDK1.8之后永久代最终被移除,方法区移至Metaspace。所以其实可是可以调整的,但不是重点。主要是处理异常:OutOfMemoryError:PermGen

六.Native Memory

  1. 这块内存在图中没有体现,但是在实际的JVM中,是不可或缺的一块。
  2. Native Memory没有相应的参数来控制大小,其大小依赖于操作系统进程的最大值。
  3. Native Memory存放的信息有:
    管理java heap的状态数据(用于GC);
    JNI调用,也就是Native Stack;
    JIT(即使编译器)编译时使用Native Memory,并且JIT的输入和输出也都是保存在Native Memory;
    NIO direct buffer。

小结:

JVM根据线程是否私有划分如下:
私有
- 程序计数器:记录当前线程所执行字节码的行号指示器
- 虚拟机栈:存放了当前线程调用方法的局部变量表、操作数栈、动态链接、方法返回值等信息(可以理解为线程的栈)
- 本地方法栈:为虚拟机使用的Native方法提供服务,后多与JVM Stack合并为一起
共享
- Java堆:占据了虚拟机管理内存中最大的一块,唯一目的就是存放对象实例(与引用是两个概念),也是垃圾回收器主要管理的地方,故又称GC堆。
- 方法区:存储加载的类信息、常量区、静态变量、JIT(即时编译器)处理后的数据等,类的信息包含类的版本、字段、方法、接口等信息。需要注意是常量池就在方法区中,也是我们这次需要关注的地方。


深入理解JVM(1) : Java内存区域划分
JVM-String常量池与运行时常量池
JVM的Heap Memory和Native Memory
JVM7、8详解及优化