87

有什么好方法可以在运行时让 JVM 可用的剩余内存?这样做的用例是让 Web 服务在接近其内存限制时优雅地失败,通过拒绝新连接并显示一个很好的错误消息“太多人使用这个,稍后再试”,而不是因为 OutOfMemory 错误而突然死亡.

请注意,这与事先计算/估计每个对象的成本无关。原则上,我可以估计我的对象占用了多少内存并根据该估计拒绝新的连接,但这似乎有点老套/脆弱。

4

9 回答 9

107

William Brendel 的这个样本可能有一些用处。

编辑:我最初提供了这个示例(链接到 William Brendel 对另一个主题的回答)。该主题的创建者 (Steve M) 想要创建一个多平台 Java 应用程序。具体来说,用户试图找到一种方法来评估正在运行的机器的资源(磁盘空间、CPU 和内存使用情况)。

这是该主题中给出的答案的内嵌文字记录。但是,有人指出,尽管我的回答被标记为已接受,但这并不是理想的解决方案。

public class Main {
  public static void main(String[] args) {
  /* Total number of processors or cores available to the JVM */
  System.out.println("Available processors (cores): " + 
  Runtime.getRuntime().availableProcessors());

  /* Total amount of free memory available to the JVM */
  System.out.println("Free memory (bytes): " + 
  Runtime.getRuntime().freeMemory());

  /* This will return Long.MAX_VALUE if there is no preset limit */
  long maxMemory = Runtime.getRuntime().maxMemory();
  /* Maximum amount of memory the JVM will attempt to use */
  System.out.println("Maximum memory (bytes): " + 
  (maxMemory == Long.MAX_VALUE ? "no limit" : maxMemory));

  /* Total memory currently in use by the JVM */
  System.out.println("Total memory (bytes): " + 
  Runtime.getRuntime().totalMemory());

  /* Get a list of all filesystem roots on this system */
  File[] roots = File.listRoots();

  /* For each filesystem root, print some info */
  for (File root : roots) {
    System.out.println("File system root: " + root.getAbsolutePath());
    System.out.println("Total space (bytes): " + root.getTotalSpace());
    System.out.println("Free space (bytes): " + root.getFreeSpace());
    System.out.println("Usable space (bytes): " + root.getUsableSpace());
  }
 }
}

用户 Christian Fries 指出,假设它Runtime.getRuntime().freeMemory()为您提供了在发生内存不足错误之前可能分配的内存量是错误的。

文档中,签名返回Runtime.getRuntime().freeMemory()如下:

返回:当前可用于未来分配的对象的内存总量的近似值,以字节为单位。

但是,用户 Christian Fries 声称此功能可能会被误解。他声称,在发生内存不足错误(可用内存)之前可能分配的大致内存量可能由下式给出:

long presumableFreeMemory = Runtime.getRuntime().maxMemory() - allocatedMemory;

allocatedMemory给予:

long allocatedMemory = 
  (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory());

这里的关键是空闲内存的概念之间的差异。一件事是操作系统为 Java 虚拟机提供的内存。另一个是由 Java 虚拟机本身实际使用的内存块组成的总字节数。

考虑到分配给 Java 应用程序的内存是由 Java 虚拟机按块管理的,Java 虚拟机可用的可用内存量可能与 Java 应用程序可用的内存量不完全匹配。

具体来说,Christian Fries 表示使用-mxor-Xmx标志来设置 Java 虚拟机可用的最大内存量。他指出以下功能差异:

/* Returns the maximum amount of memory available to 
   the Java Virtual Machine set by the '-mx' or '-Xmx' flags. */
Runtime.getRuntime().maxMemory();

/* Returns the total memory allocated from the system 
   (which can at most reach the maximum memory value 
   returned by the previous function). */
Runtime.getRuntime().totalMemory();

/* Returns the free memory *within* the total memory 
   returned by the previous function. */
Runtime.getRuntime().freeMemory();

克里斯蒂安以他的回答结束他的陈述,Runtime.getRuntime().freeMemory()事实上返回了可以称为假定的自由记忆的东西。即使未来的内存分配没有超过该函数返回的值,如果 Java 虚拟机还没有收到主机系统分配的实际内存块,ajava.lang.OutOfMemoryError仍然可能产生。

最后,正确使用的方法将在不同程度上依赖于您的应用程序的具体情况。

我提供了另一个可能有用的链接。这是用户 Richard Dormand 提出的一个问题,stones333 回答了关于确定使用的默认 Java 堆大小的问题。

于 2012-10-09T20:22:45.263 回答
76

注意:到目前为止,所有答案,即使是被接受的答案,似乎都通过说Runtime.getRuntime().freeMemory()给你在发生内存不足错误之前可能分配的内存量来回答这个问题。然而:这是错误的。

在发生内存不足错误之前可能分配的大约内存量,即空闲内存可能

long presumableFreeMemory = Runtime.getRuntime().maxMemory() - allocatedMemory;

在哪里

long allocatedMemory      = (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory());

