第二章 Java内存区域与内存异常

1. 运行时数据区

1.1 程序计数器(Program Counter Register):

线程私有,当前线程所执行的字节码行号的指示器,唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError的区域。
如果正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址,如果是Native 方法,这个计数器值则为空(Undefined)。

1.2 Java虚拟机栈(VM Stack):

线程私有,生命周期与线程相同;服务于字节码,虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接,方法出口等信息。
64位长度long和double 类型占用2个局部变量表,其余数据类型只占一个。
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError; 如果虚拟可以动态扩展,扩展申请不到足够的内存,就会抛出OutOfMemoryError.

1.3 本地方法栈(Native Method Stack):

线程私有,与虚拟机栈作用类似,服务于Native 方法。
同样会有OutOfMemoryError和StackOverflowError。

1.4 Java 堆(Head):

GC堆,分为新生代和老年代,新生代又分为Eden、Survivor From 、Survivor To,Head 物理上可以是不连续的内存空间,只要逻辑上连续即可。
-Xmx 最大堆大小 -Xms 最小堆内存大小
一般情况设置一样,避免频繁扩容和GC释放堆内存造成的系统开销/压力。

1.5 方法区(Method Area):

线程共享,用于存储被虚拟加载的类信息、常量、静态变量、即时编译后的代码等。运行时常量池是方法区的一部分,用于存放类的版本、字段、方法、接口,等描述信息,以及常量池。JDK 1.8后方法区被元空间(MetaSpace)替代, 常量池从方法区移到了堆区。
无法分配内存时会抛出OutofMemoryError。

1.6 直接内存:Unsafe 、NIO

1.7 对象内存布局:

Hotspot 对象存储的布局分为:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。对象头包含两部分信息,运行时数据,如Hashcode、GCf分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,统称Mark Word ;另一部分则是对象指针,即对象指向它的类元数据的指针。虚拟机通过这个指针确定这个对象是那个类的实例。

1.8 4种类型引用:

强引用:Object obj = new Object()强引用存在,垃圾收集器就不会回收被引用的对象。
软引用:用来描述一些还有用但非必需的对象。内存不够用了,那么软引用对象就会被回收。
弱引用:也是用来描述一些非必需的对象,只要遭遇gc就被回收。
虚引用:就干一件事,管理堆外内存用的。

第三章 垃圾收集器和内存分配策略

3.1 如何判断对象可回收

3.1.1 引用计数法:

给对象中添加一个计数器,每当引用的地方就加1,引用失效时就减1,计数器的值为0就可以被回收。
缺陷:难以解决对象间相互循环引用问题
ObjectA a = new ObjectA();
ObjectB b = new ObejctB();
a.instance = b;
b.instance = a;

3.1.2 可达性分析算法:

通过一系列GC Roots的对象作为起始点,向下搜索,所走过的路径称为引用链,当一个对象到GC Roots 没有任何引用链时,则该对象不可用,判定为可回收对象。
GC Roots 规定如下:
虚拟机栈(栈帧中本地变量表)中引用的对象
方法区中静态属性引用的对象
方法区常量引用的对象
本地方法栈中引用的对象

3.1.3 方法区回收

永久代(元空间)垃圾回收主要包含两部分 废弃常量和无用的类
废弃常量 于对象判断类似
判断类是否是无用的类
该类所有的实例都被回收
加载该类的ClassLoader 已经被回收
该类的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

3.2垃圾回收算法

3.2.1 标记-清除算法:

算法分为标记和清除两个阶段,先标记出所有需要回收的对象,标记完成后进行统一回收。
缺陷:标记和清除效率都不高;标记清除后会存在大量不连续的内存碎片,无法分配大对象就会再次触发垃圾回收。

3.2.2 复制算法:

内存分为大小相等的2块,每次只使用其中一块,这块用完了,就将把存活的对象复制到另一块内存上,使用过的内存一次清理掉。

3.2.3 标记-整理算法: