《深入理解JAVA虚拟机》个人笔记

个人笔记

JVM复习大纲

1.Java内存区域:

线程私有:程序计数器、虚拟机栈、本地方法栈

线程共享:堆,方法区

程序计数器:较小的内存区域,当前执行字节码的行号指示器

虚拟机栈:描述的是Java方法执行的内存模型。每一个方法的执行的同时会创建一个栈帧,方法执行到调用完毕代表着栈帧入栈到出栈的过程,栈帧保存着局部变量表、操作数栈、动态链接、方法出口等信息

本地方法栈:和虚拟机栈作用几乎一致,但是是为JNI方法服务的

堆:最大的内存区域,唯一目的是保存对象实例。从内存回收的角度可以分为新生代和老年代。Java堆可以处于物理上不连续的内存空间中,只要求逻辑连续即可。

方法区:用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。运行时常量池属于方法去一部分。这部分内容在类加载后进入方法区运行时常量池中存放。

2.HotSpot对象创建:

  1. 类加载检查;

  2. 分配对象内存(指针碰撞、空闲列表),确保并发安全是通过CAS+失败重试的方式确保操作原子性,或者可以通过分配TLAB的方式;

  3. 初始化零值,保证了对象的实例字段不用赋初始值就可直接使用;

  4. 对对象进行必要设置;

  5. 执行<init>方法。

3.对象的内存布局:

布局:对象头 实例数据 (对齐填充 8字节整数倍)

对象头:
  1. 用于存储对象自身的运行时数据,如GC分代年龄、哈希码、锁状态标识、线程持有的锁、偏向线程id、偏向时间戳等;

  2. 类型指针。对象指向它类元数据的指针;

  3. 如果是数组,还需要有记录长度的数据。

对象的访问定位:
  1. 使用句柄

  2. 直接指针(hotspot使用该种)

4.垃圾收集器与内存分配策略:

对象已死吗:

1.引用计数算法: 无法解决相互循环引用的问题

2.可达性分析:通过”GC ROOTS“作为起始点,向下搜索,走过的路径叫做引用链。如果一个对象到GC ROOTS没有任何引用链,则证明该对象不可用。

可作为GC ROOTS的对象:

  1. 虚拟机栈引用的对象

  2. 方法区类静态属性引用的对象

  3. 方法区常量引用的对象

  4. JNI引用的对象

再谈引用:

强引用:new 一类

软引用:只有内存溢出发生之前才会列入回收范围

弱引用:只能生存到下一次GC发生之前

虚引用:唯一目的是能在这个对象被收集是发出系统通知

回收方法区:

无用类判定条件:

  1. 该类的全部实例已经被回收

  2. 加载该类的classLoader已经被回收

  3. 该类对象的Class对象没有被任何地方所引用

垃圾收集算法:

1.标记-清除

标记需要回收对象,然后统一回收。缺点会产生内存碎片。

2.复制

将可用容量分为两个大小相等的两块,每次只使用一块。当一块内存使用完了,就将还存活的对象移动到另一块上,然后清除本块。

HotSpot Eden和Survivor 默认大小比例8:1,Eden 和 from Survivor 移动到 to Survivor。然后清除Eden和from Survivor。当Survivor不够用时,会进行分配担保。

3.标记-整理

过程与标记清除一样,但是后续过程是让所有存活对象移动到一端,然后清除掉边界外内存。

4.分带收集

根据对象存货的周期将内存划分为几块,一般是分为新生代老年代,新生代使用复制,老年代使用标记整理或者标记清除。

HotSpot的算法实现:
枚举根节点:

当STW时,虚拟机应当有办法知道哪里存放着对象引用,HotSpot的实现是用OopMap来达到目的。在类加载完成的时候,就把对象内什么偏移量是什么类型的数据计算出来,在JIT编译过程中,也会在特定位置记录那些位置是引用。

安全点:

在OopMap的协助下,可以快速准确的完成gc roots枚举。

但是引出一个新的问题 引用关系可能发生变化,即OopMap随内容变化的指令非常多

前面已经提到,只有在某些位置记录信息,这些位置称为安全点。 程序执行时并非在所有的地方停顿下来开始GC,只有在安全点才能暂停。

选定标准:是否具有让程序长时间执行的特征

另一个需要考虑的问题是如何让所有线程都跑到安全点,两种方案:抢先式中断(淘汰),主动式中断(轮询的方式)。

安全区域:

一段代码片段中引用关系不会发生变化,在这个区域任何GC都是安全的。

垃圾收集器:
CMS:

追求最小停顿时间的垃圾收集器,基于标记—清除

过程:

  1. 初识标记 STW 标记一下Gc Roots能关联的对象

  2. 并发标记 GC ROOTS Tracing

  3. 重新标记 STW 修正并发标记期间记录变动的部分

  4. 并发清除

缺陷:

  1. 对CPU资源敏感,降低总吞吐量,默认启动线程数是(CPU数 +3)/4。

  2. 无法处理浮动垃圾,可能出现“Concurrent Mode Failure”而导致另一个FULL GC。因为这个缘故,CMS不能像其他收集器一样,等老年代几乎满了之后再收集,在JDK1.6以后,启动阈值是92%。如果CMS运行期间预留的内存无法满足程序要求,就会“Concurrent Mode Failure”。启动Serial Old收集器来重新进行收集。

  3. 基于标记—清除,会有大量空间碎片产生。可以使用参数来开启Full GC内存碎片的合并整理过程。或者设置执行多少次Full GC后紧接着一次带压缩的。

G1:

特点:并行与并发 分代收集 空间整合 可预测停顿

使用G1收集器Java堆被划分为多个大小相等的region。G1跟踪每个region的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的region。

引来的问题:region不可能孤立的。一个对象分配在某个region内,他并非只能被本region其他对象引用,而是可以与整个堆发生引用关系。这样做可达性分析的时候,岂不扫描整个java堆才能保证准确性?

解决:Region之间的对象,包括其他收集器新生代与老年代的对象引用,都是通过Remembered Set来避免全堆扫描。虚拟机发现程序对引用类型进行写操作时,会暂时中断写操作,检查引用的对象是否出于不同的region。如果是,便通过Card Table把相关引用信息记录到被引用对象的Region的Remembered Set之中。

过程:

  1. 初试标记 STW

  2. 并发标记

  3. 最终标记 STW

  4. 筛选回收 STW

内存分配与回收策略:
  1. 优先分配到Eden

  2. 大对象直接进老年代

  3. 长期存活对象直接进入老年代 15岁

  4. 动态对象年龄判定 如果在Survivor空间中相同年龄所有对象大小的总和大于总空间一半,那么直接进入老年代

空间分配担保:

Minor GC前,会检查老年代最大可用空间是否大于新生代所有对象空间。如果否,会查看是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋级对象的平均大小。如果大于会尝试进行一次Minor GC。

5.类加载过程:

Java类生命周期为: 加载 验证 准备 解析 初始化 使用 卸载

类加载过程包含 加载 验证 准备 解析 初始化五个节点

加载:·

主要任务:

  1. 根据类的全限定名获取该类的二进制字节流

  2. 将该字节流的静态存储结构转化为运行时的动态数据结构

  3. 在内存中生成该类的Class对象

验证:

验证字节流是否符合JVM要求

文件格式验证

验证字节流是否符合class文件格式的规范

验证点:魔数验证、版本验证、常量验证

元数据验证

对元数据进行语义校验

验证点:父类验证、是否继承final、是否实现抽象方法

字节码验证

通过数据流和控制流分析,确保程序语义是合法的

验证点:跳转指令是否指向正确位置、操作数类型是否合理

符号引用验证

发生在解释阶段(符号运用->直接引用),保证解析阶段正确执行

验证点:符号引用的直接引用是否存在、符号引用中的类 字段 方法的访问性是否可被当前类访问

准备:

正式为类变量分配内存并设置初始值

解析:

将常量池中的符号引用替换为直接引用的过程

符号引用:一组符号来描述所引用目标。可以是任何字面量,与内存布局无关。

直接引用:可以是指针、句柄、相对偏移量。与内存布局相关。

初始化:

执行类构造器<clinit>()方法的过程,为类的静态变量赋予正确的初始值

有且只有五种情况对类进行初始化:

  1. 遇到new、getstatic、putstatic、invokestatic

  2. 反射调用时,如果类没有进行过初始化则需要先初始化

  3. 初始化一个类是如果其父类未初始化则先触发其父类初始化

  4. 主类

  5. 动态语言支持遇REF_getstatic、REF_putstatic、REF_invokestatic方法句柄,并且其对应类未初始化

类加载器:

作用:
  1. 实现类的加载作用,即通过class的全限定名获取描述此类的二进制字节流

  2. 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性

双亲委派模型:

类加载器之间层次关系

从开发者角度分类:

  1. Bootstrap Classloader

  2. Extension Classloader

  3. Application Classloader

  4. User Classloader

如果一个类加载器收到了类加载请求,首先自己不会尝试加载该类,而是委派给父加载器完成,如果父加载器反馈无法加载该类,则子加载器才会尝试加载。

作用: 类具有了带有优先级的层次关系

破坏双亲委派模型:
  1. 引入之前。java.lang.ClassLoader类在JDK1.0就已经出现,而双亲委派模型是在JDK1.2之后才引入的。

  2. 模型自身缺陷。双亲委派模型很好地解决了各个类加载器的基础类的统一问题,但是如果基础类又要调用用户代码则双亲委派模型无法解决。为了解决这个问题,引入了Thread Context ClassLoader。可以通过Thead类的setContextClassLoader设置。如果创建线程时未设置,则从父线程继承,如果全局都没有设置,则默认为Application ClassLoader。

  3. 用户对程序动态性的追求,譬如:代码热替换、模块热部署等。

补充:

变量生命周期:

发表评论

电子邮件地址不会被公开。 必填项已用*标注