垃圾收集算法,垃圾收集器_垃圾收集简介

垃圾收集算法,垃圾收集器

垃圾收集是Java虚拟机的杀手级功能之一。 使开发人员不必担心像C ++这样的语言的内存管理,可以显着提高生产力。 但是,这种提升并非免费的:垃圾回收是有代价的。

当我们在程序中分配和取消分配对象时,垃圾收集器负责执行清理工作,以释放不再需要的对象占用的内存,以便将其用于新对象。 收集器执行此操作的方式取决于我们选择的收集算法。

关于垃圾收集的特性,有两个主要的考虑因素需要考虑:吞吐量和暂停。 吞吐量是未花费在垃圾收集上的总时间的百分比,而暂停是由于正在执行垃圾收集而导致应用程序显示为无响应的时间。

根据应用程序的类型,我们可能会更关心一个或另一个,但是在大多数实际应用程序中,停顿是常见的问题。 JVM在堆中使用的内存越多,运行垃圾回收(年轻一代或终身一代)中发生暂停的可能性就越大。

如今,大多数团队最终将其JVM内存使用量限制为4-8GB,这是由于否则可能会出现长时间的停顿。 因此,必须衡量我们的应用程序和垃圾收集的行为。 更具体地说,对于正在运行的实际生产应用程序具有此信息很重要。

为了衡量垃圾回收,有3种主要方法:

  • 分析JVM打印的垃圾收集日志;

  • 使用JMX读取公开的垃圾收集信息;

  • 使用jstat之类的工具访问JVM检测计数器。

我们将分别关注这些方法中的每一种,并了解如何使用它们来测量GC行为。

分析垃圾收集日志

您需要做的第一件事是向您的java调用添加参数以打印出垃圾收集信息:

  • -Xloggc:〜/ gc.log

  • -XX:+ PrintGC详细信息

  • -XX:+ PrintGCTimeStamps

  • -XX:+ PrintTenuringDistribution

输出示例(忽略权属分配):

0.634:[GC

[PSYoungGen:10304K-> 1660K(11968K)] 10304K-> 2656K(39296K),0.0069210秒] [时间:用户= 0.01 sys = 0.00,实际= 0.01秒]

这告诉我们,在年轻一代开始进行此过程后的0.634秒内,进行了一次次要收集。 收集之前,年轻一代中有10304K个对象,之后则有1660K个对象(总计11968K年轻一代),耗时约7毫秒。

