毕业设计做系统和网站有什么区别,网站开发进度源码,定制网站开发哪里好,网站开发团队 组建JVM 基础知识学习笔记 
1. JVM 介绍 
什么是 JVM ? 
JVM 本质上是一个运行在计算机上的程序#xff0c;它的职责是运行 Java 字节码文件。 
JVM 的功能是什么 ? 
解释和运行: 对字节码文件中的指令#xff0c;实时的解释成机器码#xff0c;让计算机执行。内存管理: 自动为…JVM 基础知识学习笔记 
1. JVM 介绍 
什么是 JVM ? 
JVM 本质上是一个运行在计算机上的程序它的职责是运行 Java 字节码文件。 
JVM 的功能是什么 ? 
解释和运行: 对字节码文件中的指令实时的解释成机器码让计算机执行。内存管理: 自动为对象、方法等分配内存。自动的垃圾回收机制回收不再使用的对象。即时编译: 对热点代码进行优化提升执行效率。 
JVM 的即时编译是为了什么 主要是为了支持跨平台特性。 
由于 JVM 需要实时解释虚拟机指令不做任何优化性能不如直接运行机器码的 C、C 等语言。 JVM 提供了即时编译(Just in Time 简称JIT)进行性能优化最终能达到接近 C、C 语言的运行性能甚至在特定场景下实现超越。 
常见的 JVM 有哪些 ? 
HotSpot(Oracle JDK版)HotSpot(Open JDk版)GraalVMDragonwell JDK(龙井)Eclipse OpenJ9(原 IBM J9) 
2. 字节码文件详解 
2.1 Java 虚拟机的组成 
JVM 组成: 
类加载器ClassLoader: 加载 class 字节码文件中的内容到内存中。运行时数据区域(JVM管理的内存): 负责管理 JVM 使用到的内存比如创建对象和销毁对象。执行引擎(即时编译器、解释器、垃圾回收器等): 将字节码文件中的指令解释成机器码同时使用即时编译器优化性能。本地接口: 调用本地已经编译的方法比如虚拟机中提供的 c/c 的方法。 2.2 字节码文件的组成 
推荐使用 jclasslib 工具查看字节码文件。 
字节码文件的组成部分 
基础信息: 魔数、字节码文件对应的 Java 版本号、访问标识(public final 等等)、父类和接口常量池: 保存了字符串常量、类或接口名、字段名主要在字节码指令中使用字段: 当前类或接口声明的字段信息。方法: 当前类或接口声明的方法信息。字节码指令属性: 类的属性比如源码的文件名、内部类的列表等。 
字节码常用工具 
javap -vjclasslib阿里Arthas jar -xvf 命令解压 Jar 包 2.3 类的生命周期 
类的生命周期描述了一个类加载、使用、卸载的整个过程。 
类的生命周期-应用场景 
运行时常量池多态的原理类加载器的作用类的加密和解密 
2.3.1 生命周期概述 
类的生命周期 
加载 Loading连接 Linking (细分为验证、准备、解析)初始化 initialization使用 Using卸载 Unloading 
2.3.2 加载阶段 
加载阶段的步骤 
第一步是类加载器根据类的全限定名通过不同的渠道(渠道可以分为本地文件、动态代理生成、通过网络传输的类)以二进制流的方式获取字节码信息。类加载器在加载完类之后Java 虚拟机会将字节码中的信息保存到方法区(方法区是一个虚的是一个概念)中。生成一个InstanceKlass 对象保存类的所有信息里边还包含实现特定功能比如多态的信息。同时Java 虚拟机还会在堆中生成一份与方法区中数据类似的 java.lang.Class 对象。作用是在 Java 代码中获取类的信息以及存储静态字段的数据(JDK8及之后)。 对于开发者来说只需要访问堆中的 Class 对象而不需要访问方法区中所有信息。这样 Java 虚拟机就能很好地控制开发者访问数据的范围。 查看内存中的对象: 
hsdb: JDK自带工具工具位于JDK安装目录lib文件夹中的 sa-jdi.jar 中。启动命令java -cp sa-jdi.jar sun.jvm.hotspot.HSDB jsp 命令 查看系统中所有 java 进程。 2.3.3 连接阶段 
连接阶段: 
验证: 验证内容是否满足《Java虚拟机规范》准备: 给静态变量赋初值。解析: 将常量池中的符号引用替换成指向内存的直接引用。 
连接(Linking)阶段的第一个环节是验证验证的主要目的是检测Java字节码文件是否遵守了《Java虚拟机规范》中的约束。这个阶段一般不需要程序员参与。 
主要包含如下4个部分具体详见《Java虚拟机规范》: 
文件格式验证比如文件是否以 0xCAFEBABE 开头主次版本号是否满足当前Java虚拟机版本要求。元信息验证例如类必须有父类(super 不能为空)验证程序执行指令的语义比如方法内的指令执行到一半强行跳转到其他方法中去。符号引用验证例如是否访问了其他类中 private 的方法等。 版本号检测主版本号不能高于运行环境主版本号如果主版本号相等副版本号也不能超过。 连接(Linking)阶段的第二个环节准备准备阶段为静态变量(static)分配内存并设置初始值。 
准备阶段只会给静态变量赋初始值而每一种基本数据类型和引用数据类型都有其初始值。 final 修饰的基本数据类型的静态变量准备阶段直接会将代码中的值进行赋值。 连接(Linking)阶段的第三个环节解析解析阶段主要是将常量池中的符号引用替换为直接引用。 符号引用就是在字节码文件中使用编号来访问常量池中的内容。 2.3.4 初始化阶段 
初始化阶段会执行静态代码块中的代码并为静态变量赋值。 
初始化阶段会执行字节码文件中 clinit 部分的字节码指令。 clinit 方法中执行的顺序与 Java 中编写的顺序是一致的。 以下几种方式会导致类的初始化: 
访问一个类的静态变量或者静态方法注意变量是 final 修饰的并且右边是常量不会触发初始化。调用 Class.forName(String className)。new 一个该类的对象时。执行main方法的当前类。 添加 JVM 参数-XX:TraceClassLoading 参数可以打印出加载并初始化的类。 clinit 指令在特定情况下不会出现比如如下几种情况是不会进行初始化指令执行的。 
无静态代码块且无静态变量赋值语句。有静态变量的声明但是没有赋值语句。静态变量的定义使用final关键字这些变量会在准备阶段直接初始化。 
直接访问父类的静态变量不会触发子类的初始化。 子类的初始化 clinit 调用之前会先调用父类的 clinit 初始化方法。 数组的创建不会导致数组中元素的类进行初始化。 final 修饰的变量如果赋值的内容需要执行指令才能得出结果会执行clinit方法进行初始化。 2.4 类加载器 
类加载器(ClassLoader) 是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。 类加载器只参与加载过程中的字节码获取并加载到内存这一部分。 2.4.1 类加载器的分类 
类加载器分为两类一类是 Java 代码中实现一类是 Java 虚拟机底层源码实现的。 
虚拟机底层实现加载程序运行时的基础类。Java继承自抽象类 ClassLoaderJDk中默认提供或自定义。 类加载器的设计 JDK8 和 8 之后的版本差别较大。 JDK8 及之前的版本中默认的类加载器有 
启动类加载器(Bootstrap)扩展类加载器(Extension)应用程序类加载器(Application) 通过 Arthas 的 classloader 来查看JDK中的类加载器。 2.4.1.1 虚拟机底层实现的类加载器 
启动类加载器(Bootstrap ClassLoader) 是由 Hotspot 虚拟机提供的、使用 c 编写的类加载器。 默认加载Java安装目录 /jre/lib 下的类文件。 
通过启动类加载器去加载用户Jar包 
放入 jre/lib 下进行扩展不推荐。使用参数进行扩展推荐使用-Xbootclasspath/a:jar包目录/jar包名 进行扩展。 
2.4.1.2 Java实现的类加载器 
Java 中默认的类加载器 
扩展类加载器和应用程序类加载器都是JDK中提供的、使用Java编写的类加载器。它们的源码都位于 sun.misc.Launcher 中是一个静态内部类。继承自 UrlClassLoader。具备通过目录或者指定 jar 包将字节码文件加载到内存中。 扩展类加载器 (Extension Class Loader) 是JDK中提供的、使用 Java 编写的类加载器。 默认加载 Java 安装目录 /jre/lib/ext 下的类文件。 
通过扩展类加载器去加载用户 jar 包 
放入 /jre/lib/ext 下进行扩展不推荐使用参数进行扩展推荐使用 -Djava.ext.dirsjar 包目录进行扩展这种方式会覆盖原始目录可以用;(windows):(macos/linux)追加上原始目录。 
Arthas 中类加载器相关功能 
classloader -l: 查看有哪些类加载器。classloader -c hash查看类加载器的加载路径。classloader -t: 查看类加载器的父子关系。sc -d className: 查看类的信息 
2.4.2 双亲委派机制 
由于 Java 虚拟机中有多个类加载器双亲委派机制的核心是解决一个类到底由谁加载的问题。 
双亲委派机制的作用 
保证类加载的安全性通过双亲委派机制避免恶意代码替换 JDK 中的核心类库比如 java.lang.String, 确保核心类库的完整性和安全性。避免重复加载双亲委派机制可以避免同一个类被多次加载。 
双亲委派机制指的是当一个类加载器接收到加载类的任务时会自底向上查找是否加载过再由顶向下进行加载。 双亲委派机制的问题 
如果一个类重复出现在三个类加载器的加载位置应该由谁来加载启动类加载器加载根据双亲委派机制它的优先级最高。在自己的项目中去创建一个 java.lang.String 类会被加载吗不能会返回启动类加载器加载在rt.jar包中的String类。 
在 Java 中如何使用代码的方式去主动加载一个类呢 
使用 Class.forName 方法使用当前类的类加载器去加载指定的类。获取到类加载器通过类加载器的 loadClass 方法指定某个类加载器加载。 每个 Java 实现的类加载器中保存了一个成员变量叫 “父”(Parent)类加载器可以理解为它的上级并不是继承关系。 应用程序类加载器的 parent 父类加载器是扩展类加载器而扩展类加载器的 parent 是空但是在代码逻辑上扩展类加载器依然会把启动类加载器当成父类加载器处理。 启动类加载器使用 C 编写没有父类加载器。 2.4.3 打破双亲委派机制 
打破双亲委派机制的三种方式: 
自定义类加载器: 自定义类加载器并重写 loadClass 方法就可以将双亲委派机制的代码去除。Tomcat 通过这种方式实现应用之间类隔离。线程上下文类加载器: 利用上下文类加载器比如 JDBC 和 JNDI。Osgi框架的类加载器: 历史上 Osgi 框架实现了一套新的类加载机制允许同级之间委托进行类的加载。 双亲委派机制的核心代码就位于 loadClass 方法中。 自定义类加载器父类怎么是 AppClassLoader 呢 以JDK8为例ClassLoader 类中提供了构造方法设置 parent 的内容。这个构造方法由另一个构造方法调用其中父类加载器由 getSystemClassLoader 方法设置该方法返回的是 AppClassLoader。 
两个自定义类加载器加载相同限定名的类不会冲突吗 不会冲突在同一个 Java 虚拟机中只有相同类加载器  相同的类限定名才会被认为是同一个类。 
使用阿里 Arthas 不停机解决线上问题: 
在出问题的服务器上部署一个 arthas, 并启动。jad --source-only 类全限定名  目录/文件名.java (jad 命令反编译然后可以用其他编辑器来修改源码)sc -d className: 查看类加载器的hashcodemc -c 类加载器的hashcode 目录/文件名.java -d 输出目录 (mc 命令用来编译修改过的代码)retransform class文件所在目录/xxx.class (用 retransform 命令加载新的字节码) 程序重启之后字节码文件会恢复除非将 Class 文件放入 jar 包中进行更新。 使用 retransform 不能添加方法或字段也不能更新正在执行中的方法。 2.4.4 JDK9之后的类加载器 
JDK8 之前的版本中扩展类加载器和应用程序类加载器的源码位于 rt.jar 包中的 sun.misc.Launcher.java。 
由于 JDK9 引入了 module 的概念类加载器在设计上发生了很多变化。 
启动类加载器使用 Java 编写位于 jdk.internal.loader.ClassLoader 类中。Java 中的 BootClassLoader 继承自 BuiltinClassLoader 实现从模块中找到要加载的字节码资源文件。扩展类加载器被替换为平台类加载器(Platform Class Loader), 平台类加载器遵循模块化方式加载字节码文件所以继承关系从 UrlClassLoader 变成了 BuiltinClassLoaderBuiltinClassLoader 实现了从模块中加载字节码文件。 启动类加载器依然无法通过 Java 代码获取到返回的仍然是 null保持了统一。 平台类加载器的存在更多的是为了与老版本的设计方案兼容自身没有特殊逻辑。 3. JVM 内存区域(运行时数据区) 
Java 虚拟机在运行 Java 程序过程中管理的内存区域称之为运行时数据区。 《Java虚拟机规范》中规定了每一部分的作用。 
线程不共享 
程序计数器Java虚拟机栈本地方法栈 
线程共享 
方法区堆 
3.1 程序计数器 
程序计数器(Program Counter Register) 也叫 PC 寄存器每个线程会通过程序计数器记录当前要执行的字节码指令的地址。 
在代码执行过程中程序计数器会记录下一行字节码指令的地址。执行完当前指令之后虚拟机的执行引擎根据程序计数器执行下一行指令。 
程序计数器的作用 
程序计数器可以控制程序指令的进行实现分支、跳转、异常等逻辑。在多线程执行情况下Java 虚拟机需要通过程序计数器记录 CPU 切换前解释执行到那一句指令并继续解释执行。 内存溢出指的是程序在使用某一块内存区域时存放的数据需要占用的内存大小超过了虚拟机能提供的内存上限。 程序员无需对程序计数器做任何处理。 3.2 栈 
Java 虚拟机栈(Java Virtual Machine Stack) 采用栈的数据结构来管理方法调用中的基本数据先进后出(First In Last Out),每一个方法的调用使用一个栈帧(Stack Frame)来保存。 
Java 虚拟机栈随着线程的创建而创建而回收则会在线程的销毁时进行。由于方法可能会在不同线程中执行每个线程都会包含一个自己的虚拟机栈。 
栈帧的组成 
局部变量表: 局部变量表的作用是在运行过程中存放所有的局部变量。操作数栈: 是栈帧中虚拟机在执行指令的过程中用来存放临时数据的一块区域。帧数据: 主要包含动态链接、方法出口、异常表的引用。 
3.2.1 局部变量表 
局部变量表的作用是在方法的执行过程中存放所有的局部变量。编译成字节码文件时就可以确定局部变量表的内容。 
栈帧中的局部变量表是一个数组数组中每一个位置称之为槽(slot),long 和 double 类型占用两个槽其他类型占用一个槽。 实例方法中的序号为 0 的位置存放的是 this, 指的是当前调用方法的对象运行时会在内存中存放实例对象的地址。 方法参数也会保存在局部变量表中其顺序与方法中参数定义的顺序一致。 局部变量表保存的内容有: 实例方法的 this 对象方法的参数方法体中声明的局部变量。 为了节省空间局部变量表中的槽是可以复用的一旦某个局部变量不再生效当前槽就可以再次被使用。 3.2.2 操作数栈 
操作数栈是栈帧中虚拟机在执行指令过程中用来存放中间数据的一块区域。它是一种栈式的数据结构如果一条指令将一个值压入操作数栈则后面的指令可以弹出并使用该值。 
在编译期就可以确定操作数栈的最大深度从而在执行时正确的分配内存大小。 
3.2.3 帧数据 
帧数据中的动态链接当前类的字节码指令引用了其他类的属性或者方法时需要将符号引用(编号)转换成对应的运行时常量池中的内存地址。 
动态链接就保存了编号到运行时常量池的内存地址的映射关系。 
方法出口: 指的是方法在正确或异常结束时当前栈帧会被弹出同时程序计数器应该指向上一个栈帧中的下一条指令的地址。所以在当前栈帧中需要存储此方法出口的地址。 
异常表存放的是代码中异常的处理信息包含了异常捕获的生效范围以及异常发生后跳转到的字节码指令位置。 
3.2.4 栈内存溢出 
Java 虚拟机栈如果栈帧过多占用内存超过栈内存可以分配的最大大小就会出现内存溢出。 Java 虚拟机栈内存溢出时会出现 StackOverflowError 的错误。 
Java 虚拟机栈的默认大小如果我们不指定栈的大小JVM 将创建一个具有默认大小(1024kb)的栈。大小取决于操作系统和计算机的体系结构。 
Linux X86(64位): 1MBLinux ppc: 2MBBSD X86(64位): 1MBSolaris 64位: 1MBWindows: 基于操作系统的默认值。 
Java 虚拟机栈-设置大小 
语法-Xss栈大小单位字节(默认必须是1024的倍数)、k/K(KB)、m/M(MB)、g/G(GB) 
Java 虚拟机栈-注意事项 
与 -Xss 类似也可以使用 -XX:ThreadStackSize 调整标志来配置堆栈大小。格式为 -XX:ThreadStackSize1024Hotspot JVM 对栈大小的最大值和最小值有要求。局部变量过多、操作数栈深度过大也会影响栈内存的大小。 一般情况下工作中即便使用了递归进行操作栈的深度最多也只能到几百不会出现栈的溢出。所以此参数可以手动指定为 -Xss256k 节省内存。 本地方法栈 
Java 虚拟机栈存储了 Java 方法调用时的栈帧而本地方法栈存储的是 native 本地方法的栈帧。在 Hotspot 虚拟机中Java 虚拟机栈和本地方法栈实现上使用了同一个栈空间。本地方法栈会在栈内存上生成一个栈帧临时保存方法的参数同时方便出现异常时也把本地方法的栈信息打印出来。 
3.3 堆 
一般 Java 程序中堆内存是空间最大的一块内存区域。创建出来的对象都存在于堆上。 栈上的局部变量表可以存放堆上对象的引用。静态变量也可以存放堆对象的引用通过静态变量就可以实现对象在线程之间共享。 堆内存大小是有上限的当对象一直向堆中放入对象达到上限之后就会抛出 OutOfMemory 错误。 堆空间有三个需要关注的值 
used: 当前已使用的堆内存。total: Java 虚拟机已分配的可用堆内存。max: Java 虚拟机可以分配的最大堆内存。 在 Arthas 中查看堆内存相关的功能dashboard -i 刷新频率(毫秒) 随着堆中的对象增多当 total 可以使用的内存即将不足时Java 虚拟机会继续分配内存给堆。 如果堆内存不足Java 虚拟机就会不断的分配内存total 值会变大。total 最多只能与 max 相等。 如果不设置任何的虚拟机参数max 默认是系统内存的 1/4 ,total 默认是系统内存的 1/64。在实际应用中一般都需要设置 total 和 max 的值。 堆-设置大小: 要修改堆的大小可以使用虚拟机参数 -Xmx(max 最大值) 和 -Xms(初始的 total) 
语法: -Xmx值 -Xms值单位: 字节(默认必须是 1024 的倍数)、k或者K(KB)、m或者M(MB)、g或者G(GB)限制: Xmx 必须大于 2 MB, Xms 必须大于 1 MB Java 服务端程序开发时建议将 -Xmx 和 -Xms 设置为相同的值这样在程序启动之后可使用的总内存就是最大内存而无需向 Java 虚拟机再次申请减少了申请并分配内存时间上的开销同时也不会出现内存过剩之后堆收缩的情况。 3.4 方法区 
方法区(Method Area): 是存放基础信息的位置线程共享主要包含三部分内容 
类的元信息: 保存了所有类的基本信息。运行时常量池: 保存了字节码文件中的常量池内容。字符串常量池: 保存了字符串常量。 
方法区的作用 
方法区是用来存储每个类的基本信息(元信息)一般称之为 InstanceKlass 对象。在类的加载阶段完成。方法区除了存储类的元信息之外还存放了运行时常量池。常量池中存放的是字节码中的常量池内容。字节码文件中通过编号查表的方式找到常量这种常量池称为静态常量池。当常量池加载到内存中之后可以通过内存地址快速的定位到常量池中的内容这种常量池称为运行时常量池。方法区中除了类的元信息、运行时常量池之外还有一块区域叫字符串常量池(StringTable)。字符串常量池存储在代码中定义的常量字符串内容。 方法区是《Java 虚拟机规范》 中设计的虚拟概念每款 Java 虚拟机在实现上都各不相同。 Hotspot设计中,JDK7及之前的版本将方法区存放在堆区域中的永久代空间堆的大小由虚拟机参数控制。-XX:MaxPermSize值 Hotspot设计中,JDK8及之后的版本将方法区存放在元空间中元空间位于操作系统维护的直接内存中默认情况下只要不超过操作系统承受的上限可以一直分配。-XX:MaxMetaspaceSize值 可以通过 ByteBuddy 框架动态生成字节码文件加载到内存中。 
3.5 直接内存 
直接内存 (Direct Memory) 并不在 《Java虚拟机规范》中存在所以并不属于 Java 运行时的内存区域。 在 JDK1.4 中引入了 NIO 机制使用了直接内存主要为了解决以下两个问题: 1.Java 堆中的对象如果不再使用要回收回收时会影响对象的创建和使用。 2.IO操作比如读文件需要先把文件读入到直接内存(缓存区)再把数据复制到 Java 堆中。现在直接放入直接内存即可同时 Java 堆上维护直接内存的引用减少了数据复制的开销写文件也是类似的思路。 要创建直接内存上的数据可以使用 ByteBuffer 
语法ByteBuffer directBuffer  ByteBuffer.allocateDirect(size);注意事项: arthas 的 memory 命令可以查看直接内存的大小属性名为 direct。 如果需要手动调整直接内存的大小可以使用 -XX:MaxDirectMemorySize大小。单位k/K、m/M、g/G 默认不设置该参数情况下JVM 自动选择最大分配的大小。 4. JVM 垃圾回收 内存泄漏指的是不再使用的对象在系统中未被回收内存泄漏的积累可能会导致内存溢出。 Java 中为了简化对象的释放引入了自动的垃圾回收(Garbage Collection 简称GC)。通过垃圾回收器来对不再使用的对象完成自动的回收垃圾回收器主要负责对堆上的内存进行回收。 
自动垃圾回收自动根据对象是否使用由虚拟机来回收对象。 
优点: 降低程序员实现难度、降低对象回收 bug 的可能性。缺点: 程序员无法控制内存回收的及时性。 
手动垃圾回收: 由程序员编程实现对象的删除。 
优点: 回收即时性高由程序员把控回收时机。缺点: 编写不当容易出现悬空指针、重复释放、内存泄漏等问题。 
4.1 方法区的回收 
方法区中能回收的内容主要就是不再使用的类。 
判定一个类可以被卸载。需要同时满足下面三个条件。 
此类所有实例对象都已经被回收在堆中不存在任何该类的实例对象以及子类对象。加载该类的类加载器已经被回收。该类对应的 java.lang.Class 对象没有在任何地方被引用。 
手动触发回收 
语法: System.gc() 注意事项调用 System.gc() 方法并不一定会立即回收垃圾仅仅是向 Java 虚拟机发送一个垃圾回收的请求具体是否需要执行垃圾回收 Java 虚拟机会自行判断。 开发中此类场景一般很少出现主要在 OSGI、JSP 的热部署等应用场景中。 每个 JSP 文件对应一个唯一的类加载器当一个 JSP 文件修改了就直接卸载这个 JSP 类加载器重新创建类加载器重新加载 JSP 文件。 4.2 堆回收 
如何判断堆上的对象可以回收只有无法通过引用获取到对象时该对象才能被回收。 
Java 中的对象是否能被回收是根据对象是否被引用来决定的。如果对象被引用了说明该对象还在使用不允许被回收。 
常见的判断堆上对象没有被引用的判断方法 
引用计数法可达性分析法 Java 使用可达性分析法来判断对象是否可以被回收。 4.2.1 引用计数法 
引用计数法会为每个对象维护一个引用计数器当对象被引用时加 1取消引用时减 1。 
引用计数法的优点是实现简单C 中的智能指针就采用了引用计数法但它也存在缺点主要有两点: 
每次引用和取消引用都需要维护计数器对系统性能会有一定影响。存在循环引用问题循环引用就是A引用BB同时引用A时会出现对象无法回收的问题。 
如果想查看垃圾回收的信息可以使用 -verbose:gc 参数。 
4.2.2 可达性分析法 
可达性分析将对象分为两类: 垃圾回收的根对象(GC Root)和普通对象根对象与普通对象之间存在引用关系。 
可达性分析算法指的是如果从某个到 GC Root 对象是可达的对象就不可被回收。 
哪些对象被称之为 GC Root 对象 
线程 Thread 对象。系统类加载器加载的 java.lang.Class 对象引用类中的静态变量。监视器对象用来保存同步锁 synchronized 关键字持有的对象。本地方法调用时使用的全局对象。 
4.2.3 五种对象引用 
可达性算法中描述的对象引用一般指的是强引用即是 GC Root 对象对普通对象有引用关系只要这层关系存在普通对象就不会被回收。 
其他引用方式 
软引用弱引用虚引用终结器引用 
4.2.3.1 软引用对象 
软引用相对于强引用是一种比较弱的引用关系如果一个对象只有软引用关联到它当程序内存不足时就会将软引用中的数据进行回收。 
在 JDK1.2 版之后提供了 SoftReference 类来实现软引用软引用常用于缓存中。 
软引用中的对象如果在内存不足时回收SoftReference 对象本身也需要被回收。 
SoftReference 提供了一套队列机制 
软引用创建时通过构造器传入引用队列。在软引用中包含的对象被回收时该软引用对象会被放入引用队列。通过代码遍历引用队列将 SoftReference 的强引用删除。 
4.2.3.2 弱引用对象 
弱引用的整体机制和软引用基本一致区别在于弱引用包含的对象在垃圾回收时不管内存够不够都会直接被回收。 
在 JDK1.2 版之后提供了 WeakReference 类来实现弱引用弱引用主要在 ThreadLocal 中使用。 
弱引用对象本身也可以使用引用队列进行回收。 
4.2.3.3 虚引用和终结器引用 
这两种引用在常规开发中是不会使用的。 
虚引用也叫幽灵引用不能通过虚引用对象获取到包含的对象。虚引用唯一的用途是当对象被垃圾回收器回收时可以接收到对应的通知。Java 中使用 PhantomReference 实现了虚引用直接内存中为了及时知道直接内存对象不再使用从而回收内存使用了虚引用来实现。 
终结器引用指的是在对象需要被回收时对象将会被放置在 Finalize 类中的引用队列中并在稍后由一条由 FinalizerThread 线程从队列中获取对象然后执行对象的 Finalize 方法在这个过程中可以在 finalize 方法中再将自身对象使用强引用关联上但是不建议这样做如果耗时过长会影响其他对象的回收。 
4.2.4 垃圾回收算法 
垃圾回收算法的核心思想 
找到内存中存活的对象。释放不再存活对象的内存使得程序能再次利用这部分空间。 
1960 年 John McCarthy 发布了第一个 GC 算法: 标记清除算法。 1963 年 Marvin L. Minsky 发布了复制算法。 本质上后续所有的垃圾回收算法都是在上述两种算法的基础上优化而来。 
垃圾回收算法的分类 
标记清除算法: Mark Sweep GC复制算法: Coping GC标记-整理算法: Mark Compact GC分代GC: Generational GC 
垃圾回收算法的评价标准: Java 垃圾回收过程会通过单独的 GC 线程来完成但是不管使用哪一种 GC 算法都会有部分阶段需要停止所有的用户线程。这个过程被称之为 Stop the World 简称 STW如果 STW 时间过长则会影响用户使用。 
判断 GC 算法是否优秀可以从三个方面来考虑: 
吞吐量: 指的是 CPU 用于执行用户代码的时间与 CPU 总执行时间的比值即吞吐量  执行用户代码时间 / (执行用户代码时间  GC 时间)吞吐量数值越高垃圾回收的效率就越高。最大暂停时间: 指的是所有在垃圾回收过程中的 STW 时间最大值。最大暂停时间越短用户使用系统时受到的影响就越短。堆使用效率: 不同垃圾回收算法对堆内存的使用方式是不同的。比如标记清除算法可以使用完整的堆内存。而复制算法会将堆内存一分为二每次只能使用一半内存。从堆使用效率上来说标记清除算法要优于复制算法。 上述三种评价标准堆使用效率、吞吐量以及最大暂停时间不可兼得。 一般来说堆内存越大最大暂停时间就越长想要减少最大暂停时间就会降低吞吐量。 不同的垃圾回收算法适用于不同的场景。 4.2.4.1 标记清除算法 
标记清除算法的核心思想分为两个阶段: 
标记阶段: 将所有存活的对象进行标记。Java 中使用可达性分析算法从 GC Root 开始通过引用链遍历出所有存活对象。清除阶段: 从内存中删除没有被标记也就是非存活对象。 
标记清除算法的优点: 实现简单只需要在第一阶段给每个对象维护标志位第二阶段删除对象即可。 
标记清除算法的缺点: 
碎片化问题: 由于内存是连续的所以在对象被删除之后内存中会出现很多细小的可用内存单元。如果我们需要的是一个比较大的空间很有可能这些内存单元的大小过小无法进行分配。分配速度慢: 由于内存碎片的存在需要维护一个空闲链表极有可能发生每次需要遍历到链表的最后才能获得合适的内存空间。 
4.2.4.2 复制算法 
复制算法的核心思想是: 
准备两块空间 From 空间和 To 空间每次在对象分配阶段只能使用其中一块空间( From 空间)。在垃圾回收 GC 阶段将 FROM 中存活对象复制到 To 空间。将两块空间的 From 和 To 名字互换。 
复制算法的优点: 
吞吐量高: 复制算法只需要遍历一次存活对象复制到 To 空间即可比标记整理算法少了一次遍历的过程因而性能较好但是不如标记清除算法因为标记清除算法不需要进行对象的移动。不会发生碎片化: 复制算法在复制之后就会将对象按顺序放入 To 空间所以对象以外的区域都是可用空间不存在碎片化内存空间。 
复制算法的缺点: 
内存使用效率低: 每次只能让一半的内存空间来为创建对象使用。 
4.2.4.3 标记整理算法 
标记整理算法也叫标记压缩算法是对标记清理算法中容易产生内存碎片问题的一种解决方案。 
标记整理算法的核心思想: 
标记阶段: 将所有存活的对象进行标记。Java 中使用可达性分析算法从 GC Root 开始通过引用链遍历出所有存活对象。整理阶段: 将存活对象移动到堆的一端。清理掉存活对象的内存空间。 
标记整理算法的优点: 
内存使用效率高: 整个堆内存都可以使用不会像复制算法只能使用半个堆内存。不会发生碎片化: 在整理阶段可以将对象往内存的一侧进行移动剩下的空间都是可以分配对象的有效空间。 
标记整理算法的缺点: 
整理阶段的效率不高: 整理算法有很多种比如 Lisp2 整理算法需要对整个堆中的对象搜索 3 次整体性能不佳。可以通过 Two-Finger、表格算法、ImmixGC 等高效的整理算法优化此阶段的性能。 
4.2.4.4 分代垃圾回收算法 
现代优秀的垃圾回收算法会将上述描述的垃圾回收算法组合进行使用其中应用最广的就是分代垃圾回收算法(Generational GC)。 
分代垃圾回收将整个内存区域划分为年轻代和老年代。 
arthas 查看分代之后的内存情况: 
在 JDK8 中添加 -XX:UseSerialGC 参数使用分代回收的垃圾回收器运行程序在 Arthas 中使用 memory 命令查看内存显示出三个内存区域的情况eden_space、survivor_space、tenured_gen 
分代垃圾回收算法: 
分代回收时创建出来的对象首先会被放入 Eden 伊甸园区。随着对象在 Eden 区越来越多如果 Eden 区满新创建的对象已经无法放入就会触发年轻代的 GC,称为 Minor GC 或者 Young GC。Minor GC 会把需要 eden 中和 From 需要回收的对象回收把没有回收的对象放入 To 区。 接下来S0 会变成 To 区S1 变成 From 区。当 eden 区满时再往里放入对象依然会发生 Minor GC。 
此时会回收 eden 区和 S1(from) 中的对象并把 eden 和 from 区中剩余的对象放入 S0。 注意每次 Minor GC 中都会为对象记录他的年龄初始值为 0每次 GC 完加 1 。 如果 Minor GC 后对象的年龄达到阀值 (最大 15默认值和垃圾回收器有关)对象就会被晋升至老年代。 
当老年代中空间不足无法放入新的对象时先尝试 minor gc 如果还是不足就会触发 Full GCFull GC 会对整个堆进行垃圾回收。 
如果 Full GC 依然无法回收掉老年代的对象那么当对象继续放入老年代时就会抛出 Out of Memory 异常。 
4.2.5 垃圾回收器 
垃圾回收器是垃圾回收算法的具体实现。 由于垃圾回收器分为年轻代和老年代除了 G1 之外其他垃圾回收器必须成对组合进行使用。 
具体的关系图如下。