## 运行时区域划分(六块)

### 线程私有的

#### 程序计数器

- 存储当前虚拟机执行**指令的地址**

- 如果执行的是native方法,则为空

- 作用

- 通过改变**程序计数器**来依次**读取指令**,从而实现代码的**流程控制**,如分支,循环

- **多线程**环境下,用于**保存当前**线程的执行**位置**,以便被切换回来时直到自己执行到哪了

- 注意:**程序计数器是唯一一个不会出现OOM的区域**

#### Java虚拟机栈

- 线程调用方法时会创建一个**栈帧**,方法调用就对应着栈帧在虚拟机栈中的出栈和入栈

- 栈帧中存放的数据

- 局部变量表(一些基本数据类型和对象引用)

- 操作数栈

- **常量池引用**

- **方法出口**

- 可能抛出的异常

- 超过栈最大**深度**

- 栈**扩展**时无法**申请**到足够内存 OOM

- 参数调节:**-Xss(stack size)** 512M :指定虚拟机栈大小

- 注意:数组引用存放在虚拟机栈中,但是数组的元素是存放在堆中

#### 本地方法栈

与虚拟机栈类似,但是只为**本地方法**(native)服务

### 线程共有的

线程共有区域不需要连续内存

#### 堆

- 所有对象都在这里分配

- 新生代

- 老年代

- 参数调节

- **-Xms 1M:初始值**

- **-Xmx 512M:最大值**

#### 方法区

- 方法区也被称为非堆

- 存放的信息

- 已被加载的**类信息**

- **即时编译器**编译后的代码

- **常量**

- **静态变量**

- 1.8之后**移除了永久代**,并将方法区移到了**元空间**,它位于本地内存中,不属于虚拟机内存

- 移出永久代的原因:因为很难**确定永久代的大小**,在每次Full GC之后永久代都会改变,**很容易发生OOM**

- 其他JVM并没有永久代这个概念,hot spot虚拟机把方法区当作老年代来回收

- 方法区中有一块特殊的内存--**运行时常量池**

- 在类被加载后放入

- **编译时**生成的常量

- **运行时**生成的,如String.intern()

- 运行时常量池在1.8之后被移到了**堆**上

#### 直接内存

- 堆外内存

- 通过堆中的**DirectByteBuffer**对象引用进行操作,避免数据来回复制,提高效率

- 针对**NIO**类

## 对象回收的方法

### 线程私有

线程私有的区域随线程结束而结束

### 线程共有

#### 引用计数器算法

- 没有被JAVA引用

- 针对对象引用

- 此对象多一个引用时计数器+1

- 为0时回收

- 会出现**循环引用**的问题

#### **可达性分析算法**

- 通过GC ROOTS 作为**起始**点,不可达的对象都可以回收

- **可以作为GC ROOTS的对象**

- 虚拟机中**局部变量表**中引用的对象

- 本地方法栈中**JNI**引用的对象, c++ JNI JAVA

- 方法区中**常量 / 静态变量**引用的对象

#### 方法区的垃圾回收

- 主要是卸载**废弃常量**和**无用的类**

- 判断是否是废弃常量?--没有任何对象引用常量池中的对象

- 判断无用的类?

- 该类的**所有实例都被回收**,即JAVA**堆**中不存在任何此类的实例

- 该类对应的java.lang,class没有在任何地方被引用,**无法在任何地方通过反射调用该类的方法**

- 加载该类的**classLoader被回收**

#### finalize()

可以在代码中显示的调用对象的finaleze()方法对类进行卸载,但是不建议这么做,JVM做的更好

## Stop The World

- 目的:为了**避免回收到不该回收的对象**,在一些地方设置“安全点”

- 由GC引起

- 全局暂停,JAVA代码停止运行,但是native方法可以运行,但是不能与JVM进行交互

- stop the world的位置

- 循环的末尾

- 方法临返回前

- 调用方法的**call指令后**

- 可能抛出异常的位置

## JAVA当中的引用(与JVM对象回收有很强的关系)

### 强引用

- 通过new 对象的方式创建

- 被强引用关联的对象**不会被回收**

### 软引用

- 在**内存不足**的时候才回被回收

### 弱引用

- 被弱引用关联的对象**一定会被回收**

- 只能存活到下一次GC之前

### 虚引用

- 无法通过虚引用得到一个对象

- 与其生命周期无关

## 垃圾回收算法(四种)

### 复制算法

- **新生代**

- 分为三个区域

- Eden:使用

- from Survivor:互相备份

- to Survivor:互相备份

- 比例: 8:1:1

- 效率:90%

