【问题标题】:Garbage collection notification?垃圾收集通知?
【发布时间】:2011-01-04 16:39:07
【问题描述】:

我想向 JVM 注册一个回调,以便知道垃圾收集何时发生。有没有办法做到这一点?

编辑:我想这样做,以便在我的应用程序日志中发生垃圾收集时注销,这样我就可以查看它是否与我看到的问题相关。开启 -Xloggc 很有帮助,但是将 GC 日志中的时间(使用应用启动后的秒数)整合到我的主应用程序日志中有点棘手。

2012 年 4 月编辑:从 Java7u4 开始,您可以从 GarbageCollectorMXBean(一个不错的 example)获取通知。

【问题讨论】:

  • 理想情况下,开发人员不必关心 GC 何时发生。您可以在 JVM 参数中进行一些设置。只是出于好奇,你想做什么?也许有更好的活动可以注册。
  • @Jay,我们不是生活在一个理想的世界里。如果您真的关心您的服务是否正常工作,您可能希望在垃圾收集开始频繁发生时发出警报,因为这可能是问题的征兆。
  • 好问题。如果 GC 通知可以与负载均衡器集成,以避免在 GC 期间向实例发送流量,那就太好了。
  • @AlexR 或the other way round:周期性地选择一个服务器,让它不再有请求发送给它,让它完成它正在运行的请求,让它做它的GC,然后重新激活服务器。

标签: java garbage-collection


【解决方案1】:

【讨论】:

  • 值得一提的是,观察 MemoryPoolMXBean 并查看内存使用量何时下降通常会更快。
  • 虽然 MemoryPoolMXBean 快得多,但您根本不知道 GC 何时运行。如果了解诸如应用程序的最大内存使用量之类的信息对您很重要,那么您将需要在垃圾收集之前和之后立即检查内存使用情况。如果您进行民意调查,您将无法确定。只是我的 0.02 美元 :)
【解决方案2】:

我认为标准方法是使用JVM Tool Interface (JVM TI) 编写一个带有 GC 启动回调的代理并从中记录时间(请参阅GetTime)。请注意,Garbage Collection Start event 仅针对完整 GC 发送。

在 JDK 5.0 或 JDK 6 下载的演示目录中提供了示例 JVM TI 代理。技术文章The JVM Tool Interface (JVM TI): How VM Agents Work 是另一个非常好的资源。也看看Creating a Debugging and Profiling Agent with JVMTI

【讨论】:

  • 好建议;我没有听说过 JVM TI。但是,我们的应用程序几乎从不执行完整的 gc,所以我想了解第 1 代集合。您如何看待 MemoryPoolMXBean 的想法?
【解决方案3】:

Java 代码示例使用接受的答案中提到的GarbageCollectorMXBean

static
{
    // notification listener. is notified whenever a gc finishes.
    NotificationListener notificationListener = new NotificationListener()
    {
        @Override
        public void handleNotification(Notification notification,Object handback)
        {
            if (notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION))
            {
                // extract garbage collection information from notification.
                GarbageCollectionNotificationInfo gcInfo = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData());

                // access garbage collection information...
            }
        }
    };

    // register our listener with all gc beans
    for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans())
    {
        NotificationEmitter emitter = (NotificationEmitter) gcBean;
        emitter.addNotificationListener(notificationListener,null,null);
    }
}

site that has detailed sample code that uses the GarbageCollectorMXBean.

【讨论】:

    【解决方案4】:

    看起来您可以使用 MemoryPoolMXBean 并将集合使用阈值设置为 1。这应该会在 gc 运行并且仍有至少一个字节的内存在使用时向您发出通知。

    http://java.sun.com/j2se/1.5.0/docs/api/java/lang/management/MemoryPoolMXBean.html

    不过,这似乎不适用于所有垃圾收集器。

    【讨论】:

      【解决方案5】:

      当接收到垃圾收集的 JVMTI 事件时,JVM 在技术上已停止,因此它无法通过 JNI 回调 Java 侦听器...。此代理以比详细 GC 更高的分辨率打印出 GC 开始和结束的时间在 Sun JVM 上。

      #include <stdlib.h>
      #include <time.h>
      #include <sys/time.h>
      #include "jvmti.h"
      
      void printGCTime(const char* type) {
      
        struct timeval tv;
        gettimeofday(&tv, NULL);
      
        struct tm localTime;
        localtime_r(&tv.tv_sec, &localTime);
      
        char *startTime = calloc(1, 128);
      
        strftime(startTime, (size_t) 128, "%a %b %d %Y %H:%M:%S", &localTime);
      
        fprintf(stderr, "GC %s: %s.%06d\n", type, startTime, (int)tv.tv_usec );
        fflush(stderr);
      
        if(startTime) free(startTime);
      
      }
      
      void JNICALL
      garbageCollectionStart(jvmtiEnv *jvmti_env) {
      
        printGCTime("Start ");
      
      }
      
      void JNICALL
      garbageCollectionFinish(jvmtiEnv *jvmti_env) {
      
        printGCTime("Finish");
      
      }
      
      
      JNIEXPORT jint JNICALL
      Agent_OnLoad(JavaVM * jvm, char *options, void *reserved)
      {
        jvmtiEnv *jvmti_env;
      
        jint returnCode = (*jvm)->GetEnv(jvm, (void **) &jvmti_env,
            JVMTI_VERSION_1_0);
      
      
      
        if (returnCode != JNI_OK)
          {
            fprintf(stderr,
                "The version of JVMTI requested (1.0) is not supported by this JVM.\n");
            return JVMTI_ERROR_UNSUPPORTED_VERSION;
          }
      
      
        jvmtiCapabilities *requiredCapabilities;
      
        requiredCapabilities = (jvmtiCapabilities*) calloc(1, sizeof(jvmtiCapabilities));
        if (!requiredCapabilities)
            {
              fprintf(stderr, "Unable to allocate memory\n");
              return JVMTI_ERROR_OUT_OF_MEMORY;
            }
      
        requiredCapabilities->can_generate_garbage_collection_events = 1;
      
        if (returnCode != JNI_OK)
          {
            fprintf(stderr, "C:\tJVM does not have the required capabilities (%d)\n",
                returnCode);
            exit(-1);
          }
      
      
      
        returnCode = (*jvmti_env)->AddCapabilities(jvmti_env, requiredCapabilities);
      
      
        jvmtiEventCallbacks *eventCallbacks;
      
        eventCallbacks = calloc(1, sizeof(jvmtiEventCallbacks));
        if (!eventCallbacks)
          {
            fprintf(stderr, "Unable to allocate memory\n");
            return JVMTI_ERROR_OUT_OF_MEMORY;
          }
      
        eventCallbacks->GarbageCollectionStart = &garbageCollectionStart;
        eventCallbacks->GarbageCollectionFinish = &garbageCollectionFinish;
      
      
        returnCode = (*jvmti_env)->SetEventCallbacks(jvmti_env,
            eventCallbacks, (jint) sizeof(*eventCallbacks));
      
      
        if (returnCode != JNI_OK)
          {
            fprintf(stderr, "C:\tError setting event callbacks (%d)\n",
                returnCode);
            exit(-1);
          }
      
        returnCode = (*jvmti_env)->SetEventNotificationMode(
            jvmti_env, JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_START, (jthread) NULL);
      
        if (returnCode != JNI_OK)
          {
            fprintf(
                stderr,
                "C:\tJVM does not have the required capabilities, JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_START (%d)\n",
                returnCode);
            exit(-1);
          }
      
      
        returnCode = (*jvmti_env)->SetEventNotificationMode(
            jvmti_env, JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, (jthread) NULL);
      
        if (returnCode != JNI_OK)
          {
            fprintf(
                stderr,
                "C:\tJVM does not have the required capabilities, JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH (%d)\n",
                returnCode);
            exit(-1);
          }
      
      
        if(requiredCapabilities) free(requiredCapabilities);
        if(eventCallbacks) free(eventCallbacks);
      
        return JVMTI_ERROR_NONE;
      }
      

      【讨论】:

        【解决方案6】:

        获取即将发生的 GC 通知的另一个用例:如果您的应用程序是负载平衡的,那么您可以在 GC 即将开始时通知负载平衡器将您的节点从池中取出,因为它没有收到会必须等待一个完整的 GC 来处理。

        这无助于在 GC 启动之前到达的正在进行的请求,但至少在我的情况下,大多数请求是亚秒级的,主要 GC 是 5-10 秒,每隔几分钟。我们可以调整 NewGen 比率等,但一般点仍然适用:主要 GC 可能比典型的响应时间长得多,因此您可能希望预先阻止启动主要 GC 的节点接收请求。 p>

        当 GC 结束时,JVM 中的一个线程可以向负载均衡器发送通知,让它知道它重新开始工作,或者 LB 可以依赖其通常的 keepalive。

        【讨论】:

          【解决方案7】:

          我知道这已经很晚了,但我希望有一天它会对某人有所帮助。

          您可以使用我正在开发的名为gcRadar 的库来接收此类事件。它提供有关对象被垃圾回收的确切时间的信息。

          欢迎对图书馆提出任何改进建议。

          【讨论】:

            【解决方案8】:

            有一个interesting article on Javalobby 讨论这样做的一种方法。

            【讨论】:

            • 有趣的方法,但它会引入一些开销,并且仅在收集特定对象时通知您。我想要 GC 发生时的通知,其频率与 -Xloggc 命令记录到文件的频率相同。
            【解决方案9】:

            您自己的程序没有标准方法可以从 JVM 获取有关垃圾收集的信息。任何此类 API 都是特定于供应商的。

            为什么您发现的设施不足?

            【讨论】:

            • 如果我没记错的话,JVM TI 是 Java 5+ 的标准。你想要一个程序来听它自己吗?
            • 我找不到最终链接,但 JVM TI 似乎已由大多数 JVM 实现:Sun、IBM (publib.boulder.ibm.com/infocenter/javasdk/v6r0/index.jsp?topic=/…)、Oracle/BEA、Harmony(可能还有更多)。跨度>
            • 哦,我明白你的意思了。但是代理可以是你自己的程序:)
            • 代理不知道垃圾回收。让我建议您实际上尝试使用 JVM TI 让程序本身监听 gc 的建议。你可能会感到惊讶:)
            【解决方案10】:

            关于-Xloggc:从jdk1.6 更新4 开始,您可以使用-XX:+PrintGCDateStamps 让Sun/Oracle JVM 打印出日期和时间。这使得日志非常更加有用,特别是如果您添加了可以通知您任何 GC 问题的日志扫描器/监视器。

            【讨论】:

              【解决方案11】:

              如果您将其视为诊断工具,我建议您将应用程序日志重定向到 StdOut,然后将 StdOut 和 StdErr 都重定向到文件中。这将为您提供 JVM 日志记录的详细信息,而不会强制您更改应用程序代码。

              【讨论】:

              • 在我写这篇文章时,我开始怀疑 JVM 日志是否可以重定向到您的应用程序日志框架中。你用什么来做应用记录器?
              猜你喜欢
              • 1970-01-01
              • 2011-01-21
              • 1970-01-01
              • 1970-01-01
              • 2018-12-30
              • 1970-01-01
              • 1970-01-01
              • 2010-12-13
              • 2012-05-04
              相关资源
              最近更新 更多