主页
  • 主页
  • 分类
  • 热文
  • 教程
  • 面试
  • 标签
Java

Java 基础

Java 主页
Java 概述
Java 历史
Java 功能
Java 与 C++
Java JVM(Java虚拟机)
Java JDK、JRE 和 JVM
Java Hello World 程序
Java 环境设置
Java 基本语法
Java 变量类型
Java 数据类型
Java 类型转换
Java Unicode 系统
Java 基本运算符
Java 注释
Java 用户输入
Java 日期和时间

Java 控制语句

Java 循环控制
Java 决策结构
Java if-else 语句
Java switch 语句
Java for 循环
Java for each 循环
Java while 循环
Java do...while 循环
Java break 语句
Java continue 语句

Java 面向对象编程

Java OOP概念
Java 类和对象
Java 类属性
Java 类方法
Java 方法
Java 变量作用域
Java 构造函数
Java 访问修饰符
Java 继承
Java 聚合
Java 多态
Java 覆盖
Java 方法重载
Java 动态绑定
Java 静态绑定
Java 实例初始化块
Java 抽象
Java 封装
Java 接口
Java 包
Java 内部类
Java 静态类
Java 匿名类
Java 单例类
Java 包装类
Java 枚举类
Java 枚举构造函数
Java 枚举字符串

Java 内置类

Java 数字
Java 布尔值
Java 字符
Java 数组
Java 数学类

Java 文件处理

Java 文件
Java 创建文件
Java 写入文件
Java 读取文件
Java 删除文件
Java 目录操作
Java I/O流

Java 错误和异常

Java 异常
Java Try Catch
Java try-with-resources
Java 多个 Catch
Java 嵌套 try
Java finally
Java 抛出异常
Java 异常传播
Java 内置异常
Java 自定义异常

Java 多线程

Java 多线程
Java 线程生命周期
Java 创建线程
Java 启动线程
Java 加入线程
Java 命名线程
Java 线程调度
Java 线程池
Java 主线程
Java 线程优先级
Java 守护线程
Java 线程组
Java JVM 关闭

Java 同步

Java 线程同步
Java 块同步
Java 静态同步
Java 线程间通信
Java 线程死锁
Java 中断线程
Java 线程控制
Java 可重入锁

Java 网络

Java 网络编程
Java 套接字编程
Java URL 处理
Java URL 类
Java URLConnection 类
Java HttpURLConnection 类
Java Socket 类
Java 泛型

Java 集合

Java 集合框架
Java 集合接口

Java 接口

Java 列表接口
Java 队列接口
Java 映射接口
Java SortedMap 接口
Java 集合(Set)接口
Java SortedSet 接口

Java 数据结构

Java 数据结构
Java 枚举接口

Java 集合算法

Java 迭代器
Java 比较器
Java Comparable 接口

Java 高级

Java 命令行参数
Java Lambda 表达式
Java 发送电子邮件
Java 小应用程序
Java Javadoc
Java 自动装箱和拆箱
Java mismatch() 方法
Java REPL
Java 多版本发布 JAR
Java 私有接口方法
Java 金刚石操作符
Java 多分辨率图像 API
Java 集合的工厂方法
Java 模块系统
Java Nashorn 引擎
Java Optional 类
Java 方法引用
Java 功能接口
Java 默认方法
Java Base64 工具类
Java Switch 表达式
Java Collectors.teeing() 方法
Java 基准测试
Java 文本块
Java 动态CDS
Java ZGC
Java NullPointerException
Java jpackage
Java 密封类
Java 记录
Java 隐藏类
Java instanceof
Java 紧凑数字格式化
Java 垃圾回收
Java JIT 编译器

Java 杂项

Java 递归
Java 正则表达式
Java 序列化
Java 字符串类
Java 进程 API
Java Stream API
Java @Deprecated 注释
Java CompletableFuture API
Java Streams
Java 日期时间 API

基础

Java 主页
Java 概述
Java 历史
Java 功能
Java 与 C++
Java JVM(Java虚拟机)
Java JDK、JRE 和 JVM
Java Hello World 程序
Java 环境设置
Java 基本语法
Java 变量类型
Java 数据类型
Java 类型转换
Java Unicode 系统
Java 基本运算符
Java 注释
Java 用户输入
Java 日期和时间

控制语句

