[英]How to estimate if the JVM has enough free memory for a particular data structure?
我有以下情况:有几台机器组成一个集群。 客户端可以加载数据集,我们需要选择将加载数据集的节点,如果没有一台机器可以适合数据集,则拒绝加载/避免OOM错误。
我们当前所做的事情:我们现在是数据集中的entry count
,并估计memory to be used
entry count * empirical factor
的memory to be used
entry count * empirical factor
(手动确定)。 然后检查它是否低于空闲内存(由Runtime.freeMemory()
得到),如果是,则加载它(否则重做其他节点上的进程/报告没有空闲容量)。
这种方法的问题是:
empirical factor
freeMemory
有时可能由于某些未清理的垃圾而报告不足(可以通过在每次调用之前运行System.gc
来避免,但这会降低服务器的速度并且还可能导致过早的升级) 这个问题有更好的解决方案吗?
empirical factor
可以计算为构建步骤并放置在属性文件中。
虽然freeMemory()
几乎总是小于GC之后可以释放的数量,但是如果maxMemory()
表明可能有很多,你可以检查它是否可用并调用System.gc()
。
注意:在生产中使用System.gc()
仅在非常罕见的情况下进行,并且通常它经常被错误地使用,从而导致性能降低并使实际问题变得模糊。
我会避免触发OOME,除非你正在运行的是一个JVM,你可以根据需要重启。
我的解决方案
如果除了您的程序之外没有其他进程在运行,请将Xmx设置为物理机RAM的90%-95%
。 对于32 GB RAM机器,将Xmx
设置为27MB - 28MB
。
使用良好的gc算法之一 - CMS或G1GC并微调相关参数。 I prefer G1GC if you need more than 4 GB RAM for your application
。 如果您选择G1GC,请参阅此问题:
自己计算内存使用量上限而不是检查空闲内存。 添加要分配的已用内存和内存。 Subtract it from your own cap like 90% of Xmx
。 如果仍有可用内存,则授予内存分配请求。
另一种方法是隔离自己的JVM中的每个数据负载。 您只需预定义每个JVM的最大堆大小等,并设置每个主机的JVM数量,使每个JVM可以占用其完整的最大堆大小。 这将使用更多的资源 - 这意味着你不能通过填充更多的低内存数据负载来利用每个内存的最后一个字节 - 但它大大简化了问题(并降低了出错的风险),它可以告诉您何时/是否需要添加新主机,最重要的是,它可以减少任何一个客户端对所有其他客户端的影响。
使用这种方法,给定的JVM要么“忙”,要么“可用”。
在任何给定的数据加载完成之后,相关的JVM可以声明自己可用于新的数据加载,或者它可以关闭。 (无论哪种方式,您都希望有一个单独的进程来监视JVM并确保正确的数字始终在运行。)
另一种方法是“只是尝试加载数据集”(如果抛出OOM则返回)但是一旦抛出OOM,你可能会破坏在同一个JVM中运行的其他线程,并且没有优雅的方法从中恢复。
在JVM中没有很好的方法来处理和从OOME中恢复,但是在 OOM发生之前有办法做出反应。 Java有java.lang.ref.SoftReference, 保证在虚拟机抛出OutOfMemoryError之前清除它 。 这个事实可以用于OOM的早期预测。 例如,如果预测被触发,则可以中止数据加载。
ReferenceQueue<Object> q = new ReferenceQueue<>();
SoftReference<Object> reference = new SoftReference<>(new Object(), q);
q.remove();
// reference removed - stop data load immediately
可以使用-XX:SoftRefLRUPolicyMSPerMB标志(对于Oracle JVM)调整灵敏度。 解决方案并不理想,它的有效性取决于各种因素 - 代码中使用的其他软参考,GC如何调整,JVM版本,火星上的天气......但如果你幸运的话它可以帮助。
正如您所正确指出的那样,使用freeMemory
不会告诉您Java垃圾收集可以释放的内存量。 您可以使用JVM的JConsole,VisualVM,jstat和printGCStats
选项等工具运行负载测试并了解JVM堆使用模式和内存分配, printGCStats
分配模式。 这将给出更准确地计算empirical factor
的想法,基本上了解您的java应用程序可以处理的加载模式是什么。 接下来将选择正确的GC并调整基本GC设置以提高效率。 这不是一个快速的解决方案,但从长远来看可能是一个更好的解决方案。
使用-XX杀死JVM的另一种方法:OnOutOfMemoryError =“kill -9%p” JVM设置,一旦发生OOM,然后写入,就会生成一个简单的进程监视脚本,以便在JVM关闭时启动它。
客户端可以加载数据集,我们需要选择将加载数据集的节点,如果没有一台机器可以适合数据集,则拒绝加载/避免OOM错误。
这是一个工作调度问题,即我有限的资源我们如何最好地利用它们。 我会在接近结束时收到OOM问题。
我们有一个主要因素,即RAM,但调度问题的解决方案取决于许多因素,即......
作业是小还是大,即在节点上运行数百/数千个或两个或三个。 想想Linux调度程序。
他们需要在特定的时间框架内完成吗? 实时调度程序。
鉴于我们在工作开始时所知道的一切,我们可以预测一份工作何时会在一段时间内结束? 如果我们可以预测在节点X上我们每15到20秒释放100MB,我们就可以在该节点上安排200Mb的工作,即我相信在40秒内我将在该节点上完成200Mb的空间并且40秒是提交作业的人员或机器的可接受限制。
让我们假设我们有如下函数。
predicted_time predict(long bytes[, factors]);
这些factors
是我们需要考虑的其他因素,我在上面提到过,对于每个应用程序,都会有一些东西可以添加以适合您的场景。
这些因素将计算时,可以任意的加权predicted_time
。
predicted_time
是(可以是任何TIMEUNIT),这点从现在开始,它可以服务于这个任务,节点给你最小的数字相信的毫秒数是工作应安排的节点。 然后,您可以使用此函数,我们有一个任务队列,即在下面的代码中this.nodes[i]
表示一个JVM实例。
private void scheduleTask() {
while(WorkEvent()) {
while(!this.queue.isEmpty()) {
Task t = this.queue.poll();
for (int i = 0; i < this.maxNodes; i++) {
long predicted_time = this.nodes[i].predict(t);
if (predicted_time < 0) {
boolean b = this.queue.offer(t);
assert(b);
break;
}
if (predicted_time <= USER_EXPERIENCE_DELAY) {
this.nodes[i].addTask(t);
break;
}
alert_user(boolean b = this.queue.offer(t);
assert(b);
}
}
}
}
如果predicted_time < 0
我们有错误,我们重新安排工作,实际上我们想知道为什么,但这并不难添加。 如果predicted_time <= USER_EXPERIENCE_DELAY
,则可以安排作业。
这是如何避免OOM的
我们可以从我们的调度程序中收集我们想要的任何统计数据,即正确安排的X大小的工作量,目标是减少错误并使其随着时间的推移更可靠,即减少我们告诉客户他们的工作的次数无法提供服务。 我们所做的就是将问题减少到我们可以在统计上改进的最佳解决方案。
客户端可以加载数据集,我们需要选择将加载数据集的节点,如果没有一台机器可以适合数据集,则拒绝加载/避免OOM错误。
这是一个工作调度问题,即我有限的资源我们如何最好地利用它们。 我会在接近结束时收到OOM问题。
我们有一个主要因素,即RAM,但调度问题的解决方案取决于许多因素,即......
作业是小还是大,即在节点上运行数百/数千个或两个或三个。 想想Linux调度程序。
他们需要在特定的时间框架内完成吗? 实时调度程序。
鉴于我们在工作开始时所知道的一切,我们可以预测一份工作何时会在一段时间内结束? 如果我们可以预测在节点X上我们每15到20秒释放100MB,我们就可以在该节点上安排200Mb的工作,即我相信在40秒内我将在该节点上完成200Mb的空间并且40秒是提交作业的人员或机器的可接受限制。
让我们假设我们有如下函数。
predicted_time predict(long bytes[, factors]);
这些factors
是我们需要考虑的其他因素,我在上面提到过,对于每个应用程序,都会有一些东西可以添加以适应您的场景,即有多少因素取决于您。
这些因素将计算时,可以任意的加权predicted_time
。
predicted_time
是(可以是任何TIMEUNIT),这点从现在开始,它可以服务于这个任务,节点给你最小的数字相信的毫秒数是工作应安排的节点。 然后,您可以使用此函数,我们有一个任务队列,即在下面的代码中this.nodes[i]
表示一个JVM实例。
private void scheduleTask() {
while(WorkEvent()) {
while(!this.queue.isEmpty()) {
Task t = this.queue.poll();
for (int i = 0; i < this.maxNodes; i++) {
long predicted_time = this.nodes[i].predict(t);
if (predicted_time < 0) {
boolean b = this.queue.offer(t);
assert(b);
break;
}
if (predicted_time <= USER_EXPERIENCE_DELAY) {
this.nodes[i].addTask(t);
break;
}
alert_user(boolean b = this.queue.offer(t);
assert(b);
}
}
}
}
如果predicted_time < 0
我们有错误,我们重新安排工作,实际上我们想知道为什么,但这并不难添加。 如果predicted_time <= USER_EXPERIENCE_DELAY
,则可以安排作业。
这是如何避免OOM的
我们可以从我们的调度程序中收集我们想要的任何统计数据,即正确安排的X大小的工作量,目标是减少错误并使其随着时间的推移更可靠,即减少我们告诉客户他们的工作的次数无法维修或失败。 我们已经或至少试图尝试的是将问题减少到我们可以在实现最佳解决方案时进行统计改进的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.