1.JVM内存模型

分为两大类:线程共享和线程私有。
线程私有:程序计数器,这是一个比较小的内存区域,它占用内存比较少,也是唯一一块不会发生OOM的区域,他的作用就是用来表明当前程序的运行的行数,线程进行上下文切换的时候,能够知道当前程序的走向。另外两个就是虚拟机栈和本地方法栈,他的执行单位是栈帧,方法的调用就是栈帧的入栈和出栈的过程。
线程共享:方法区,他是JVM的一个规范,1.7的实现是永久代,存放的是常量池还有类的元数据信息;1.8的实现是元空间,直接使用到内存空间,存放的是常量池还有类的元数据信息;会发生OOM。还有一个就是堆,是JVM中占用比例最大的一个空间,主要用来存放运行时动态生成的对象,这个地方会发生OOM。
运行时常量池,在1.8中搬到了堆内存中存储,之前是在永久代存储的。用于存放编译期生成的各种字面量和符号引用
直接内存,XXXX

2.OOM的排查过程

可以使用linux的top命令查看占用资源比较高的进程,然后通过java的jps命令查看占用资源较高的线程,然后用jstack命令查看栈内存的信息。
也可以通过jvisualvm工具来查看堆的详细信息,可以dump下来堆的信息。dump会出来hprof快照文件,可以使用mat工具分析一下。
或者可以添加JVM参数 -XX:+HeapDumpOnOutOfMemoryError,添加参数后OOM的时候会自动dump,可以直接进行分析。

3.垃圾回收器和垃圾回收算法

垃圾回收器

  1. Serial 垃圾收集器(单线程、 复制算法)
  2. ParNew 垃圾收集器(Serial+多线程)
  3. Parallel Scavenge 收集器(多线程复制算法、高效)
  4. CMS 收集器(多线程标记清除算法)
  5. G1 收集器

    垃圾回收算法

  6. 复制算法

按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉

  1. 标记清除

标记阶段标记出所有不可达的对象,清除阶段回收被标记的对象所占用的空间。
标记不可达:引用计数器,废弃;可达性分析,通过一系列GC roots的对象为起点开始进行搜索,如果在GC ROOTS到这个对象之间没有可达的路径,那么证明这个对象是不可达的。
GC ROOTS的对象:虚拟机栈引用的对象,方法区静态变量引用的对象,常量引用的对象,

  1. 标记整理

因为标记清除算法会产生内部碎片,所以创造了这个算法,就是先将要回收的对象进行一个标记,然后将存或的对象移动到内存的一段,然后清除外界的对象。

4.运行时常量池

运⾏时常量池是⽅法区的⼀部分。Class ⽂件中除了有类的版本、字段、⽅法、接⼝等描述信息外,还有常量池信息(⽤于存放编译期⽣成的各种字⾯量和符号引⽤)既然运⾏时常量池时⽅法区的⼀部分,⾃然受到⽅法区内存的限制,当常量池⽆法再申请到内存时会抛出 OutOfMemoryError 异常。
JDK1.7及之后版本的 JVM 已经将运⾏时常量池从⽅法区中移了出来,在 Java 堆(Heap)中开辟了⼀块区域存放运⾏时常量池。

5.直接内存

直接内存并不是虚拟机运⾏时数据区的⼀部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使⽤。⽽且也可能导致 OutOfMemoryError 异常出现。
JDK1.4 中新加⼊的 NIO(New Input/Output) 类,引⼊了⼀种基于通道(Channel) 与缓存区(Buffer) 的 I/O ⽅式,它可以直接使⽤ Native 函数库直接分配堆外内存,然后通过⼀个存储在Java 堆中的 DirectByteBuffer 对象作为这块内存的引⽤进⾏操作。这样就能在⼀些场景中显著提⾼性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。
本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存⼤⼩以及处理器寻址空间的限制。