Java 循环控制
Java 决策结构
Java if-else 语句
Java switch 语句
Java for 循环
Java for each 循环
Java while 循环
Java do...while 循环
Java break 语句
Java continue 语句

面向对象编程

Java OOP概念
Java 类和对象
Java 类属性
Java 类方法
Java 方法
Java 变量作用域
Java 构造函数
Java 访问修饰符
Java 继承
Java 聚合
Java 多态
Java 覆盖
Java 方法重载
Java 动态绑定
Java 静态绑定
Java 实例初始化块
Java 抽象
Java 封装
Java 接口
Java 包
Java 内部类
Java 静态类
Java 匿名类
Java 单例类
Java 包装类
Java 枚举类
Java 枚举构造函数
Java 枚举字符串

内置类

Java 数字
Java 布尔值
Java 字符
Java 数组
Java 数学类

文件处理

Java 文件
Java 创建文件
Java 写入文件
Java 读取文件
Java 删除文件
Java 目录操作
Java I/O流

错误和异常

Java 异常
Java Try Catch
Java try-with-resources
Java 多个 Catch
Java 嵌套 try
Java finally
Java 抛出异常
Java 异常传播
Java 内置异常
Java 自定义异常

多线程

Java 多线程
Java 线程生命周期
Java 创建线程
Java 启动线程
Java 加入线程
Java 命名线程
Java 线程调度
Java 线程池
Java 主线程
Java 线程优先级
Java 守护线程
Java 线程组
Java JVM 关闭

同步

Java 线程同步
Java 块同步
Java 静态同步
Java 线程间通信
Java 线程死锁
Java 中断线程
Java 线程控制
Java 可重入锁

网络

Java 网络编程
Java 套接字编程
Java URL 处理
Java URL 类
Java URLConnection 类
Java HttpURLConnection 类
Java Socket 类
Java 泛型

集合

Java 集合框架
Java 集合接口

接口

Java 列表接口
Java 队列接口
Java 映射接口
Java SortedMap 接口
Java 集合(Set)接口
Java SortedSet 接口

数据结构

Java 数据结构
Java 枚举接口

集合算法

Java 迭代器
Java 比较器
Java Comparable 接口

高级

Java 命令行参数
Java Lambda 表达式
Java 发送电子邮件
Java 小应用程序
Java Javadoc
Java 自动装箱和拆箱
Java mismatch() 方法
Java REPL
Java 多版本发布 JAR
Java 私有接口方法
Java 金刚石操作符
Java 多分辨率图像 API
Java 集合的工厂方法
Java 模块系统
Java Nashorn 引擎
Java Optional 类
Java 方法引用
Java 功能接口
Java 默认方法
Java Base64 工具类
Java Switch 表达式
Java Collectors.teeing() 方法
Java 基准测试
Java 文本块
Java 动态CDS
Java ZGC
Java NullPointerException
Java jpackage
Java 密封类
Java 记录
Java 隐藏类
Java instanceof
Java 紧凑数字格式化
Java 垃圾回收
Java JIT 编译器

杂项

Java 递归
Java 正则表达式
Java 序列化
Java 字符串类
Java 进程 API
Java Stream API
Java @Deprecated 注释
Java CompletableFuture API
Java Streams
Java 日期时间 API

Java 垃圾回收


上一章 下一章

Java 虚拟机(JVM)管理着 Java 对象的生命周期。一旦程序员创建了一个对象,就不需要担心其后续的生命周期。JVM 会自动找到那些不再使用的对象,并从堆中回收它们所占用的内存。

Java 垃圾回收是什么?

垃圾回收(GC)是 JVM 执行的一项主要操作,优化它能显著提升应用程序的性能。现代 JVM 提供了多种垃圾回收算法,我们需要了解应用的需求来决定使用哪种算法。

在 Java 中,你无法像在 C/C++ 这样的非垃圾回收语言中那样编程性地释放对象。因此,在 Java 中不会出现悬挂引用。但是,可能会存在空引用(引用指向一块 JVM 永远不会存储对象的内存区域)。每当使用空引用时,JVM 就会抛出 NullPointerException。

尽管由于垃圾回收的存在使得 Java 程序中的内存泄漏较为罕见,但仍然可能发生。我们将在本章末尾创建一个内存泄漏的例子。

垃圾回收器的种类

现代 JVM 使用以下几种垃圾回收器:

  • Serial collector
  • Throughput collector
  • CMS collector
  • G1 collector

