垃圾收集通知?

40

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

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

编辑于2012年4月:自Java7u4以来,您可以从GarbageCollectorMXBean(一个不错的示例)获取通知。


理想情况下,开发人员不应该关心GC何时发生。您可以在JVM参数中进行一些设置。只是出于好奇,您想要做什么?也许有更好的事件可供注册。 - Jay
16
@Jay,我们并非生活在一个理想的世界。如果你真的关心你的服务能够正常运行,那么当垃圾回收发生得太频繁时,你可能需要发出警报,因为这可能是问题的信号。 - tster
1
不错的问题。如果GC通知可以与负载均衡器集成,以避免在GC期间向实例发送流量,那就太好了。 - Alex R
@AlexR 或者 反过来:定期选择一个服务器,不再向其发送更多请求,让它完成正在运行的请求,让它执行垃圾回收,并重新激活该服务器。 - maaartinus
11个回答

20

1
值得一提的是,通常观察MemoryPoolMXBean并查看内存使用情况何时下降会更快。 - Xorlev
虽然MemoryPoolMXBean更快,但你不知道GC何时运行。如果你想知道应用程序的最大内存使用情况之类的信息,那么你需要在垃圾回收之前和之后立即检查内存使用情况。如果你轮询,你就不能确定。这只是我的个人意见 :) - CodeBlind

10

好的建议,我之前没有听说过JVM TI。然而,我们的应用程序几乎从不进行完整的垃圾回收,所以我想了解一下关于第一代集合的情况。您对MemoryPoolMXBean的想法有什么看法? - Ted Graham

7

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);
    }
}

这个网站有详细的样例代码,使用了GarbageCollectorMXBean


3

2

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

#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;
}

2

我知道现在已经很晚了,但我希望这能对某个人有所帮助。

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

欢迎提出有关库的任何改进建议。


2
另一个获取即将发生GC通知的用例:如果您的应用程序是负载平衡的,则可以在GC即将开始时通知负载均衡器将您的节点从池中取出,以便它不会接收到需要等待完整GC处理的请求。但这无法帮助已经到达的正在进行中的请求,因为在GC开始之前它们就已经到达了。但至少在我的情况下,大多数请求都是亚秒级别的,而主要GC则是每隔几分钟5-10秒钟一次。我们可以调整NewGen比率等,但总体上仍然适用:主要GC可能比典型响应时间长得多,因此您可能希望预先阻止节点开始进行主要GC并接收请求。当GC结束时,JVM中的线程可以向负载均衡器发送通知,让它知道它已经恢复正常运行,或者LB可以依赖于其通常的保持活动状态。

1

在Javalobby上有一篇有趣的文章,讨论了一种实现这个功能的方法。


有趣的方法,但它会引入一些开销,并且只在特定对象被收集时通知您。我希望在GC发生时得到通知,与-Xloggc命令记录到文件的频率相同。 - Ted Graham

1

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

为什么您发现的工具不足呢?


如果我没记错的话,JVM TI是Java 5+的标准。你想要一个程序来监听自己吗? - Thorbjørn Ravn Andersen
我找不到最终链接,但是JVM TI似乎被大多数JVM实现:Sun、IBM(http://publib.boulder.ibm.com/infocenter/javasdk/v6r0/index.jsp?topic=/com.ibm.java.doc.diagnostics.60/diag/tools/jvmti.html)、Oracle/BEA、Harmony(可能还有其他)。 - Pascal Thivent
哦,我明白你的意思了。但是代理可以是你自己的程序 :) - Pascal Thivent
代理程序不知道垃圾回收。让我建议您实际尝试一下使用JVM TI在程序中监听gc的建议。也许您会感到惊讶 :) - Thorbjørn Ravn Andersen

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接