Java UDx 资源管理

当启动时,Java 虚拟机 (JVM) 会分配固定数量的内存。此固定内存分配机制会使 Java UDx 的内存管理复杂化,因为 UDx 在处理数据时无法动态分配和释放内存。与此不同的是,C++ UDx 可以动态分配资源。

为了控制 Java UDx 所占用的内存量,Vertica 包含一个名为 jvm 的内存池,它使用该池为 JVM 分配内存。如果该内存池已用完,则在该池中有足够内存可用于启动新的 JVM 之前,调用 Java UDx 的查询会阻塞。

默认情况下,jvm 池存在以下情况:

  • 由于没有分配给自己的内存,它会从 GENERAL 池借用内存。

  • 其 MAXMEMORYSIZE 设置为系统内存的 10% 或 2 GB(以较小者为准)。

  • 其 PLANNEDCONCURRENCY 设置为 AUTO,因此它会继承 GENERAL 池的 PLANNEDCONCURRENCY 设置。

您可以通过查询 RESOURCE_POOLS 表来查看 jvm 池的当前设置:

=> SELECT MAXMEMORYSIZE,PLANNEDCONCURRENCY FROM V_CATALOG.RESOURCE_POOLS WHERE NAME = 'jvm';
 MAXMEMORYSIZE | PLANNEDCONCURRENCY
---------------+--------------------
 10%           | AUTO

当 SQL 语句调用 Java UDx 时,Vertica 会检查 jvm 内存池是否具有足够内存可用于启动新的 JVM 实例以执行函数调用。在启动每个新的 JVM 时,Vertica 会将其堆内存大小设置为大约 jvm 池的 MAXMEMORYSIZE 参数除以其 PLANNEDCONCURRENCY 参数。如果内存池不包含足够内存,则在另一个 JVM 退出并将其内存返回到池中之前,查询会阻塞。

如果 Java UDx 尝试使用的内存多于已分配给 JVM 堆大小的内存,它会退出并显示错误。您可以尝试通过以下方法解决此问题:

  • 增加 jvm 池的 MAXMEMORYSIZE 参数。

  • 减少 jvm 池的 PLANNEDCONCURRENCY 参数。

  • 更改 Java UDx 的代码以使用更少内存。

调整 jvm 池

根据需求调整 jvm 池,您必须考虑以下两个因素:

  • Java UDx 运行所需的 RAM 容量

  • 您希望数据库运行多少个并发 Java UDx 函数

您可以使用几种方法来了解 Java UDx 所需的内存数量。例如,您的代码可以使用 Java 的 Runtime 类来获取已分配的总内存的估算值并使用 ServerInterface.log() 记录该值。(此类的一个实例将传递给您的 UDx。)如果数据库中有多个 Java UDx,请基于使用最多内存的 UDx 设置 jvm 池的内存大小。

需要运行 Java UDx 的并发会话数不能与全局 PLANNEDCONCURRENCY 设置相同。例如,您可能只有一个用户运行 Java UDx,这意味着您可以将 jvm 池的 PLANNEDCONCURRENCY 设置减少至 1。

在获取 RAM 数量的估算值和需要运行 Java UDX 的并发用户会话数之后,您可以将 jvm 池调整为适当大小。将该池的 MAXMEMORYSIZE 设置为需要最多资源的 Java UDx 所需的最大 RAM 容量乘以运行 Java UDx 所需的并发用户会话数。将该池的 PLANNEDCONCURENCY 设置为运行 Java Udx 所需的并发用户会话数。

例如,假设您的 Java UDx 最多需要 4 GB 内存才能运行,并且您希望最多有两个用户会话使用 Java UDx。您应使用以下命令调整 jvm 池:

=> ALTER RESOURCE POOL jvm MAXMEMORYSIZE '8G' PLANNEDCONCURRENCY 2;

MEMORYSIZE 已设置为 8 GB,即 Java UDx 使用的最大内存 (4 GB) 乘以并发用户会话数(2 个)。

有关对 jvm 和其他资源池进行优化的详细信息,请参阅管理工作负载