以上每种算法都执行相同的工作——找出不再使用的对象并回收它们在堆中所占的内存。一种简单的但天真的方法是计算每个对象的引用计数并在引用计数变为零时释放它(这也被称为引用计数)。为什么说这是天真的?考虑一个循环链表。每个节点都有对其它节点的引用,但整个对象没有从任何地方被引用,理想情况下应该被释放。

内存合并

JVM 不仅释放内存,还会将小块内存合并成更大的一块。这样做是为了防止内存碎片化。

简单来说,典型的垃圾回收算法执行以下活动:

  • 寻找未使用的对象
  • 释放它们在堆中所占的内存
  • 合并碎片

GC 运行时需要停止应用程序线程。这是因为当 GC 运行时,它会移动对象的位置,因此这些对象在此期间不能被使用。这样的暂停被称为“全局暂停”,减少这些暂停的频率和持续时间是我们优化 GC 的目标。

下面是一个简单的内存合并演示:

阴影部分是需要释放的对象。即使在所有空间都被重新分配后,我们也只能分配最大为 75Kb 的对象。即便如此,我们仍有 200Kb 的自由空间:

垃圾回收中的代

大多数 JVM 将堆分为三代——年轻代(YG)、老年代(OG)以及永久代(也称为持久代)。

让我们看一个简单的例子。Java 中的 String 类是不可变的。这意味着每次你需要更改 String 对象的内容时,都必须创建一个新的对象。假设你在循环中对字符串修改了 1000 次,如下所示:

String str = "G11 GC";

for(int i = 0 ; i < 1000; i++) {
   str = str + String.valueOf(i);
}

在每次循环中,我们创建一个新的字符串对象,而上一次迭代创建的字符串变得无用(即没有引用)。该对象的生命周期仅为一次迭代——它们很快就会被 GC 收集。这种短寿命的对象会被保存在堆的年轻代区域中。从年轻代收集对象的过程被称为小垃圾回收(minor garbage collection),并且总是会导致全局暂停。

小垃圾回收

随着年轻代被填满,GC 会执行一次小垃圾回收。无用的对象被丢弃,存活的对象被移到老年代。这个过程会导致应用线程暂停。

这里我们可以看到这样的代设计带来的好处。年轻代只是堆的一小部分,并且很快就会被填满。但处理它的时间远远少于处理整个堆所需的时间。因此,这种情况下全局暂停的时间更短,尽管可能更频繁。我们应该始终争取更短的暂停时间,即使它们更频繁。

完整垃圾回收

年轻代被分为两个空间——伊甸园(eden)和幸存者空间。在伊甸园中幸存下来的对象会被移动到幸存者空间,而在幸存者空间中继续存活的对象会被移动到老年代。年轻代在收集过程中会被压缩。

随着对象被移动到老年代,它最终也会被填满,并需要被收集和压缩。不同的算法对此采取不同的方法。有些算法会停止应用线程(这会导致较长的全局暂停,因为老年代相比年轻代要大得多),而有些算法则会在应用线程持续运行的同时并发地执行。这一过程被称为完整垃圾回收(full GC)。两种这样的收集器是 CMS 和 G1。

垃圾回收器调优

我们可以根据需要调整垃圾回收器。以下是我们可以根据情况配置的一些领域:

  • 堆大小分配
  • 代大小分配
  • 永久代和元空间配置

让我们详细了解每一点及其影响。我们还将讨论基于可用内存、CPU 配置和其他相关因素的建议。

堆大小分配

堆大小是 Java 应用程序性能的一个重要因素。如果太小,它会频繁填满,导致 GC 频繁执行。另一方面,如果我们增加堆的大小,虽然它不需要那么频繁地被收集,但暂停的时间长度会增加。

此外,增加堆大小会对底层操作系统产生严重的开销。操作系统通过分页技术让我们的应用程序看到比实际可用更多的内存。操作系统通过使用磁盘上的交换空间来管理这一点,将程序的不活跃部分复制到其中。当需要这些部分时,操作系统再从磁盘复制回内存。

假设一台机器有 8G 的内存,而 JVM 可以看到 16G 的虚拟内存,JVM 并不知道实际上系统只有 8G 的内存。它只会向操作系统请求 16G 的内存,一旦获得这些内存,它将继续使用。操作系统将不得不频繁地在磁盘和内存之间交换数据,这对系统来说是一个巨大的性能开销。

