网站推广方式都有哪些,怎么做微信里的网页网站链接,如何写网站建设方案书,微信小程序在哪里制作这是我打算写的一系列博客文章的第一部分#xff0c;其目的是解释垃圾回收在现实世界中的工作方式#xff08;特别是在JVM中 #xff09;。 我将介绍一些我认为对于充分理解垃圾收集对于实际目的是必要的理论#xff0c;但是将其降至最低。 其动机是在各种情况下#xff0… 这是我打算写的一系列博客文章的第一部分其目的是解释垃圾回收在现实世界中的工作方式特别是在JVM中 。 我将介绍一些我认为对于充分理解垃圾收集对于实际目的是必要的理论但是将其降至最低。 其动机是在各种情况下例如在Cassandra邮件列表中不断出现与垃圾回收相关的问题。 尝试提供帮助时的问题是在针对特定情况定制的邮件列表回复中临时解释垃圾收集的细微差别是一项过多的工作而您几乎没有关于这种情况的足够信息来告诉某人他们的情况特殊问题是由引起的。 我希望本指南将成为我回答这些问题的参考。 我希望它会足够详细以便有用但易于消化并且对于广泛的读者来说也不够学术性。 我非常感谢您对我需要澄清改进彻底淘汰等方面的任何反馈。 这里的许多信息并非特定于Java。 但是为了避免不断调用通用和抽象术语我将在可能的地方用Hotspot JVM的具体术语进行发言。 为什么有人要关心垃圾收集器 这是一个好问题。 完美的垃圾收集器可以在没有人注意到它存在的情况下完成其工作。 不幸的是没有已知的完美的垃圾回收算法。 此外实际上对于大多数人可用的垃圾收集器的选择还限于实际上实施的垃圾收集算法的子集。 类似地 malloc也不是完美的并且存在其问题有多种实现方式具有不同的特性。但是尽管这是一个有趣的话题但是本文并未尝试对比自动和显式内存管理。 现实情况是与许多技术问题一样需要权衡取舍。 根据经验如果您使用的是可免费使用的基于Hotspot的JVMsOracle / SunOpenJDK 那么您最关心的就是垃圾回收器如果您担心延迟 。 如果您不这样做那么垃圾回收器将不会很麻烦–除了可能选择与默认值不同的最大堆大小之外。 所谓等待时间是指垃圾收集的暂停时间 。 垃圾收集器有时需要暂停应用程序才能完成其某些工作。 这通常被称为停止这世界的停顿“世界”是从Java应用程序的GC说话的角度或突变可观测宇宙因为它是变异堆而垃圾收集器试图收集重要的是要注意尽管所有实际可用的垃圾收集器都在应用程序上施加了世界暂停但这些暂停的频率和持续时间随垃圾收集器垃圾收集器设置和应用程序行为的选择而变化很大。 就像我们将看到的那样存在垃圾收集算法这些算法试图避免需要在停顿世界的暂停中收集整个堆。 这是一个重要属性的原因是如果在任何时候即使很少停止应用程序以完全收集堆则应用程序所遭受的暂停时间将与堆大小成正比 。 通常这是您在关心延迟时要避免的主要事情。 也有其他问题但这通常是一个大问题。 跟踪与参考计数 您可能听说过正在使用引用计数 例如cPython在大多数垃圾收集工作中都使用了引用计数方案。 我不会谈论太多因为它与JVMs无关只说两件事 计数垃圾回收的引用具有的一个属性是将在删除最后一个引用时立即知道该对象是不可访问的。 引用计数将不会检测为不可访问的循环数据结构并且还有其他一些问题使其无法成为所有垃圾收集的最终选择。 JVM而是使用所谓的跟踪垃圾收集器。 之所以称为跟踪是因为至少在抽象级别上识别垃圾的过程涉及获取根集 例如堆栈上的局部变量或全局变量之类的东西并跟踪从那些对象到直接或间接所有对象的路径。从所述根集合可以间接到达。 一旦确定了所有可到达的活动的对象就可以通过消除过程来标识符合垃圾收集器释放条件的对象。 基本停止标记扫动恢复 一个非常简单的跟踪垃圾收集器使用以下过程工作 完全暂停应用程序。 通过跟踪对象图即递归地遵循引用标记所有可到达的对象从根集开始参见上文。 释放所有无法访问的对象。 恢复应用程序。 在单线程环境中这很容易想象负责分配新对象的调用将立即返回新对象或者如果堆已满则启动上述过程以释放空间然后执行通过完成分配并返回对象。 没有一个JVM垃圾收集器像这样工作。 但是最好理解垃圾收集器的这种基本形式因为可用的垃圾收集器实质上是上述过程的优化。 JVM不实现这种垃圾回收的两个主要原因是 每个垃圾回收暂停将足以收集整个堆。 换句话说它的延迟很差。 对于几乎所有现实应用程序而言它都不是执行垃圾回收的最有效方法它具有很高的CPU开销。 压缩与非压缩垃圾回收 垃圾收集器之间的一个重要区别是它们是否正在压缩 。 压缩是指将对象移动在内存中以将其收集在一个密集的内存区域中而不是稀疏地散布在较大的区域中。 真实世界的类比考虑一个随机空间中地板上满是东西的房间。 拿走所有这些东西并将其紧紧塞在角落里实际上就是将它们压实。 释放空间。 记住什么是压实的另一种方法是设想其中的一台机器会像汽车一样将其压实成一块金属从而消除了空气所占的全部空间从而比原来的汽车占用更少的空间但是有人指出虽然汽车ID遭到破坏但堆上的物体却没有。 相比之下非紧凑型收集器从不移动对象。 将对象分配到内存中的特定位置后该对象将一直保留在那里或直到释放为止。 两者都有一些有趣的属性 执行压缩收集的成本是堆上实时数据量的函数。 如果只有1的数据处于活动状态则仅需要压缩1的数据复制到内存中。 相比之下在非紧凑型收集器中不再可访问的对象仍然意味着记账因为它们的存储位置必须保持释放状态以便将来分配使用。 在压缩收集器中分配通常是通过“ 碰到指针”方法来完成的。 您有一些空间区域并保持当前的分配指针。 如果您分配一个n字节的对象则只需将该指针加n我就避免了诸如多线程和暗示的优化之类的复杂性。 在一个非压实集电极分配涉及找到其中使用一些机构其依赖于用于跟踪的空闲存储器的可用性的确切机制来分配。 为了满足n字节的分配必须找到n字节可用空间的连续区域。 如果找不到一个因为堆是碎片化的 这意味着它由可用空间和分配的空间混合在一起分配将失败。 现实类比再次考虑您的房间。 假设您是一个压缩收集器。 您可以在闲暇时随意在地板上移动东西。 当您需要为地板中间的那个大沙发腾出空间时可以四处移动其他东西以腾出适当大小的沙发空间。 另一方面如果您是一个不紧凑的收藏家那么地板上的所有东西都会被钉牢并且无法移动。 尽管您有足够的可用地板空间但大沙发可能不适合放置–只有一个单独的空间不足以容纳沙发。 分代垃圾收集 大多数现实世界中的应用程序倾向于对短期对象即已分配的对象短暂使用的对象然后不再引用执行大量分配。 分代垃圾收集器尝试利用此观察结果以提高CPU效率换句话说具有更高的吞吐量 。 更正式地说大多数应用程序具有此行为的假设被称为弱代假设 。 之所以称其为“世代”是因为对象分为几代 。 收集器之间的细节会有所不同但此时的合理近似值是将对象分为两代 年轻的一代是最初分配对象的地方。 换句话说所有物体都始于年轻一代。 老一辈是反对“花钱”的对象因为他们在年轻一代中度过了一段时间。 代收集者通常更高效的原因是他们与老一代分开收集年轻一代。 处于稳定状态下进行分配的应用程序的典型行为是在收集年轻代时经常出现短暂的停顿–不经常出现但在老一代填满并触发整个堆旧的和新的的完整收集时会出现较长的停顿。 如果查看典型应用程序的堆使用情况图它将类似于以下内容 吞吐量收集器使用堆的典型锯齿行为 锯齿状外观的出现是年轻一代垃圾收集的结果。 接近尾声的时候是老一代人变满了而JVM对整个堆进行了完整的收集。 该下降结束时的堆使用量是该时间点实际活动集的合理近似值。 注意这是针对配置为使用默认JVM吞吐量收集器的Cassandra实例运行压力测试的图它不反映Cassandra的即开即用行为。 请注意仅在该图上的任意时间点选择“当前堆使用情况” 都不会使您了解应用程序的内存使用情况 。 我不能足够强调这一点。 通常认为内存“使用”是活动集 而不是任何特定时间的堆使用情况。 堆的使用更多取决于垃圾收集器的实现细节。 应用程序的内存使用量对堆使用量的唯一影响是它为堆使用量提供了一个下限 。 现在回到为什么代收集者通常更高效的原因。 假设我们的假设应用是所有物体中有90 早逝 换句话说它们永远无法生存到足以被提升为老一代的程度。 此外假设我们的年轻一代集合实际上是紧凑的请参阅前面的部分。 现在收集年轻一代的成本大约是跟踪和复制它所包含的对象的10的成本。 剩下的90的成本很小。 年轻一代的收藏会在充满时发生并且是世界停下来的停顿。 幸存的对象的10可能会立即升级为老一代或者它们可能在年轻一代中再生存一轮或两轮取决于各种因素。 但是要理解的重要总体行为是对象从年轻一代开始并由于在年轻一代中生存而提升为老一代。 精明的读者可能已经注意到不可能完全分开收集年轻一代–如果旧一代中的对象引用了新一代中的对象该怎么办这确实是垃圾收集器必须处理的事情以后的文章会谈论这个。 优化过程很大程度上取决于年轻一代的规模 。 如果大小太大则可能太大以至于与收集它相关的暂停时间是一个明显的问题。 如果大小太小则可能甚至死得很年轻的物体也不会足够快地死去 以至于它们死后仍处于年轻一代中。 回想一下年轻的一代是在变得饱满时收集的 这意味着它越小收集它的频率就越高。 进一步回想一下当对象在年轻一代中幸存下来时它们将被提升为老一代。 如果大多数对象尽管早逝都不会因为其太小而在年轻一代中死亡-他们将被提升到老一代而代际垃圾收集器试图进行的优化将失败而您将承担以后在老一代中收集对象的全部费用加上从年轻一代中复制对象的前期费用。 平行收集 拥有分代收集器的目的是为了优化吞吐量 ; 换句话说应用程序在特定时间内完成的工作总量。 副作用是由于垃圾收集而引起的大多数暂停也会变得更短。 但是没有尝试消除周期性的完整收集这意味着完成完整收集所需的暂停时间。 为了减轻这种情况吞吐量收集器做了一件值得一提的事情它是并行的 这意味着它同时使用多个CPU内核来加速垃圾收集。 确实可以缩短停顿时间但是您可以走多远还是有一个限制–即使在线性加速的不现实完美情况下意味着双CPU计数-收集时间的一半您也会受到数量的限制系统上的CPU内核数。 如果要收集30 GB的堆即使使用16个并行线程也将花费大量时间。 用垃圾回收的话来说并行一词用于表示同时在多个CPU内核上工作的收集器。 增量收集 垃圾回收上下文中的增量是指将需要完成的工作分成较小的块通常目的是将应用程序暂停多个短暂的时间而不是一个长时间的暂停。 从年轻的一代收集器构成增量功的意义上讲上述一代收集器的行为是部分增量的。 但是从总体上看收集过程不是增量的因为在旧的一代变满时会发生全部堆收集。 其他形式的增量收集也是可能的 例如对于应用程序执行的每个分配收集器可以执行少量的垃圾收集工作。 该概念与特定的实施策略无关。 并发收集 垃圾回收上下文中的并发是指与应用程序变异器 同时执行垃圾回收工作。 例如在8核系统上垃圾收集器可能保留两个后台线程这些线程在应用程序运行时执行垃圾收集工作。 这允许完成大量工作而不会导致应用程序暂停通常会以一定的吞吐量和实现复杂性为代价对于垃圾收集器实现者。 可用的热点垃圾收集器 Hotspot中垃圾收集器的默认选择是吞吐量收集器它是一个世代的并行压缩收集器。 完全针对吞吐量进行了优化 在给定时间段内应用程序完成的工作总量。 CMS收集器是解决延迟/暂停时间问题的传统替代方法。 CMS代表并发标记和扫描 是指收集器使用的机制。 收集器的目的是最大程度地减少甚至消除长时间的停顿将垃圾回收工作限制为较短的停顿通常是并行停顿并与应用程序同时执行更长的工作相结合。 CMS收集器的一个重要属性是它不紧凑因此存在碎片问题有关详细信息请参阅后面的博客文章。 在JDK 1.6和JDK 1.7的更高版本中有一个新的垃圾收集器称为G1 代表Garbage First 。 像CMS收集器一样其目的是尝试减轻或消除长时间停顿世界停顿的需求并且它的大部分工作都是在短暂的停顿世界渐进停顿的同时进行的其中一些工作也在完成中与应用程序同时进行。 与CMS相反G1 是紧凑的收集器并且没有碎片问题的困扰-而是有其他折衷的选择同样在以后的博客文章中将对此进行更多讨论。 观察垃圾收集器行为 我鼓励读者尝试使用垃圾收集器的行为。 使用jconsole与JDK一起提供或VisualVM 在本文较早的时候生成了该图来可视化正在运行的JVM上的行为。 但是尤其要开始运行JVM以开始熟悉垃圾收集日志的输出已更新jbellis的反馈–谢谢 -XX:PrintGC -XX:PrintGCDetails -XX:PrintGCDateStamps -XX:PrintGCApplicationStoppedTime -XX:PrintPromotionFailure 也有用但冗长含义在以后的文章中解释 -XX:PrintHeapAtGC -XX:PrintTenuringDistribution -XX:PrintFLSStatistics1 对于吞吐量收集器输出非常容易读取。 对于CMS和G1在没有介绍的情况下输出对于分析而言更加不透明。 我希望在以后的更新中对此进行介绍。 同时得出的结论是每当怀疑与GC相关的问题时上面的那些选项可能就是您要使用的第一件事。 当人们开始假设GC问题时这几乎总是我告诉人们的第一件事。 您是否看过GC日志 如果您还没有那可能是在浪费时间猜测GC。 结论 我试图制作一个速成课程介绍希望对我有启发性但主要是作为后续文章的背景。 我欢迎任何反馈尤其是在情况不清楚或我做出太多假设的情况下。 正如我一开始所说的那样我希望这个系列能够被广泛的读者所接受尽管我当然确实具有一定的专业水平。 但是不需要垃圾收集方面的知识。 如果是我已经失败了-请让我知道。 参考 实用垃圾收集第1部分–我们的JCG合作伙伴 Peter Schuller在modworldscode博客上的介绍 翻译自: https://www.javacodegeeks.com/2012/01/practical-garbage-collection-part-1.html