释放 JVM 内存

当用户在其会话期间第一次调用 Java UDx 时,Vertica 会分配 jvm 池中的内存并启动新的 JVM。只要用户会话处于打开状态,此 JVM 就会保持运行,以便可以处理其他 Java UDx 调用。让 JVM 保持运行可以减少由同一个会话执行多个 Java Udx 所产生的开销。如果 JVM 不保持处于打开状态,则对 Java UDx 的每次调用都会导致 Vertica 需要耗费更多时间来分配资源和启动新的 JVM。但是,让 JVM 保持处于打开状态意味着无论是否会再次使用 JVM 的内存,该内存在会话的有效期内都会保持处于已分配状态。

如果减小 jvm 内存池,则包含 Java UDx 的查询会在内存变为可用之前阻塞,或者最终会由于缺少资源而失败。如果发现查询由于此原因而阻塞或失败,您可以向 jvm 池分配更多内存并增加其 PLANNEDCONCURRENCY。另一个选项是要求用户在他们不再需要运行 Java UDx 时调用 RELEASE_JVM_MEMORY 函数。此函数可关闭属于该用户会话的任何 JVM 并将其已分配的内存恢复到 jvm 内存池中。

以下示例演示了查询 V_MONITOR.SESSIONS 以确定所有会话已分配给 JVM 内存。此示例还演示了如何通过调用 Java UDx 来分配内存以及如何通过调用 RELEASE_JVM_MEMORY 来释放内存。

=> SELECT USER_NAME,EXTERNAL_MEMORY_KB FROM V_MONITOR.SESSIONS;
 user_name | external_memory_kb
-----------+---------------
 dbadmin   |             0
(1 row)

=> -- Call a Java UDx
=> SELECT add2ints(123,456);
 add2ints
----------
      579
(1 row)
=> -- JVM is now running and memory is allocated to it.
=> SELECT USER_NAME,EXTERNAL_MEMORY_KB FROM V_MONITOR.SESSIONS;
 USER_NAME | EXTERNAL_MEMORY_KB
-----------+---------------
 dbadmin   |         79705
(1 row)

=> -- Shut down the JVM and deallocate memory
=> SELECT RELEASE_JVM_MEMORY();
           RELEASE_JVM_MEMORY
-----------------------------------------
 Java process killed and memory released
(1 row)

=> SELECT USER_NAME,EXTERNAL_MEMORY_KB FROM V_MONITOR.SESSIONS;
 USER_NAME | EXTERNAL_MEMORY_KB
-----------+---------------
 dbadmin   |             0
(1 row)

在极少数情况下,您可能需要关闭所有 JVM。例如,您可能需要为重要查询释放内存,或者 Java UDx 的多个实例可能需要太长时间才能完成。您可以使用 RELEASE_ALL_JVM_MEMORY 关闭所有用户会话中的所有 JVM:

=> SELECT USER_NAME,EXTERNAL_MEMORY_KB FROM V_MONITOR.SESSIONS;
  USER_NAME  | EXTERNAL_MEMORY_KB
-------------+---------------
 ExampleUser |         79705
 dbadmin     |         79705
(2 rows)

=> SELECT RELEASE_ALL_JVM_MEMORY();
                           RELEASE_ALL_JVM_MEMORY
-----------------------------------------------------------------------------
 Close all JVM sessions command sent. Check v_monitor.sessions for progress.
(1 row)

=> SELECT USER_NAME,EXTERNAL_MEMORY_KB FROM V_MONITOR.SESSIONS;
 USER_NAME | EXTERNAL_MEMORY_KB
-----------+---------------
 dbadmin   |             0
(1 row)

注意

  • jvm 资源池仅用于为语句中的 Java UDx 函数分配内存。SQL 语句所需的其余资源来自其他内存池。

  • 第一次调用 Java UDx 时,Vertica 会启动 JVM 以执行某些 Java 方法,从而在查询规划阶段获取有关 UDx 的元数据。此 JVM 的内存也是从 jvm 内存池获取的。