- 问题:剩余对象可能 > 10%,就需要老年代进行担保

### 标记-清除

- **老年代**

- 标记需要**回收**的对象

- 缺点

- **效率**低

- **内存碎片**,导致无法给大对象分配空间

### 标记-整理

- **老年代**

- 标记**存活**的对象

- 将存活对象移动到一边,删除**边界**外的对象

### 分代收集算法

- 将**内存分块**,不同代用不同的收集算法

- 新生代:复制

- 老年代:标记-消除,标记-整理

## 垃圾收集器(七种)

垃圾收集器主要分为三大类

- 串行收集器

- 并行收集器

- **并发标记收集器**(CMS)

### Serial

- **串行**

- **单线程**

- **新生代**

### Serial Old

- Serial的**老年代**版本

### parNew

- **Serial多线程版本**

- **新生代**

### parallel sanvenge

- 和parNew一样的多线程收集器

### parallel Old

- parallel老年代版本

### **CMS**

- **标记-清除**算法

- 专门收集老年代,但是必须扫描整个老年代

- 过程

- 初始标记(仅仅是标记一下GC Roots的关联对象,**需要停顿**)

- 并发标记 (进行GC Roots tracing的过程,是时间最长的一个过程,**不需要停顿**)

- **重新标记** (需要排除那些标记变动的对象,**需要停顿**)

- 并发清除 (**不需要停顿**)

- 在并发标记和并发清除的过程中允许垃圾收集线程和用户线程并行执行,所以缺点是CPU占用比较高

- 缺点

- **吞吐量率低**,他较低的停顿时间是以牺牲吞吐量获得的

- **CPU占用高**

- 它所用的标记-清除算法会导致较多的**内存碎片**

- 无法处理**浮动垃圾**(浮动垃圾指的是在并发标记期间用户线程又产生的那部分垃圾)

### G1

- 目的:替换掉CMS

- 适用:多CPU,大内存

- 区别

- 别的收集器都是只针对新生代或老年代,但**G1可以新生代老年代一起回收**

- 把内存空间**分为多块**,使得每块空间(region)都可以进行回收,并且使得新生代老年代不再物理隔离

- 可以**预测**回收某一块所需的时间(通过过去的经验),从而设置**优先级**

- 通过记录region,可以在可达性分析时**不必全表扫描**

- **不会产生内存碎片**

- 步骤

- 初始标记

- 并发标记

- **最终标记**:主要是排除因为程序运行而变动的标记

- 筛选回收:通过region优先级排序,选择回收价值最大的

- **收集模式**

- Young GC (新生代垃圾收集)

- **Mixed GC** (混合垃圾收集)

- 对比

- Young GC只收集新生代

- Mixed GC 收集老年代和新生代

- full GC 会暂停整个引用,对新生代和老年代一起回收

- 特点

- 将堆分区(region),物理上**不需要连续**

- G1名字的由来:首先收集垃圾最多的区域,**优先级**

- 和CMS一样,可以并发标记

- 从整体上看是标记-清除算法,从局部(两个region之间)上看是复制算法,所以**不会产生内存碎片**

- 可以**预测停顿时间**

## GC分类(三种)

### minor GC

回收新生代,新生代存活时间短,回收很频繁,执行的速度也比较快

### major GC

回收老年代

### full GC

新生代和老年代一起回收

## JVM内存分配策略

#### 对象优先在Eden区分配

- eden区不足,会发起minor GC

#### 大对象直接进入老年代

- 大对象指的是需要**连续内存空间**的对象,例如很长的String和数组

- 很可能会触发垃圾回收

#### 长期存活的对象进入老年代

- 在Eden出生

- **经过minor GC**

- 移动到**Survivor**区一次,年龄+1,到一定年龄(15)就移入老年代

#### 年龄动态判定

- **Survivor**区

- 相同年龄对象**总数大于**Survivor区的**一半(50%)**,则将大于等于的移入老年代

#### **空间分配担保**

- 在minor GC之前,先判断允不允许**担保**失败,允许则进行minor GC

- 否则进行 **full GC**

## GC 触发条件

### minor GC触发条件

- eden区不足

### full GC触发条件

- **调用System.gc()**

- 老年代空间不足

- **空间分配担保失败**

- **分配大对象**时可能会执行full GC(其实也是老年代空间不足)

## 类加载过程(五步)

#### 加载(双清委派模型)

- 通过类的**全限定名**获取类的**二进制文件**

- 来源

- 磁盘上的.class文件

- jar,war,.zip文件

- 网络流

- 将这个字节流所代表的**静态存储结构**转化为**方法区**的运行时存储结构。

