JVM内存管理 | 8lovelife's life
0%

JVM内存管理

记录 Java中的堆与栈究竟是什么,JVM在计算机物理内存中是如何工作的

计算机内存结构

计算机中存在不同类型的内存包括:RAM,CPU寄存器,缓存CACHE等

image

物理内存

物理内存一般指的是RAM与存储器,CPU与RAM或寄存器通信依靠的是地址总线。总线的宽度决定了CPU与内存间通信的数据量。如32位的地址总线寻址范围为:0x00000000~0xffffffff,即232个内存位置,每个内存位置会引用一个字节,所以232 byte = 4GB,计算机的内存是通过操作系统进行分配的,不同进程申请到的内存是逻辑上隔离的

虚拟内存

操作系统按照进程管理内存的申请,进程间各自内存由操作系统保证独立性,独立性不表示一个内存空间只能由一个进程使用。虚拟内存使得不同进程间可以共享内存空间,然而逻辑上不同进程是不能互相访问内存的。当一个进程不活动的情况下,操作系统会将这个进程中的数据移动到磁盘文件中(Windows中的页面文件或者Linux中的swap区),当不活跃进程恢复则操作系统会把磁盘数据重新交换到物理内存中(磁盘IO开销远大于读内存,应该避免频繁的内存交换),而真正高效的内存留给活跃进程使用。若Linux中的swap区活跃度较高说明物理内存的已经不足,swap区被频繁使用会导致系统运行缓慢。虚拟内存提高了内存利用率,而且能够扩展内存的地址空间,如虚拟内存映射到物理内存、文件、其他存储设备上

内核空间与用户空间

  • 内核空间:只要是操作系统运行时使用的用于程序调度、连接硬件资源等的操作逻辑
  • 用户空间:程序真正能够使用的申请地址空间

通常网络传输的数据一般从内核空间传送到远端计算机的内核空间,然后将数据从内核空间复制到用户空间供用户使用,这种数据COPY是很耗时的(内核态到用户态的切换

JMM内存模型

JMM是JVM针对物理内存抽象出来的Java内存模型,是JVM在计算机内存中的工作方式。JVM按照Java运行时数据的存储结构定义Java内存模型

image

PC寄存器

PC寄存器用来保存当前线程正常执行的程序的内存地址,当多线程交叉中断执行时线程能够按照之前中断时的指令继续执行

Java栈区

Java中的栈区与线程相关联,栈主要用来执行程序。JVM为每个新建线程创建栈区,栈区各线程不共享。栈区由多个栈帧组成,栈帧包含方法局部变量、操作栈、返回值等信息。每当程序进入方法都会创建一个栈帧(入栈,方法执行完毕后栈帧会弹出(出栈,弹出的栈帧元素为返回值存入操作栈中。PC寄存器指向活动栈帧(栈区顶部

堆是Java对象存储的位置,由JVM动态开辟、自动回收,堆是Java运行时的数据区。堆是Java线程共享的区域,多线程访问存在一致性问题.堆区结构

image

堆的组成

  • Young区:Java新创建的对象都在Eden区,当Eden区满后会触发minor GC 将Eden区存活的对象复制到其中一个Survivor区,另一个Survivor区同样将存活对象复制到这个Survivor区,始终保证有一个Survivor区空置
  • Old区:Old区存储的是minor GC后 Yong区仍然存活的对象。当Eden区满后将对象复制到Survivor区。1.若Survivor空间不总则对象会被放置到Old区。2.若Survivor区中经过多次minor GC 存在 存活多次的“老”对象,也直接放置在Old区。Old区满后将触发Full GC
  • Metaspace区:JDK1.8将Perm区移除取而代之为Metaspace区,Metaspace区为native memory

不同堆区大小设置

1
java -Xms100M -Xmx200M -Xmn100M -Xss256K -XX:MetaspaceSize=200M -XX:MaxMetaspaceSize=400M
  • -Xms100M : 设置JVM初始堆内存大小
  • -Xmx200M : 设置JVM最大分配的堆内存
  • -Xmn100M : 设置Young区大小
  • -XX:NewRatio=4 : 设置Young区与Old区的比值,Young区:Old区 = 1:4
  • -XX:SurvivorRatio=4 : 设置Eden区与Survivor区的比值,From:To:Eden区 = 1:1:4
  • -XX:MetaspaceSize=200M : 设置Metaspace区初始大小
  • -XX:MaxMetaspaceSize=400M : 设置Metaspace区最大值(若未设置则JVM会动态调整,无上限
  • -Xss256K : 设置每个线程栈大小

方法区

Metaspace区是方法区的实现,主要存放类的结构信息、方法体、常量池、接口初始化,构造函数等,方法区的大小在程序启动一段时间基本就固定了,JVM已经加载了程序所需的类信息,若存在对类的动态编译则需要关注方法区的大小

运行常量池

常量池是方法区的一部分,常量池代表运行时每个class文件中的常量表,常量表包括:方法、域的引用(运行时会进行解析指向真实地址、编译期的数字常量。每个class或interface常量池都是在创建class或interface时创建的

本地方法栈

本地方法栈是 1.JVM调用本地方法(C或C++实现的方法)时的栈空间。2.JVM利用JIT技术时会将Java代码重新编译为本地代码,编译后的代码会利用本地方法栈追踪方法的执行

内存分配策略

  • 静态内存分配:在程序编译阶段就已经确定每个数据在运行是的存储空间(java中的基本类型、对象引用,递归、嵌套结构会导致编译阶段无法确认程序运行存储空间大小
  • 栈内存分配:程序对数据区的需求在编译时是未知的,只有运行时才能知道,但是在程序入口处 必须知道该程序所需的数据区大小 才能够为其分配内存,栈内存按照先进后出原则进行分配
  • 堆内存分配:只有在程序真正运行到相应的代码时才知道空间的大小

The only way to fix things is to keep reliving them. - Colter Stevens

Source Code