概述
目的
本章节介绍了垃圾回收如何与Hotspot JVM 配置使用的基础知识。了解垃圾收集器的工作原理,了解Java SE 7Hotspot JVM中有哪些垃圾收集器可用
完成时间
介绍
本章涵盖了 Java 中 Java 虚拟机 (JVM) 垃圾收集 (GC) 的基础知识。在第一部分中,提供了 JVM 的概述以及对垃圾收集和性能的介绍。接下来为学生提供有关垃圾收集如何在 JVM 中工作的分步指南。接下来提供动手活动,让学习者试用 Java JDK 中提供的一些监控工具,并将他们刚刚学到的关于垃圾收集的知识付诸实践。最后,提供了一个部分,介绍 Hotspot JVM 中可用的垃圾收集方案选项。
Java技术和JVM
Java概述
Java 是一种编程语言和计算平台,由 Sun Microsystems 于 1995 年首次发布。它是支持 Java 程序(包括实用程序、游戏和商业应用程序)的基础技术。Java 运行在全球超过 8.5 亿台个人计算机上,以及全球数十亿台设备上,包括移动和电视设备。Java 由许多关键组件组成,这些组件作为一个整体构成了 Java 平台。
Java 运行时版(JRE)
当您下载 Java 时,您将获得 Java 运行时环境 (JRE)。JRE 由 Java 虚拟机 (JVM)、Java 平台核心类和支持的 Java 平台库组成。要在您的计算机上运行 Java 应用程序,这三者都是必需的。
Java 编程语言
Java 是一种面向对象的编程语言,包括以下特性。
- 平台独立性 - Java 应用程序被编译成字节码,字节码存储在类文件中并加载到 JVM 中。由于应用程序在 JVM 中运行,因此它们可以在许多不同的操作系统和设备上运行。
- 面向对象 - Java 是一种面向对象的语言,它采用了 C 和 C++ 的许多特性并对其进行了改进。
- 自动垃圾收集 - Java 自动分配和释放内存,因此程序不会负担该任务。
- 丰富的标准库 - Java 包含大量预制对象,可用于执行输入/输出、网络和日期操作等任务。
Java 开发工具包
Java 开发工具包 (JDK) 是一组用于开发 Java 应用程序的工具。使用 JDK,您可以编译用 Java 编程语言编写的程序并在 JVM 中运行它们。此外,JDK 提供了用于打包和分发应用程序的工具。
JDK 和 JRE 共享 Java 应用程序编程接口 ( Java API )。Java API 是开发人员用来创建 Java 应用程序的预打包库的集合。Java API 通过提供工具来完成许多常见的编程任务,包括字符串操作、日期/时间处理、网络和实现数据结构(例如,列表、映射、堆栈和队列),从而使开发更容易。
Java虚拟机
Java 虚拟机 (JVM) 是一种抽象的计算机器。JVM 是一个程序,对于编写在其中执行的程序来说,它看起来像一台机器。通过这种方式,Java 程序被写入相同的接口和库集。特定操作系统的每个 JVM 实现将 Java 编程指令转换为在本地操作系统上运行的指令和命令。这样,Java 程序就实现了平台独立性。
Java 虚拟机的第一个原型实现是在 Sun Microsystems, Inc. 完成的,它模拟了由类似于当代个人数字助理 (PDA) 的手持设备托管的软件中的 Java 虚拟机指令集。Oracle 当前的实现在移动、桌面和服务器设备上模拟 Java 虚拟机,但 Java 虚拟机不假定任何特定的实现技术、主机硬件或主机操作系统。它不是固有的解释,但也可以通过将其指令集编译为硅 CPU 的指令集来实现。它也可以在微代码中或直接在硅中实现。
Java 虚拟机对 Java 编程语言一无所知,只知道一种特定的二进制格式,即类文件格式。类文件包含 Java 虚拟机指令(或字节码)和符号表,以及其他辅助信息。
为了安全起见,Java 虚拟机对类文件中的代码施加了很强的语法和结构约束。但是,任何具有可以用有效类文件表示的功能的语言都可以由 Java 虚拟机托管。被普遍可用的、独立于机器的平台所吸引,其他语言的实现者可以将 Java 虚拟机作为他们语言的交付工具。
探索 JVM 架构
热点架构
HotSpot JVM 的架构支持强大的特性和能力基础,支持实现高性能和大规模可扩展性的能力。例如,HotSpot JVM JIT 编译器生成动态优化。换句话说,它们在 Java 应用程序运行时做出优化决策,并生成针对底层系统架构的高性能本地机器指令。此外,通过其运行时环境和多线程垃圾收集器的成熟进化和持续工程,HotSpot JVM 甚至在最大的可用计算机系统上也能产生高可扩展性。
图片可以参照JVM官网观看
JVM 的主要组件包括类加载器、运行时数据区和执行引擎。
关键热点组件
下图中突出显示了与性能相关的 JVM 关键组件。
JVM 的三个组件在调优性能时重点关注。该堆是你的对象数据的存储位置。该区域然后由启动时选择的垃圾收集器管理。大多数调整选项与调整堆大小和选择最适合您情况的垃圾收集器有关。JIT 编译器对性能也有很大影响,但很少需要使用较新版本的 JVM 进行调整。
性能基础
通常,在调整 Java 应用程序时,重点是两个主要目标之一:响应能力 (responsiveness) 或吞吐量 (throughput)。随着教程的进行,我们将回顾这些概念。
响应能力(responsiveness)
响应性是指应用程序或系统对请求的数据做出响应的速度。例子包括:
- 桌面 UI 对事件的响应速度有多快
- 网站返回页面的速度
- 返回数据库查询的速度
对于注重响应的应用程序,大的暂停时间是不可接受的。重点是在短时间内做出反应。
吞吐量
吞吐量侧重于在特定时间段内最大化应用程序的工作量。如何测量吞吐量的示例包括:
- 在给定时间内完成的交易数量。
- 批处理程序在一小时内可以完成的作业数。
- 一个小时内可以完成的数据库查询数。
对于专注于吞吐量的应用程序,高暂停时间是可以接受的。由于高吞吐量应用程序在较长时间内专注于基准测试,因此不考虑快速响应时间。
描述垃圾回收(Garbage Collection)
什么是自动垃圾收集?
自动垃圾回收是查看堆内存、识别哪些对象正在使用、哪些未使用以及删除未使用对象的过程。一个使用中的对象,或一个引用的对象,意味着你的程序的某些部分仍然维护着一个指向那个对象的指针。未使用的对象或未引用的对象不再被程序的任何部分引用。因此可以回收未被引用的对象使用的内存。
在像 C 这样的编程语言中,分配和释放内存是一个手动过程。在 Java 中,释放内存的过程由垃圾收集器自动处理。基本过程可以描述如下。
第一步:标记
该过程的第一步称为标记。这是垃圾收集器识别哪些内存在使用,哪些没有使用的地方。
在标记阶段扫描所有对象以进行此确定。如果必须扫描系统中的所有对象,这可能是一个非常耗时的过程。
第二步:正常删除
内存分配器保存对可以分配新对象的可用空间块的引用。
步骤 2a:通过压缩删除
为了进一步提高性能,除了删除未引用的对象外,还可以压缩剩余的引用对象。通过一起移动引用的对象,这使得新的内存分配更容易和更快。
为什么要分代垃圾回收?(分代)
如前所述,必须标记和压缩 JVM 中的所有对象是低效的。随着越来越多的对象被分配,对象列表越来越大,导致垃圾收集时间越来越长。然而,对应用程序的实证分析表明,大多数对象都是短暂的。
如您所见,随着时间的推移,分配的对象越来越少。事实上,大多数物体的寿命都很短,如图表左侧的较高值所示。
JVM的代Generations
从对象分配行为中学到的信息可用于增强 JVM 的性能。因此,堆被分成更小的部分或几代。堆部分是:新生代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation)
新生代中有三个东西eden称为伊甸园区,和幸存区(细分为幸存1区和幸存2区),一般对象创建会在伊甸园区,之后经过筛选活下来的在幸存1区和幸存2区,之后活下来的会过度到老年代
在Young Generation是所有新对象分配和老化。当年轻代填满时,这会导致轻微的垃圾回收\。假设高对象死亡率,可以优化次要集合。一个充满死对象的年轻代被收集得非常快。一些幸存的对象会老化并最终移动到老年代。
停止世界事件(Stop the World Event)- 所有小垃圾收集都是“停止世界”事件。这意味着所有应用程序线程都将停止,直到操作完成。次要垃圾收集始终是Stop the World 事件。
在Old Generation用来存放长幸存的对象。通常,为年轻代对象设置一个阈值,当达到该年龄时,对象将移动到年老代。最终需要收集老年代。此事件称为主要垃圾收集(major garbage collection)。
主要的垃圾收集也是 Stop the World 事件。通常,主要收集要慢得多,因为它涉及所有活动对象。所以对于响应式应用程序,主要的垃圾收集应该被最小化。另请注意,主要垃圾收集的 Stop the World 事件的长度受用于老年代空间的垃圾收集器类型的影响。
Permanent generation包含元数据需要由JVM来描述应用程序使用的类和方法。JVM 在运行时根据应用程序使用的类填充永久代。此外,Java SE 库类和方法可能存储在这里。
如果 JVM 发现不再需要类并且其他类可能需要空间,则可能会收集(卸载)这些类。永久代包含在完整的垃圾收集中。
分代垃圾回收过程
现在您已经了解了为什么将堆分成不同的代,是时候看看这些空间究竟是如何相互作用的了。下面的图片走遍了 JVM 中的对象分配和老化过程。
1、首先,任何新对象都被分配到 eden 空间。两个幸存者空间一开始都是空的。
2、当伊甸园空间填满时,会触发一个次要的垃圾收集。
3、引用的对象被移动到第一个幸存者空间。当 eden 空间被清除时,未引用的对象将被删除。
4、在下一次小 GC 中,同样的事情发生在 eden 空间。未引用的对象被删除,引用的对象被移动到幸存者空间。然而,在这种情况下,它们被移动到第二个幸存者空间(S1)。此外,来自第一个幸存者空间 (S0) 上最后一次次要 GC 的对象的年龄增加并移动到 S1。一旦所有幸存的对象都被移动到 S1,S0 和 eden 都会被清除。请注意,我们现在在幸存者空间中有不同年龄的对象。
5、在下一个次要 GC 中,重复相同的过程。然而这一次幸存者空间切换。引用的对象移动到 S0。幸存的物体已经老化。伊甸园和S1被清除。
6、在minor GC之后,当老化的对象达到一定的年龄阈值(本例中为8)时,它们会从年轻代提升到老年代。
7、随着次要 GC 的继续发生,对象将继续被提升到老年代空间。
8、所以这几乎涵盖了年轻一代的整个过程。最终,将在老年代执行一次主要 GC,清理并压缩该空间。
Java垃圾收集器
您现在了解了垃圾收集的基础知识,并观察了垃圾收集器在示例应用程序中的运行情况。在本节中,您将了解可用于 Java 的垃圾收集器以及选择它们所需的命令行开关。
常见的堆相关开关
有许多不同的命令行开关可以与 Java 一起使用。本节介绍一些更常用的开关,这些开关也在本 OBE 中使用。
转变 | 描述 |
---|---|
-Xms |
设置 JVM 启动时的初始堆大小。 |
-Xmx |
设置最大堆大小。 |
-Xmn |
设置年轻代的大小。 |
-XX:PermSize | 设置永久代的起始大小。 |
-XX:MaxPermSize | 设置永久代的最大大小 |
-XX:+PrintGCDetails | 打印垃圾回收信息 |
-XX:HeapDumpOnOutOfMemoryError | oom DUMP |
串行 GC
串行收集器是 Java SE 5 和 6 中客户端风格机器的默认设置。使用串行收集器,次要和主要垃圾收集都是串行完成的(使用单个虚拟 CPU)。此外,它使用了一种标记紧凑的收集方法。此方法将旧内存移动到堆的开头,以便将新的内存分配分配到堆末尾的单个连续内存块中。这种内存压缩可以更快地将新的内存块分配给堆。
串行 GC 是大多数应用程序的首选垃圾收集器,这些应用程序对暂停时间要求不高并且在客户端类型的机器上运行。它仅利用单个虚拟处理器进行垃圾收集工作(因此得名)。尽管如此,在今天的硬件上,串行 GC 可以有效地管理许多具有数百 MB 的 Java 堆的非平凡应用程序,最坏情况的暂停时间相对较短(完全垃圾收集大约需要几秒钟)。
串行 GC 的另一个流行用途是在同一台机器上运行大量 JVM 的环境中(在某些情况下,JVM 多于可用处理器!)。在这种环境中,当 JVM 进行垃圾收集时,最好只使用一个处理器来最小化对其余 JVM 的干扰,即使垃圾收集可能持续更长时间。串行 GC 很好地适应了这种权衡。
最后,随着具有最少内存和很少内核的嵌入式硬件的激增,串行 GC 可能会卷土重来。
将参数放入到VM Option中,如-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
概括
在章中,您已概述了 Java JVM 上的垃圾收集系统。首先,您了解了堆和垃圾收集器如何成为任何 Java JVM 的关键部分。自动垃圾收集是使用分代垃圾收集方法完成的。一旦你了解了这个过程,你就可以使用 Visual VM 工具观察它。最后,您查看了 Java Hospot JVM 中可用的垃圾收集器。
在本教程中,您学习了:
- Java JVM 的组件
- 自动垃圾收集的工作原理
- 分代垃圾回收过程
- 如何使用 Visual VM 监控您的 JVM
- JVM 上可用的垃圾收集器类型
在本教程中,您学习了:
- Java JVM 的组件
- 自动垃圾收集的工作原理
- 分代垃圾回收过程
- 如何使用 Visual VM 监控您的 JVM
- JVM 上可用的垃圾收集器类型
资源
有关更多信息和相关信息,请参阅这些站点和链接。
GC题目总结
JVM的内存模型和分区 ~ 详细到每个区放什么?
jvm内存模型,分区。详细到每个区都放什么。jvm内存模型按照线程分可分为线程独占和线程共享两种.
线程独占 本地方法栈,虚拟机方法栈,程序计数器.
线程共享 堆,方法区
首先本地方法栈:本地方法栈放的就是本地方法的栈针,这种方法一般是由c语言底层写的.通过JNI调用.
虚拟机方法栈:它是以栈帧为单位存储的.栈帧中包含
方法索引,输入输出参数,局部变量八大基本类型,操作数栈,动态链接,父帧,子帧.
堆:堆是线程共享,存储的是对象以及数组实例,引用在方法栈中.
方法区:方法区主要存储的是类信息包括方法信息,字段信息,类名,并不是类实例,静态变量常量,运行时常量池等等.
pc寄存器:因为pc寄存器是线程独占的,所以每个线程都有一个寄存器.他不会发生内存溢出的情况因为他不会因为程序的执行而改变寄存器中数据所占的空间。它存储的是程序虚拟机字节码的地址.
堆里面的分区有哪些?Eden,form,to,老年区,说说它们的特点!
GC的算法有哪些?标记清除法,标记压缩,复制算法,引用计数器,怎么用的?
轻GC和重GC分别在什么时候发生?
标记清除法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时候计数器为0时的对象就是不能再被使用。
对计数器的对象进行扫描,对活着的对象进行标记,对没有标记的对象进行清除
优点:不需要额外的空间!
缺点:两次扫描,严重浪费时间,会产生内存碎片。
复制算法
用于幸存区的from与to之间的转换。
好处:没有内存的碎片
缺点:浪费了内存空间~:多了一半空间永远是空的to。假设对象存活(极端情况)
复制算法最佳使用场景:对象存活度较低的时候;新生区
标记压缩
防止内存碎片产生,优化标记清除算法的,再次扫描,向一端移动存活的对象多了一个移动成本
算法总结
内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)
内存整齐度:复制算法 = 标记压缩算法>标记清除算法
内存利用度:标记压缩算法 = 标记清除算法 > 复制算法
思考一个问题:难道没有最优算法吗?
答案:没有,没有最好的算法,只有最合适的算法—>GC:分代收集算法
年轻代:
- 存活率低
- 复制算法!
老年代:
- 区域大:存活率
- 标记清除(内存碎片不是太多) + 标记压缩混合实现