- 在内存中生成一个代表这个类的class对象,并且将他作为方法区中这个类各种数据的入口

### 验证

验证这些字节码文件符不符合虚拟机要求,会不会对虚拟机造成危害

### 分配空间和设置初始值

- **静态变量**在**方法区**中分配空间,并设置**初始值**,只初始化类对象,即static修饰的成员,实例变量不在这个阶段分配,他是在实例化的时候被分配到堆中

- 初始值并不是我们代码里写的值,例如int 初始值是0

- **final**修饰的初始值为代码里的值

- 分配空间的方式(取决于使用**哪种收集算法**)

- 指针碰撞:标记-整理(指针即分界值指针)

- 空闲链表:标记-清除(记录哪一块区域是可用的)

### 解析(设置对象头)

**将常量池中的符号引用替换为直接引用(为了支持java的动态绑定)**

### 初始化

- **执行类的构造器方法**,这个时候才真正执行我们的代码

- 代码执行顺序

- static代码块: 只在类加载时执行一次

- 代码块{}:在每次新建对象的时候执行一次,在**构造函数之前**

- 声明了多个静态代码块/变量:按照声明顺序加载

- 顺序

- 父类static块

- 子类static块

- 父类{}块

- **父类构造函数**

- 子类{}块

- **子类构造函数**

## 类初始化时机

- **new()**的时候

- 调用类的static方法的时候

- 对类进行**反射调用**的时候

- 调用子类,必须先初始化父类

,注意:子类调用父类的static方法,子类不会初始化

## new String("abc)创建了几个对象

共两个:拆分:abc , new String()

先有字符串"abc"放入常量池,然后 new 了一份字符串"abc"放入Java堆(字符串常量"abc"在编译期就已经确定放入常量池,而 Java 堆上的"abc"是在运行期初始化阶段才确定),然后 Java 栈的 str1 指向Java堆上的"abc"。

- abc: 先去方法区的常量池里找,找到则返回一个引用,这种情况只产生**一个对象**,没有则在方法区创建一个

- new String():new肯定是创建了一个对象,是在堆上创建

## 类加载

### 加载器

- 启动类加载器

- 是由 **C++** 语言编写

- 最顶层加载器

- 如果某个类的加载器为**null**,说明他的加载器是是启动类加载器

- 加载核心包,如tr.jar,resource.jat,chart_set.jar等

- 扩展类加载器

- 加载javax.xxx的类,如javax.util

- 应用程序加载器

- 这个就是面对开发者的加载器

- 加载classpath下的类

- 所有的**main()**及自己编写的类其实都是由应用程序加载器加载的

### 双亲委派模型

- 实现

- 从下至上检查类有没有被加载

- 从上往下尝试加载,只有加载不出才会让原有类加载,要是原有类也不能加载,就会报:classNotFoundException

- 原因:为了使类**不被重复加载**

## Class.forName()和ClassLoader区别

- 都是用于加载类的

- class.forname()中**第二个参数为true时**,不仅仅加载.class文件,还会**对类进行解释,执行类中的static块**

- class.forname()可以指定由哪个加载器加载

- 而classLoader只负责把类加载到JVM

- class.forname其实也是**调用classLoader**实现的

- IOC使用的是classLoader

## 如果出现OOM如何排查与解决

- 设置参数

- JAVA堆内存溢出:设置参数大小,-Xms(最大堆),-Xmx(最小堆)

- JAVA虚拟机栈溢出:

- 检查代码中是否有**死循环**

- 代码中是否有较深层次的递归调用

- 是否在循环内不断的new对象

- 设置 -Xss参数(虚拟机栈大小)

- 可以利用一些内存分析可视化工具来dump出堆内存信息进行分析

## 内存溢出和内存泄露区别

- 内存**溢出(OOM)**指的是应用程序在申请内存时,没有足够的内存供其使用

- 内存**泄露**是指:应用程序申请了空间,但是**无法释放**已经申请的空间(所以这块内存也就不可用了),**最后还是会导致OOM**,最后导致内存消耗殆尽

## JVM 调优常用命令
- **jps**:查看系统内虚拟机进程
- jstat:查看虚拟机运行**状态**的信息,比如:类加载,内存,垃圾收集等
- **jmap**:用于生成**head dump**文件,还可以查看堆和永久代的信息,比如使用率和使用的是哪种垃圾收集器
- jhat:和jmap配合使用,来分析dump出的文件
- **jstack**:生成虚拟机当前时刻的**线程快照**,包含每条线程执行的堆栈集合
- **jinfo**:可以**实时**查看和调整虚拟机运行**参数**