说明: 如果通过 -mx 参数(或 -Xmx)启动 JVM,则指定 JVM 可用的最大数量。Runtime.getRuntime().maxMemory()会给你这个数额。JVM 将从这个系统内存量中以块的形式分配内存,例如 64 mb 的块。开始时,JVM 只会从系统中分配这样一个块,而不是全部。Runtime.getRuntime().totalMemory()给出从系统分配的总内存,同时给出分配的总内存Runtime.getRuntime().freeMemory()的空闲内存。

因此:

long definitelyFreeMemory = Runtime.getRuntime().freeMemory();

是 JVM 已经保留的空闲内存,但它可能只是一小部分。你很可能会得到presumableFreeMemory. 当然,即使您尝试分配的数量小于presumableFreeMemory. 如果 JVM 没有从系统中获取下一个内存块,则可能会发生这种情况。但是,在大多数系统上,这永远不会发生,系统宁愿开始交换 - 您希望避免这种情况。回到原来的问题:如果 -mx 设置为一个合理的值,那么presumableFreeMemory它是空闲内存的一个很好的指标。

于 2013-08-21T19:43:09.207 回答
12

要获取操作系统范围内的可用内存,请OSHI使用以下 maven 依赖项添加:

<dependency>
    <groupId>com.github.oshi</groupId>
    <artifactId>oshi-core</artifactId>
    <version>LATEST</version>
</dependency>

然后在 Java 中,使用以下代码:

SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
long availableMemory = hal.getMemory().getAvailable();
于 2016-04-10T18:03:26.990 回答
8

除了使用 Runtime 方法之外,您还可以使用以下方法获取一些额外的内存信息

MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heap = memBean.getHeapMemoryUsage();
MemoryUsage nonheap = memBean.getNonHeapMemoryUsage();

每个MemoryUsage都提供了 init、used、committed 和 max 值。如果创建一个内存监视器线程来轮询内存并记录它,这可能很有用,为您提供一段时间内的内存使用历史记录。有时,查看导致错误的内存使用情况会很有帮助。

如果您真的想将其发挥到极致,请创建一个堆转储线程。随着时间的推移监控您的内存使用情况,当它超过某些阈值时执行以下操作(这适用于 JBoss 5.0 - 您的里程可能会有所不同):

// init code
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
HotSpotDiagnosticMXBean diagBean = ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class); 

// loop code
// add some code to figure if we have passed some threshold, then

File heapFile = new File(outputDir, "heap-" + curThreshold + ".hprof");
log.info("Dumping heap file " + heapFile.getAbsolutePath());
diagBean.dumpHeap(heapFile.getAbsolutePath(), true);

稍后您可以使用 eclipse内存分析器或类似工具查看这些堆转储文件,以检查内存泄漏等。

于 2012-10-09T23:11:03.547 回答
7

除了其他答案之外,我想指出这样做不一定是一个好主意,因为您的应用程序中可能有一个使用SoftReferences的缓存。

一旦 JVM 达到其内存限制,这样的缓存就会释放内存。分配内存,即使没有足够的空闲内存,也会首先导致内存被软引用释放,并使其可用于分配。

于 2012-10-09T20:25:22.713 回答
1

你可以随时打电话Runtime.getRuntime().freeMemory()

问题的另一半,获取对象的成本,对我来说似乎更成问题。

我认为更好的解决方案是弄清楚如何集群和扩展您的 Web 服务,以便它们可以优雅地接受 150% 的额定负载,而不会拒绝新的连接。听起来,规模练习会给你带来比代码破解更好的解决方案。

于 2012-10-09T20:21:27.760 回答
1

要获取物理机内存、已内存和可用内存信息,我们也可以通过 javaRuntime.exec()使用命令参数获取它free -m,然后将其解释如下:

Runtime runtime = Runtime.getRuntime();

BufferedReader br = new BufferedReader(
    new InputStreamReader(runtime.exec("free -m").getInputStream()));

String line;
String memLine = "";
int index = 0;
while ((line = br.readLine()) != null) {
  if (index == 1) {
    memLine = line;
  }
  index++;
}
//                  total        used        free      shared  buff/cache   available
//    Mem:          15933        3153        9683         310        3097       12148
//    Swap:          3814           0        3814

List<String> memInfoList = Arrays.asList(memLine.split("\\s+"));
int totalSystemMemory = Integer.parseInt(memInfoList.get(1));
int totalSystemUsedMemory = Integer.parseInt(memInfoList.get(2));
int totalSystemFreeMemory = Integer.parseInt(memInfoList.get(3));

System.out.println("Total system memory in mb: " + totalSystemMemory);
System.out.println("Total system used memory in mb: " + totalSystemUsedMemory);
System.out.println("Total system free memory in mb: "   + totalSystemFreeMemory);
于 2020-01-01T10:41:17.423 回答
0

Runtime.getRuntime().freeMemory()是一种在运行时为 JVM 获取空闲内存的方法。这是好方法(或)不是完全取决于您的应用程序。

于 2012-10-09T20:21:47.047 回答
0
long freeMemory = Runtime.getRuntime().freeMemory();
于 2012-10-09T20:22:38.163 回答