接着是完整 GC 期间发生的暂停。由于 GC 需要对整个堆进行收集和压缩,它将不得不等待大量虚拟内存从磁盘中交换出来。对于并发收集器而言,后台线程将不得不等待大量数据从交换空间复制到内存中。

所以,如何确定最优的堆大小就成了一个问题。第一条规则是永远不要向操作系统请求超过实际存在的内存。这完全避免了频繁交换的问题。如果机器上有多个运行的 JVM,那么所有 JVM 请求的总内存应该小于系统中的实际物理内存。

你可以通过以下两个标志来控制 JVM 请求的内存大小:

  • -XmsN 控制初始请求的内存大小。
  • -XmxN 控制可以请求的最大内存。

这两个标志的默认值取决于底层的操作系统。例如,对于运行在 MacOS 上的 64 位 JVM,-XmsN 为 64M,-XmxN 为最小的 1G 或总物理内存的四分之一。

需要注意的是,JVM 可以在这两个值之间自动调整。例如,如果它注意到发生了太多的 GC,它会一直增加内存大小,直到达到 -XmxN,并且满足预期的性能目标为止。

如果你确切知道你的应用需要多少内存,那么可以设置 -XmsN = -XmxN。在这种情况下,JVM 不需要确定“最优”的堆值,从而使 GC 过程变得更高效。

Java 中的代大小分配

你可以决定想要分配给年轻代(YG)多少堆空间,以及想要分配给老年代(OG)多少堆空间。这两个值都会影响到我们应用程序的性能。

如果年轻代的大小非常大,那么它将被较少地收集。这将导致较少的对象晋升到老年代。另一方面,如果你过多地增加老年代的大小,那么收集和压缩它将花费太多时间,这将导致长时间的 STW(Stop-The-World)暂停。因此,用户必须在这两个值之间找到平衡。

以下是你可以用来设置这些值的标志:

  • -XX:NewRatio=N: 年轻代与老年代的比例(默认值=2)
  • -XX:NewSize=N: 年轻代的初始大小
  • -XX:MaxNewSize=N: 年轻代的最大大小
  • -XmnN: 使用此标志将 NewSize 和 MaxNewSize 设置为相同的值

年轻代的初始大小由 NewRatio 的值通过以下公式确定:

(总堆大小)/(newRatio + 1)

由于 newRatio 的初始值为 2,上述公式给出的年轻代初始值为总堆大小的 1/3。你总是可以通过明确指定年轻代的大小来覆盖这个值。NewSize 标志没有任何默认值,如果没有明确设置,年轻代的大小将继续使用上述公式进行计算。

永久代和元空间配置

永久代和元空间是 JVM 存放类元数据的堆区。在 Java 7 中,这部分空间被称为“永久代”,而在 Java 8 及之后版本中,它被称为“元空间”。这些信息被编译器和运行时所使用。

你可以使用以下标志来控制永久代的大小:-XX:PermSize=N 和 -XX:MaxPermSize=N。元空间的大小可以使用 -XX:MetaspaceSize=N 和 -XX:MaxMetaspaceSize=N 来控制。

当标志值未设置时,永久代和元空间的管理方式有所不同。默认情况下,两者都有一个默认的初始大小。但是,元空间可以占据所需的尽可能多的堆空间,而永久代占据的空间不能超过默认的初始值。例如,64 位 JVM 具有 82M 的最大永久代大小。

请注意,由于元空间可以占据未指定数量的内存,可能会导致内存溢出错误。每当这些区域正在调整大小时,就会发生完整的垃圾回收。因此,在启动时,如果有很多类被加载,元空间可能会不断调整大小,从而导致每次都会发生完整的垃圾回收。因此,对于大型应用程序来说,如果初始元空间大小太小,启动时间会很长。增加初始大小是个好主意,因为它减少了启动时间。

尽管永久代和元空间持有类元数据,但这并不是永久性的,并且空间也会被垃圾收集器回收,就像对象一样。这通常发生在服务器应用程序中。每当向服务器进行新的部署时,旧的元数据需要被清理,因为新的类加载器现在需要空间。这个空间由垃圾收集器释放。

上一章 下一章
阅读号二维码

关注阅读号

联系二维码

联系我们

© 2024 Yoagoa. All rights reserved.

粤ICP备18007391号

站点地图