JVM
1、什么是 JVM 内存结构?
JVM 将虚拟机分为5大区域,程序计数器、虚拟机栈、本地方法栈、java堆、方法区;
- 程序计数器:线程私有的,是一块很小的内存空间,作为当前线程执行的字节码的行号指示器,用来存储下一条将要执行的字节码指令的地址;
- 虚拟机栈:线程私有的,每个线程在创建时都会创建一个虚拟机栈,虚拟机栈由许多栈帧组成,每个方法被执行的时候,JVM 都会同步创 建一个栈帧,用于存储局部变量表、操作数、动态链接和方法返回等信息。方法执行时,栈帧入栈,执行完成后,栈帧出栈。当线程请求的栈深度超过了虚拟机允许的最大深度时,就会抛出StackOverFlowError;当创建线程时没有足够的栈空间,会抛出 OOM 异常。
- 本地方法栈:线程私有的,保存的是native方法的信息,当一个
JVM创建的线程调用native方法后,JVM不会在虚拟机栈中为该线程创建栈帧,而是简单的动态链接并直接调用该方法; - 堆:java 堆是所有线程共享的一块内存,几乎所有对象的实例和数组都要在堆上分配内存,因此该区域经常发生垃圾回收的操作;
- 方法区:是各个线程共享的内存区域,用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据。在jdk1.8中不存在方法区了,被元数据区替代了,原方法区被分成两部分;1:加载的类信息,2:运行时常量池;加载的类信息被保存在元数据区中,运行时常量池保存在堆中;元数据区使用的是本地内存。
- 为什么需要程序计数器呢?
- 通过修改程序计数器的值来确定下一条应该执行什么样子的指令。
- 为什么程序计数器是线程私有的?
- 为了准确记录各个线程正在执行的当前字节码指令的地址,采用线程独立的设计可以独立计算,不会出现相互干扰的情况。
2、什么是 Java 内存模型?
Java 内存模型规定了变量的访问规则,保证了操作的原子性、可见性、有序性。
3、heap 和 stack 有什么区别?
先介绍下堆和栈分别是什么,有什么作用。
虚拟机栈:线程私有的,每个线程在创建时都会创建一个虚拟机栈,虚拟机栈由许多栈帧组成,每个方法被执行的时候,JVM 都会同步创建一个栈帧,用于存储局部变量表、操作数、动态链接和方法返回等信息。方法执行时,栈帧入栈,执行完成后,栈帧出栈。当线程请求的栈深度超过了虚拟机允许的最大深度时,就会抛出StackOverFlowError;当创建线程时没有足够的栈空间,会抛出 OOM 异常。
堆:java 堆是所有线程共享的一块内存,几乎所有对象的实例和数组都要在堆上分配内存,因此该区域经常发生垃圾回收的操作;
- 栈线程私有,堆线程共享
- 存储的内容不同,栈存储的是栈帧,堆存储的是对象。
- 对象的申请方式不同,栈是 jvm 自动生成,堆需要程序主动申请
- 申请的效率不一样,栈是由系统自动创建,效率较高,堆由 new 分配的内存,速度一般较慢。
- 申请后系统的响应不同,栈是有空间就分配,没空间就提示栈溢出。堆分配内存有两种方式,指针碰撞和空闲列表。
- 指针碰撞:Java 堆的内存是规整的,使用的内存放在一边,未使用的内存放在另一边,中间使用一个指针进行分隔,当分配内存时,指针向空闲内存方向移动一个等于对象内存大小的距离。
- 空闲列表:Java 堆的内存不是规整的,JVM 采用一个列表来记住内存空闲节点,在分配内存时找到一个足够大内存节点,将其分配给程序,并更新空闲列表。
- 栈内存比较小,堆内存比较大
- 栈不存在垃圾回收,堆需要进行垃圾回收。 栈和堆都会出现内存溢出的问题。
4、什么情况下会发生栈内存溢出?
栈是线程私有的,它的生命周期和线程一样,随着线程的启动而创建,在线程终止时销毁。每个方法在执行时会创建一个栈帧,用于存放局部变量表、操作数、动态链接、方法返回等信息。
- 当线程请求的栈深度超过
JVM允许的最大深度时会抛出StackOverFlowError异常。 一般是递归的时候出现该问题。可以使用-Xss来设置JVM栈的大小。- 当创建线程时,如果栈的内存不足,会抛出 OOM 异常。
5、谈谈对 OOM 的认识?如何排查 OOM 的问题?
StackOverFlowError:递归调用、方法内部定义了大量的局部变量导致栈空间不足。 可以通过
-Xss来设置 JVM 栈的大小。
Java heap space:创建一个超大的对象、大量对 象引用没有释放,JVM 无法对其自动回收,通过-Xmx来设置JVM堆内存空间
GC overhead limit exceeded:JVM 花费了 98% 以上的时间执行 GC ,但只恢复了不到 2% 的内存就会报错。
Direct buffer memory:通过 native 方法直接分配对堆外内存,直接内存溢出。
Unable to create new native thread:不能创建更多的线程了
Metaspace: 元空间不足
Requested array size exceeds VM limit
Out of swap space
kill process or sacrifice child
排查 OOM 的方法:
- 增加两个参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof,当 OOM 发生时自动 dump 堆内存信息到指定目录;
- 同时 jstat 查看监控
JVM的内存和 GC 情况,先观察问题大概出在什么区域; - 使用 MAT 工具载入到 dump 文件,分析大对象的占用情况,比如 HashMap 做缓存未清理,时间长了就会内存溢出,可以把改为弱引用 。