java-JVM相关内容
1.JVM概述
1.1 什么是虚拟机
虚拟机
:模拟某种计算机体系结构,执行特定指令集的软件。
分为两大类:
- 系统虚拟机( Virtual Box、VMware)
- 程序虚拟机(JVM、.NET CLR、P-Code)
1.2 什么是Java 虚拟机
Java 语言虚拟机
- 可以执行Java 语言的高级语言虚拟机。Java 语言虚拟机并不一定就可以称为JVM,譬如:Apache Harmony
Java™ 虚拟机
- 必须通过Java TCK(Technology Compatibility Kit)的兼容性测试的Java语言虚拟机才能称为“Java™ 虚拟机”
- Java™ 虚拟机并非一定要执行“Java”程序
- 业界三大商用JVM:Oracle HotSpot 、Oracle JRockit VM、IBM J9 VM
- 其他虚拟机:Google Dalvik VM、Microsoft JVM、TaoBao JVM等
1.3 Java虚拟机架构
- Class Loader:类加载器
- Runtime Data Area:运行时数据区
- Execution Engine:执行引擎
- Native Interface:本地接口
1.4 HotSpot 虚拟机
- 最初由名为“Longview Technologies”的小公司开发,后被Sun 公司收购。最初并非面向Java 语言开发,而是面向Strongtalk 语言。
- HotSpot 命名来自它的“热点代码探测”技术。
- 从JDK 1.2 开始加入Sun(Oracle)JDK,在JDK 1.3 开始成为Sun(Oracle)JDK 的默认实现,在JDK1.4中成为唯一的虚拟机。
- 在2006年底开始开源,由此建立的OpenJDK 项目。
- 本课程中所有的虚拟机实现,指的都是Oracle HotSpot 虚拟机。
1.5 公有设计,私有实现
- 前面所讲内存区域是在《Java 虚拟机规范》(JVMS)中定义的概念模型,但JVMS也同时声明这些概念不约束虚拟机的具体实现,只要求虚拟机实现的效果在外部看起来与规范描述一致即可。
- 比如:堆内存在规范中规定是需要自动内存管理的,但是如何管理是具体虚拟机实现。
- https://docs.oracle.com/javase/specs/index.html
- Java语言规范和JVM规范
[wppay]
2.类加载
2.1 类加载器
- Java类加载器(Java Classloader)是Java运行时环境(Java RuntimeEnvironment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。
• 类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。
•-verbose:class:显示类加载过程
JVM中有3个默认的类加载器:
- 引导(Bootstrap)类加载器。
- 由原生代码(如C语言)编写,不继承自java.lang.ClassLoader。负责加载核心Java库,存储在/jre/lib目录中。
- 扩展(Extensions)类加载器。
- 用来在/jre/lib/ext,或java.ext.dirs中指明的目录中加载Java的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java 类。该类由sun.misc.Launcher$ExtClassLoader实现。
- Apps类加载器(也称系统类加载器)。
- 根据Java应用程序的类路径(java.class.path或CLASSPATH环境变量)来加载Java类。一般来说,Java 应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。该类由sun.misc.Launcher$AppClassLoader实现。
- 这三个类装载器存在父子层级关系,即根装载器是ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。默认情况下使用AppClassLoader装载应用程序的类。
2.2 类加载过程
class文件加载到虚拟机的内存,这个过程称为类加载。
七个过程:加载、验证、准备、解析、初始化、使用和卸载
- 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建
一个Class对象 - 验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
- 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
- 解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
- 初始化: 类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值)。
- 使用
- 卸载
2.3 面试题
2.4 双亲委托机制
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委托机制。
为什么要使用这种双亲委托模式呢?因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
演示错误:
- 创建java.lang包并创建String类,可以运行吗?
- 创建java.lang包并创建Person类,可以运行吗?
2.5 使用类加载器加载属性文件
3.运行时数据区
- 在《Java 虚拟机规范》中定义了若干种程序运行期间会使用到的存储不同类型数据的区域。
- 有一些区域是全局共享的,随着虚拟机启动而创建,随着虚拟机退出而销毁。有一些区域是线程私有的,随着线程开始和结束而创建和销毁。、
- 是所有Java 虚拟机共同的内存区域概念模型
运行时数据区的划分
线程私有
- 程序计数器
- Java 虚拟机栈
- 本地方法栈
线程共享
- Java 堆
- 方法区
3.1 程序计数器
程序计数器也称为PC寄存器(Program Counter Register)
- 一块较小的的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。
- 如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。
- 此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
3.2 虚拟机栈和本地方法栈
- 虚拟机栈的特征
- 线程私有
- 后进先出(LIFO)栈
- 存储栈帧,支撑Java 方法的调用、执行和退出
- 可能出现OutOfMemoryError 异常和StackOverflowError 异常
- 本地方法栈的特征
- 线程私有
- 后进先出(LIFO)栈
- 作用是支撑Native 方法的调用、执行和退出
- 可能出现OutOfMemoryError 异常和StackOverflowError 异常
- HotSpot虚拟机将Java 虚拟机栈和本地方法栈合并实现
栈帧
每一个线程的启动,在内存中创建一个对应的JVM栈,用于存储“栈帧”。
执行机制
- 一个方法对应一个栈帧结构
- 一个方法从调用起到执行完毕的过程,就对应一个栈帧在JVM中从入栈到出栈的过程。
栈帧的概念和特征
- Java 虚拟机栈中存储的内容,它被用于存储数据和执行过程结果的数据结构,同时也被用来处理动态连接、方法返回地址和异常完成信息
- 一个完整的栈帧包含:
局部变量表、操作数栈、动态连接、方法返回地址
等附加信息。
栈帧结构-局部变量表
局部变量表概念和特征
- 由若干个Slot(槽) 组成,长度由编译期决定,Code属性的locals指定。
- 单个Slot可以存储一个类型为boolean、byte、char、short、int、float、reference 和returnAddress(已过时)的数据,两个Slot可以存储一个类型为long或double的数据。
- 局部变量表用于方法间参数传递,以及方法执行过程中存储基本数据类型的值和对象的引用
栈帧结构-操作数栈
操作数栈的概念和特征
- 是一个后进先出栈,由若干个Entry 组成,长度由编译期决定、由Code属性的stacks指定。
- 单个Entry 即可以存储一个Java 虚拟机中定义的任意数据类型的值,包括long和double 类型,但是存储long 和double 类型的Entry 深度为2,其他类型的深度为1
- 在方法执行过程中,操作数栈用于存储计算参数和计算结果;在方法调用时,操作数栈也用来准备调用方法的参数以及接收方法返回结果。
栈帧演示
栈帧结构-动态连接
- 每个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。
- 每一次方法调用时,动态的将符号引用转成直接引用(入口地址),支持多态。
栈帧结构-方法返回地址
- 方法结束有正常结束和异常结束:
- 正常结束:
- 当前栈帧承担着恢复调用者状态的责任,其中包括恢复调用者的局部变量表和操作数栈、正确递增程序计数器、将返回值压入调用者的操作数栈。
- 异常结束:
- 如果当前方法中没有处理此种异常、当前栈帧恢复调用者状态的同时,不会返回任何值,而是通过athrow指令将当前异常抛给调用者。
3.3 Java 堆(heap)
Java 堆的特征
- 全局共享
- 通常是Java 虚拟机中最大的一块内存区域
- 作用是做为Java 对象或数组的主要存储区域
- JVMS 明确要求该区域需要实现自动内存管理,即常说的GC,但并不限制采用哪种算法和技术去实现
- 可能出现OutOfMemoryError 错误
- 由JVM自动管理的线程共享区域,在JVM启动时创建。
- 堆可以处于逻辑上连续、物理上不连续的空间当中。既可以实现固定大小的,也可以实现为可扩展的,当前主流虚拟机实现都是可扩展的,可通过相关的参数进行配置。
Java堆相关参数:
- -Xms:初始堆大小
- -Xmx:最大堆大小
- -XX:+PrintGC:打印查看GC日志
- -XX:+PrintGCDetails:打印查看GC日志详情
- -XX:+PrintCommandLineFlags:打印虚拟机的参数
- HotSpot虚拟机采用分代存储(Java内存模型JMM)的方式。
- 分代存储: 因对象的生命周期不同,HotSpot虚拟机将堆中对象按照年龄进行划分,分别存入“新生代”和“老年代”。
相关参数:
- -Xmn:新生代大小
- -XX:NewRatio=?: 表示年轻代和老年代的比例, 默认1:2
- -XX:SurvivorRatio=?:Eden和Survivor的比例,默认是8:1:1,但实际调整6:1:1
堆-对象分配策略
- 优先分配Eden区:绝大多数对象都是“朝生夕死”的对象,Eden区的回收时间短、效率高,适用于频繁回收。
- 大对象直接进入老年代:Enden和Survivor的空间不足时,大对象直接进入老年代。-XX:PretenureSizeThreshold=KB ,对象大小超过此值,直接进入老年代。
- 长期存活对象进入老年代:在Survivor区存活N岁(来回复制N次)的对象,将直接进入老年代。-XX:MaxTenuringThreshold=15(只有单线程收集器可用)
栈到堆的关联过程:
Java对象
- 对象:对象头、实例数据、对齐填充三部分组成。
- 对象头:Mark Word(8)、Klass Pointer(4)、数组长度
- 实例数据:对象属性信息
- 对齐填充:必须是8的倍数
- Object对象16个字节
堆内存异常实战
- Java 堆可能发生如下异常情况:
- 如果实际所需的堆超过了自动内存管理系统能提供的最大容量,那Java 虚拟机将会抛出一个OutOfMemoryError 异常。
3.4 方法区和运行时常量池
方法区
方法区的特征
- 全局共享
- 作用是存储Java 类的结构信息、常量、静态变量、即时编译器编译后的代码
- JVMS 不要求该区域实现自动内存管理,但是商用Java 虚拟机都能够自动管理该区域的内存
- 可能出现OutOfMemoryError 异常
运行时常量池
运行时常量池的特征
- 全局共享
- 是方法区的一部分
- 作用是存储Java 类文件常量池中的符号信息
- 可能出现OutOfMemoryError 异常
HotSpot方法区实现的变迁
- 永久代与方法区
- 在JDK 1.2 ~ JDK 6,HotSpot 使用永久代(PermGen)实现方法区
- 在JDK 7 开始,HotSpot 开始了移除永久代计划
- 符号表被移到Native Heap 中
- 字符串常量和类的静态变量被移到Java Heap 中
- 在JDK 8 开始,永久代已被元空间(Metaspace)所代替
- 字符串常量和类的静态变量仍然在堆中
3.5 直接内存(Direct Memory)
- 直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。
- 应用在某些场景中能显著提高性能,因为其避免了在Java堆和Native堆中来回复制数据。
- 在JDK1.4的NIO中已经出现直接内存的使用。
- 可能出现OutOfMemoryError 异常。
3.6 其他空间
- 在HotSpot虚拟机中还有其他一些空间
- TLAB: TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域。
- CodeCache:Code Cache用于存储JVM JIT产生的编译代码。
- Compressed Class Space(JDK1.8以后):类指针压缩空间,目的为了节省内存。
3.7 逃逸分析
- 逃逸分析(Escape Analysis)
- Java Hotspot 虚拟机可以分析新创建对象的使用范围,并决定是否在Java 堆上分配内存的一项技术。
- 逃逸分析的JVM 参数如下:
- 开启逃逸分析:-XX:+DoEscapeAnalysis
- 关闭逃逸分析:-XX:-DoEscapeAnalysis
- 显示分析结果:-XX:+PrintEscapeAnalysis
- 逃逸分析技术在Java SE 6u23+ 开始支持,并默认设置为启用状态,可以不用额外加这个参数。
- 对象逃逸
- 当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数或返回值传递到其他地方中,称为对象逃逸。反之没有逃逸。
使用逃逸分析,编译器可以对代码做如下优化:
(1)锁消除:
- 线程同步锁是非常牺牲性能的,当编译器确定当前对象只有当前线程使用,那么就会移除该对象的同步锁。
- 锁消除的JVM 参数如下:
- 开启锁消除:-XX:+EliminateLocks
- 关闭锁消除:-XX:-EliminateLocks
- 锁消除在JDK8 中都是默认开启的,并且锁消除都要建立在逃逸分析的基础上。
(2)标量替换:
- 标量:不能被进一步分解的数据,基本类型数据和对象的引用可以理解为标量
- 聚合量:可以被进一步分解成标量,比如对象
- 标量替换:将对象成员变量分解为分散的标量,这就叫做标量替换。
- 如果一个对象没有发生逃逸,那压根就不用创建它,只会在栈或者寄存器上创建它用到的成员标量,节省了内存空间,也提升了应用程序性能。
- 标量替换的JVM 参数如下:
- 开启标量替换:-XX:+EliminateAllocations
- 关闭标量替换:-XX:-EliminateAllocations
- 显示标量替换详情:-XX:+PrintEliminateAllocations
- 标量替换同样在JDK8 中都是默认开启的,并且都要建立在逃逸分析的基础上。
- 标量替换的JVM 参数如下:
(3) 栈上分配
- 当对象没有发生逃逸时,该对象就可以通过标量替换分解成成员标量分配在栈内存中,和方法的生命周期一致,随着栈帧出栈时销毁,减少了GC 压力,提高了应用程序性能。
面试题:Java对象都是在堆中分配吗?
4.垃圾收集(GC)
4.1 什么是垃圾回收
- 垃圾回收(Garbage Collection,GC):
- 顾名思义就是释放垃圾占用的空间,防止内存溢出或内存泄露。为了有效的使用内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
- 在C或C++ 等语言创建对象时要不断的去开辟空间,不用的时候又需要不断的去释放空间。
- 1960年,基于MIT 的Lisp 首先提出了垃圾回收的概念,而这时Java 还没有出世呢!所以实际上GC 并不是Java的专利,GC 的历史远远大于Java 的历史。
4.2 垃圾判定
- 垃圾:没有任何引用指向的对象,称为垃圾。
- 垃圾判定算法:
- 引用计数算法
- 可达性分析算法
引用计数算法
- 引用计数算法(Reachability Counting)
- 是通过在对象头中分配一个空间来保存该对象被引用的次数(Reference Count)。如果该对象被其它对象引用,则它的引用计数加1,如果删除对该对象的引用,那么它的引用计数就减1,当该对象的引用计数为0时,那么该对象就会被回收。
- 优点:
- 引用计数收集器可以很快的执行,交织在程序运行中。
- 对程序需要不被长时间打断的实时环境比较有利。
- 缺点:
- 无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。
可达性分析算法
通过一系列的称为"GC Roots" 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是不可用的。此算法解决
了循环引用的问题。
Root对象
- 在Java语言中,可作为GC Roots 的对象包括下面几种
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 字符串常量池里的引用
- 本地方法栈中JNI(Native方法)引用的对象
- synchronized锁对象
- Class对象
Java对象引用
- Java中对象引用分为
- 强引用(Strong Reference)
- 垃圾收集器永远不会回收存活的强引用对象。类似"Object obj=new Object()"这类的引用
- 软引用(Soft Reference)
- 还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
- 弱引用(Weak Reference)
- 当垃圾收集器工作时,无论内存是否足够,都会回收掉只被弱引用关联的对象。
- 虚引用(Phantom Reference)
- 无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
- 强引用(Strong Reference)
- 这4 种引用强度依次逐渐减弱。
4.3 Java的垃圾回收
- Java中垃圾回收主要是针对堆中内存进行回收。
- 从次数上来说:
- 频繁回收年轻代
- 较少回收老年代
- 基本不动永久代(元空间)
4.4 常见垃圾回收算法
- 标记-清除算法(Mark-Sweep)
- 标记-整理算法(Mark-Compact)
- 复制算法(Copying)
- 分代收集算法(Generation Collection)
标记-清除算法(Mark-Sweep)
- 第一步(标记),利用可达性遍历内存,把“存活”对象和“垃圾”对象进行标记。
- 第二步(清除),我们再遍历一遍,把所有“垃圾”对象所占的空间直接清空即可。
特点:
- 实现简单
- 容易产生碎片,需要记录可用空间。
标记-整理算法(Mark-Compact)
- 第一步(标记):利用可达性遍历内存,把“存活”对象和“垃圾”对象进行标记。
- 第二步(整理):把所有存活对象堆到同一个地方,这样就没有内存碎片了。
特点:
- 适合存活对象多,垃圾少的情况
- 需要整理的过程,下次直接使用指针碰撞分配空间。
复制算法(Copying)
将内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完了,就将还活着的对象复制到另一块上,然后再把使用过的内存空间一次性清理掉。
特点:
- 简单
- 不会产生碎片
- 内存利用率太低,只用了一半
分代收集算法
- 当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,根据对象存活周期的不同将内存划分为几块并采用不同的垃圾收集算法。
- Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
- 新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
- 老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清除”或者“标记—整理”算法来进行回收。
- Minor GC:
- 新生代GC,指发生在新生代的垃圾收集动作,所有的Minor GC都会触发全世界的暂停(stop-the-world),停止应用程序的线程,不过这个过程非常短暂。
- Major GC/Full GC:指发生在老年代和永久代的GC。
5 垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现
5.1 Serial收集器(串行收集器)
- Serial 收集器是最基本、发展历史最悠久的收集器,曾经是虚拟机新生代收集的唯一选择。这是一个单线程的收集器,在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
- "Stop The World"这项工作实际上是由虚拟机在后台自动发起和自动完成的,在用户不可见的情况下把用户正常工作的线程全部停掉,这对很多应用来说都是难以接受的。
5.2 ParNew收集器
- ParNew 收集器其实就是Serial 收集器的多线程版本。
- 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU 上。
5.3 Parallel Scavenge收集器
- Parallel Scavenge 收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)。
- 所谓吞吐量就是CPU 用于运行用户代码的时间与CPU 总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100 分钟,其中垃圾收集花掉1分钟,那吞吐量就是99% 。
5.4 Serial Old 收集器
- Serial Old 是Serial 收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。
5.5 Parallel Old收集器
- Parallel Old 是Parallel Scavenge 收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6 中才开始提供的,在此之前,新生代的Parallel Scavenge 收集器一直处于比较尴尬的状态。
- 原因是,如果新生代选择了Parallel Scavenge 收集器,老年代除了Serial Old(PS MarkSweep)收集器外别无选择(Parallel Scavenge 收集器无法与CMS 收集器配合工作)。
5.6 CMS收集器
- CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
- 尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS 收集器就非常符合这类应用的需求。
- CMS 收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent mark)
- 重新标记(CMS remark)
- 并发清除(CMS concurrent sweep)
- 初始标记、重新标记这两个步骤仍然需要"Stop The World"。初始标记仅仅只是标记一下GC Roots 能直接关联到的对象,速度很快,并发标记阶段就是进行GC RootsTracing 的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
- 由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS 收集器的内存回收过程是与用户线程一起并发执行的。
5.7 G1收集器
- G1(Garbage-First)收集器是当今收集器技术发展的最前沿成果之一。
- 它将整个Java 堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region (不需要连续)的集合。
5.8 其他收集器
JDK1~JDK14十种垃圾收集器
ZGC、Epsilon、Shenandoah
- Java 11引入了ZGC,宣称暂停时间不超过10ms,支持4TB,JDK13 到了16TB,即使是TB级内存也只停顿1-10ms。
- Java11还引入一个什么都不做的垃圾收集器(A NoOp Garbage Collector)Epsilon收集器。开发一个处理内存分配但不实现任何实际内存回收机制的GC, 一旦可用堆内存用完, JVM就会退出。主要用途性能测试,内存压力测试。
- JDK12新增的一个名为Shenandoah的GC算法,它的evacuation阶段工作能通过与正在运行中Java工作线程同时进行(即并发,concurrent),从而减少GC的停顿时间。
[/wppay]