下一个数字是指堆的总大小–总堆使用量为10304K(意味着我们之前只使用过年轻一代的堆),已减少到2656K(大于年轻一代中的1660K,这意味着我们提升了996K(旧一代)。 堆的总大小(年轻+旧gen)现在为39296K。

完整gc的示例如下所示:

4.268:[完整GC [PSYoungGen:1649K-> 0K(42880K)] [ParOldGen:19757K-> 18765K(44928K)] 21406K-> 18765K(87808K)[PSPermGen:23549K-> 23537K(47104K)],0.2047590秒] [时间:user = 0.31 sys = 0.00,真实= 0.20秒]

此输出的结构与以前相同,但是在这里我们可以看到堆的三个不同区域(年轻,老一代和永久一代)及其大小如何变化。

在调整JVM的生成大小和不同参数时,这些数据无疑将是有价值的,但对于我们监视生产中垃圾收集行为的目标也非常有用。

在生产中写入这些日志的影响可以忽略不计,因此解析它们并发送警报(例如,向团队发送电子邮件或通知公司的运营警报系统)是必要的。

一个简单的示例是监视此文件并在发生完全垃圾收集时发送电子邮件。 这是在Unix系统中可以实现的一种方法:

> tail -f gc.log | grep –行缓冲的“完整GC” | 边读边; 回显“ $ line” | mail -s“发生了完全垃圾收集”“ email@domain.com”; 做完了

这只会将各个旧的收集行发送到电子邮件。 这相当简单,但是不一定总能获得复杂而成熟的解决方案来获得所需的东西。

有一些开源项目可以解析垃圾收集日志,但是这些格式有两个问题:它们根据所使用的收集器而有所不同,并且可以随时间而变化(并且过去也是如此!)。 请注意,其中一些项目不再适用于当前的日志格式。

通过JMX阅读垃圾收集信息

读取有关垃圾回收信息的另一种方法是通过Java管理扩展(JMX)。

通过MBean公开的数据点很多,可以在进程内部,从同一台机器上的另一个进程或远程进程中读取。 对于我们的特定情况,我们有可以使用的垃圾收集bean(它们又取决于我们使用的垃圾收集算法):

图1:使用MBean公开垃圾回收bean。

在jconsole中,我们可以看到,除其他信息外,我们还有2个与GC相关的MBean:PS MarkSweep和PS Scavenge(在这种情况下,我们使用并发标记扫描作为我们的垃圾收集算法)。

但是,除了在jconsole或类似的管理GUI中简单地查看此数据之外,我们还可以做得多。 我们可以从代码中读取此信息,并根据需要处理发生的情况。

清单1显示了我们如何阅读它。

public void monitor(int vmpid) throws Exception {
JMXServiceURL url = new JMXServiceURL(getVMAddress(vmpid));
        JMXConnector connector = JMXConnectorFactory.connect(url);

        final MBeanServerConnection serverConnection = connector.getMBeanServerConnection();
        Set<ObjectName> beanSet = serverConnection.queryNames(new ObjectName("java.lang:type=GarbageCollector,name=PS MarkSweep"), null);

        final ObjectName bean = beanSet.iterator().next();

        GarbageCollectorMXBean gcBean = JMX.newMXBeanProxy(serverConnection,bean, GarbageCollectorMXBean.class);
        System.out.println("collection time: " + gcBean.getCollectionTime());
        System.out.println("collection count: " + gcBean.getCollectionCount());

        GcInfo gcInfo = gcBean.getLastGcInfo();
        Map<String, MemoryUsage> memUsages = gcInfo.getMemoryUsageBeforeGc();
        for (Entry<String, MemoryUsage> memUsage : memUsages.entrySet()) {
                System.out.println(memUsage.getKey() + ": " + memUsage.getValue());
        }

        listenToNotifications(serverConnection, bean);
}

private String getVMAddress(int pid) throws AttachNotSupportedException, IOException {
        String jmxAddressProp = "com.sun.management.jmxremote.localConnectorAddress";
        VirtualMachine vm = VirtualMachine.attach(String.valueOf(pid));
        return vm.getAgentProperties().getProperty(jmxAddressProp);
}

清单2显示了示例输出。

collection time: 82037
collection count: 116
PS Survivor Space: init = 1703936(1664K) used = 65536(64K) committed = 32047104(31296K) max = 32047104(31296K)
PS Eden Space: init = 10551296(10304K) used = 0(0K) committed = 69795840(68160K) max = 113049600(110400K)
PS Old Gen: init = 27983872(27328K) used = 239432344(233820K) committed = 357957632(349568K) max = 357957632(349568K)
Code Cache: init = 2555904(2496K) used = 19949568(19482K) committed = 20185088(19712K) max = 50331648(49152K)
PS Perm Gen: init = 21757952(21248K) used = 148450536(144971K) committed = 155058176(151424K) max = 268435456(262144K)

我们只是打印出不同空间的收集时间,计数和统计信息,就像查看垃圾收集日志时所看到的那样。 但是,在这种情况下,我们拥有丰富的数据结构,这些数据结构使我们无需解析文本即可使用此信息。

在这个简单的示例中,我们从本地计算机上运行的进程读取垃圾收集信息。 为了做到这一点,我们所需要的只是流程进程ID,该ID传递给monitor()方法。

您可以看到我们传递给queryNames()方法的标识符与我们在上一个jconsole屏幕截图中看到的树匹配:

“java.lang:type=GarbageCollector,name=PS MarkSweep”

我们这里可能正在阅读PS Scavenge收集器(针对年轻一代的收集器),您可能会更改此代码以能够读取您决定配置的任何垃圾收集器。

在最后一行,我们正在调用listenToNotifications方法。 该方法类似于清单3

private void listenToNotifications(final MBeanServerConnection serverConnection, final ObjectName bean) throws InstanceNotFoundException, IOException {
        final Queue<Notification> notifications = new LinkedBlockingQueue<Notification>();
        NotificationListener listener = new NotificationListener() {
                @Override
                public void handleNotification(Notification notification, Object ctx) {
                        notifications.add(notification);
                }
        };
        serverConnection.addNotificationListener(bean, listener, null, null);

        while (true) {
                Notification notification = notifications.poll();
                if (notification != null) {
                        process(notification);
                }
        }
}

这种方法使我们能够侦听特定垃圾收集器中的更改,即每次发生垃圾收集时都会收到通知。 处理方法未实现,在这种情况下,它将收集所有必要的信息并通过电子邮件发送,在内部警报系统中发布通知等。

访问JVM检测计数器

在生产中访问垃圾收集信息的最后一种有趣的方法是访问实际的JVM计数器。 当JVM运行时,它使用内部数据结构来保留所有有关垃圾收集的信息(以及更多信息)。 这是直接在C ++中完成的,我们无法直接从Java中访问它。

但是,JVM将这些数据结构存储在一个文件中,该文件通常保存在一个名为hsperfdata_ <user>的临时文件夹中(在Linux中为/ tmp),其中包含所有信息。 JVM将其写入并读取为内存映射文件,因此可以通过简单地读取文件来访问那些数据结构。 这种方法的优点是,它几乎不会影响正在运行的进程,因为该文件以只读模式打开,以确保我们不影响内存。 与JMX示例不同,此过程无需执行额外的工作,在JMX示例中,需要执行代码来访问所需的信息。

尽管实际上完成此任务并不像前面的示例那么简单,但是我们可以从现有工具中获得一些好的指导。 与JDK一起分发的一种有趣的工具是jstat。 实际上,是读取hsperfdata文件并在stdout上显示性能计数器。 还有另一个名为jstatd的类似工具,该守护程序允许远程进程(例如VisualVM)连接并查询数据。 但是,此通信的格式未公开,并且不能保证保持不变。

有两种利用这种访问方法的方法:使用jstat并像处理垃圾收集日志一样解析其输出,或者直接读取性能计数器数据结构。 第二个选项对于本文的范围而言过于复杂,但是jstat的使用非常简单。 我们可以这样运行它:

> jstat –gccause –t <pid> <millis_interval>

这将在每个millis_interval中获取垃圾收集信息,并进行打印以显示类似于清单4的内容。

Timestamp S0 S1 E O P YGC YGCT FGC FGCT GCT LGCC GCC

82214.9 9.04 0.00 78.21 68.87 96.27 968 20.731 116 82.039 102.769 Allocation Failure No GC

82215.9 9.04 0.00 78.21 68.87 96.27 968 20.731 116 82.039 102.769 Allocation Failure No GC

这为您提供了大多数基本计数器-幸存者空间0、1,伊甸园,旧的和永久的利用率,垃圾收集计数,垃圾收集所花费的时间以及垃圾收集的原因。 jstat的手册页很好地解释了每个计数器以及可以传递给其他信息的其他选项。

就像在垃圾收集日志中一样,我们可以以一种非常幼稚的方式解析此输出以发送电子邮件-但我将由您自己决定。

结论

有许多方法可以以不同的努力和优势来监视生产中的垃圾收集行为。 重要的部分是在生产之前就已对这种行为进行了测量,并且我们将这些监视解决方案保留在生产中以获取有关运行系统的重要信息。

作者简介: Ruben在Morgan Stanley担任软件工程师。 他是葡萄牙语Java用户组(PT.JUG)的创始人之一,并且在过去10年中一直致力于Java和其他语言的研究。 他在可伸缩性和低延迟系统的咨询,初创公司和银行业务方面做过一切。

本文先前发表在《 JAX杂志:齐心协力》中。 对于该问题和其他问题,请单击此处

图片由edenpictures提供


翻译自: https://jaxenter.com/an-introduction-to-garbage-collection-106154.html

垃圾收集算法,垃圾收集器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值