# Java 虚拟机 #
## 1. JVM内存区域与内存溢出异常 ##
### 1.1 运行时数据区和直接内存 ###
运行时数据区包括:程序计数器,JAVA栈,本地方法栈,方法区,JAVA堆。

- **程序计数器** 线程私有,存储线程当前执行的字节码指令的位置,如果执行的是Native方法,则为空值(Undefined)
- **JAVA栈** 线程私有,存储方法执行的过程,请求的栈深度大于虚拟机允许的最大深度时会抛出StackOverflowError,可扩展的栈无法扩展时(内存无可用空间时)会抛出OutofMemeryError
- **本地方法栈** 线程私有,用来执行Native方法,作用和可抛出异常与JAVA栈相同。Hotspot虚拟机将JAVA栈和本地方法栈合二为一
- **方法区** 线程共享,用来存储类的信息(类名,方法名,成员变量名)、常/静态变量和运行时常量池。版本更迭对方法区的变化具体详见下一节[1.2 HotSpot 虚拟机方法区的演变](#1.2 方法区)。无法扩展时也会抛出OutofMemeryError。
- **运行时常量池**(Hotspot1.7已移除,字面量进入java堆中,符号引用进入native堆) 运行时常量池是方法区的一部分,存储字面量和符号引用,可动态扩展如`String.intern`
- **JAVA堆** 线程共享 线程共享,分配对象和数组
- **直接内存** 不是运行时数据区的一部分,提供给Native函数库来直接分配堆外内存,通过`DirectByteBuffer`对象来操作这块内存。直接操作堆外内存好处是,避免了数据flush到远程时Java堆和Native堆来回复制数据,因为JAVA堆中的数据flush到远程时,JAVA堆会先复制到Native堆。
### 1.2 HotSpot 虚拟机方法区的演变 ###
Java1.6 及 1.6 之前,HotSpot 虚拟机中将GC分代收集扩展到了方法区,使用永久代来实现了方法区。此时的永久带中包含类的信息(类名,方法名,成员变量名)、常/静态变量和运行时常量池。
Java1.7 时 HotSpot 将运行时常量区从永久带移除,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
Java1.8 时 HotSpot 彻底没有了永久带,而是将方法区放在一个与堆不相连的本地内存区域,这个区域被叫做元空间,运行时常量池仍然放在 Java 堆中。
### 1.3 Java 堆的内存分配 ###
所有对象实例都在这里分配内存。
是垃圾收集的主要区域("GC 堆"),现代的垃圾收集器基本都是采用分代收集算法,该算法的思想是针对不同的对象采取不同的垃圾回收算法,因此虚拟机把 Java 堆分成以下三块:
- 新生代(Young Generation)
- 老年代(Old Generation)
- 永久代(Permanent Generation)
当一个对象被创建时,它首先进入新生代,之后有可能被转移到老年代中。新生代存放着大量的生命很短的对象,因此新生代在三个区域中垃圾回收的频率最高。为了更高效地进行垃圾回收,把新生代继续划分成以下三个空间:
- Eden
- From Survivor
- To Survivor

###### JVM内存管理参数 ######
- **-Xms** 初始化内存(年轻带+年老带)的大小
- **-Xmx** 内存的最大大小
- **-Xss** 每个线程的栈大小
- **-XX:NewSize** 新生代初始大小
- **-XX:MaxNewSize** 新生代最大大小
- **-Xmn** 新生代固定大小
- **-XX:SurvivorRatio N** 伊甸园区与幸存者区的比例,具体为`Eden : Survivor From : Survivor To = N : 1 : 1`
- **-XX:NewRatio N** 新生代和老年代的比例,具体为`Old : Young = 1 : N`
### 1.4 对象的内存布局和定位 ###
对象的内存布局包括三部分:对象头,实例数据和对齐填充
- **对象头** 对象头包括两部分,第一部分存储 Mark Word 即对象运行时数据(哈希值,GC分带年龄,锁状态,偏向线程ID等),第二部分存储类型指针,用来确定对象属于哪个类,但不是所有虚拟机都必须在对象上存储类型指针,因为不通过对象本身也可以查到对象的元数据。
- **实例数据** 存储对象的各种字段(包括父类的),相同的宽度的字段总是存放在一起
- **对齐填充** 将对象大小填充至8字节的整数倍
###### 对象的访问定位 ######
栈上的数据通过 reference 数据来操作栈中的数据,reference 访问方式有两种:句柄和直接指针
- **句柄** JAVA 堆中划分出一块区域来存储句柄,句柄中存储了对象的数据指针和类型指针。reference 先访问句柄,在通过句柄存储的指针到具体的数据和类型。
- **直接指针** reference 直接存储对象的数据指针,使用直接指针访问模式的堆实例数据,数据中包含了对象类型的指针。
句柄的好处是,当数据地址修改时,只需要修改句柄中的地址,reference 本身不需要修改;直接指针的好处是速度更快。Hotspot 使用的是直接指针的方式访问对象。
###### OOP-KLASS ######
OOP-KLASS 是 HotSpot 中存储对象的一种方式,OOP(Ordinary Object Pointer)普通对象指针,具体使用 instanceOopDesc 和 arrayOopDesc分别描述普通类和数组的对象头;KLASS 使用 instanceKlass 和 arrayKlass 描述对象具体类型。
下图就是 HotSpot 根据引用找到类元信息的示例,先通过引用找到堆中的对象,在根据对象中的 instanceOopDesc 找到类元信息。

## 2. 垃圾收集器与内存分配策略 ##
### 2.1 对象存活判定 ###
- **引用计数法** 基本不使用,无法解决孤岛问题
- **可达性算法** 是否从`GC Roots`可达判定存项是否存活。可以作为`GC Roots`的对象:
- JAVA栈和本地方法栈中的对象
- 方法区的常/静态引用变量
两次标记判定对象是否死亡:
第一次,标记`GC Roots`中不可达的对象,将覆盖了`finalize()`方法且还没有执行过的对象,加入`F-Quene`队列中,否则加入即将回收集合。(`F-Queue`中的对象稍后会有优先级比较低的Finalizer线程执行它们的`finalize()`方法) 第二次,重新对`F-Quene`队列中的对象标记,如果从`GC Roots`重新可达,则移出即将回收的集合,最后仍在即将回收集合的对象判定为死亡。
`finalize()`拯救成功示例:
```java
@Override
protected void finalize() throws Throwable {
super.finalize();
OneGCRoot.someField = this;
}
```
### 2.2 方法区的回收 ###
方法区主要回收两个内容,无用的字面量和无用的类。字面量的回收和对象的回收过程基本相同,类的回收需要以下复杂的判定:
- 类的所有实例已被回收
- 加载该类的ClassLoader已被回收
- 类的Class对象不会被引用,反射不会再访问该类
### 2.3. 引用类型 ###
无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否可被回收都与引用有关。
Java 具有四种强度不同的引用类型。
**(一)强引用**
被强引用关联的对象不会被垃圾收集器回收。
使用 new 一个新对象的方式来创建强引用。
```java
Object obj = new Object();
```
**(二)软引用**
被软引用关联的对象,只有在内存不够的情况下才会被回收。软引用非常适合于创建内存敏感的数据缓存。
使用 SoftReference 类来创建软引用。
```java
Object obj = new Object();
SoftReference