天天看点

Java虚拟机学习笔记(一)——内存区域与内存溢出

一:Java虚拟机的内存区域

Java虚拟机在执行Java程序时,会将管理的内存分为若干区域

  • 方法区
  • 虚拟机栈
  • 本地方法栈
  • 程序计数器

1. 程序计数器

(1)概念

当前线程所执行的字节码的行号指示器

  • 如果线程当前执行的是Java方法,则记录当前执行的字节码指令的地址
  • 如果线程当前执行的是Native方法,则为空

(2)作用

字节码解释器的工作就是通过改变该计数器的值,选取下一条需要执行的字节码指令

(3)特点

  • 线程私有:由于Java虚拟机的多线程是通过线程轮流切换并分配处理器的执行时间的方式实现的,为了线程被切换后能恢复到正确的执行位置,每个线程都要有一个独立的程序计数器
  • 唯一没有规定任何OutOfMemoryError内存溢出异常情况的区域

2. Java虚拟机栈

(1)概念

每个Java方法的执行,都会创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口。方法的调用和执行完成的过程,就是栈帧进栈和出栈的过程

局部变量表

内容

存放编译期可知的各种基本数据类型、对象引用、returnAddress类型(指向一条字节码指令的地址)

内存空间

在编译期完成分配,方法的局部变量表是固定的,不会改变

  • long和double类型的数据会占用2个局部变量空间
  • byte,boolean,char,short,int,float占用1个局部变量空间

(2)特点

  • 线程私有
  • 生命周期与线程相同

(3)异常

  • 线程请求的栈深度>虚拟机允许的深度:StackOverflowError栈溢出异常
  • 虚拟机栈可以动态扩展,扩展到无法申请内存:OutOfMemoryError内存溢出异常

3. 本地方法栈

(1)概念

每个Native方法的执行,都会创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口。方法的调用和执行完成的过程,就是栈帧进栈和出栈的过程

(2)特点

  • 线程私有
  • 生命周期与线程相同

(3)异常

  • 线程请求的栈深度>虚拟机允许的深度:StackOverflowError栈溢出异常
  • 虚拟机栈可以动态扩展,扩展到无法申请内存:OutOfMemoryError内存溢出异常

4. Java堆

(1)概念

存放对象实例,所有的对象实例和数组都要在堆上分配

(2)特点

  • 被所有线程共享的一块内存区域,在虚拟机启动时创建。
  • 是垃圾收集器管理的主要区域,也被称为GC堆
  • 可以处于物理上不连续的内存空间中

(3)异常

堆的内存没有完成实例分配,并且堆无法扩展:OutOfMemoryError内存溢出异常

5. 方法区

(1)概念

用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

运行时常量池
  • 存储编译期产生的常量:Class文件中的常量池(编译期生成的各种字面量和符号引用)
  • 存储翻译后的直接引用
  • 存储运行期产生的常量

(2)特点

  • 被所有线程共享的一块内存区域,在虚拟机启动时创建。
  • 可以处于物理上不连续的内存空间中
  • 可以不被垃圾收集(垃圾主要是对常量池的回收和对类型的卸载)

(3)异常

方法区的内存没有满足需求,并且无法扩展:OutOfMemoryError内存溢出异常

6. 直接内存

直接内存不属于虚拟机运行时数据区,但是由于会被忽略,导致在动态扩展内存时,出现OutOfMemoryError内存溢出异常

二:Java虚拟机的对象

1. 对象的内存区域

Object obj = new Object();
           
  • Object obj:反映到Java栈的本地变量表中,作为一个reference引用类型数据出现
  • new Object():
    • 反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,存储在实例池中
    • 反映到方法区中,存储Object类型的父类、接口、方法等的地址

2. 对象的访问

(1)使用句柄

  • Java堆划分出一块内存作为句柄池,存储句柄
  • Java栈中本地变量表的reference引用类型存储的就是对象的句柄地址
  • 句柄中包含了对象的实例数据的具体地址信息(实例池)和类型数据的具体地址信息(方法区)

特点:在对象被移动时只会改变句柄的实例数据指针,reference本身不需要修改

(2)使用直接指针

  • Java堆存储对象的类型数据+实例数据
  • Java栈中本地变量表的reference引用类型直接存储对象地址
  • 指针指向方法区中的对象类型数据

特点:速度更快,节省了一次指针定位的时间开销

三:内存溢出

1. 堆溢出

  • Xms内存大小:设置堆的大小
  • Xmx内存大小:设置堆的最大值

解决:

  • 内存泄露:查看泄露对象到GC Roots的引用链
  • 内存溢出:检查虚拟机的堆参数能否增加,检查程序的对象生命周期能否缩小

2. 栈溢出

  • Xoss内存大小:设置本地栈大小
  • Xss内存大小:设置栈内存大小

解决

  • 单线程:栈溢出
  • 多线程:内存溢出。减少最大堆和栈的容量

3. 方法区溢出或运行时常量池溢出

  • XX:PermSize=内存大小:设置方法区大小
  • XX:MaxPermSize=内存大小:设置最大方法区

解决

在经常动态生成大量Class的应用中,需要注意类的回收

4. 本机直接内存溢出

  • XX:MaxDirectMemorySize:设置直接内存的大小