大量内存溢出导致堆大小在大约 8 秒内从大约 64mb 变为 1.5gb。垃圾收集器的问题?
Posted
技术标签:
【中文标题】大量内存溢出导致堆大小在大约 8 秒内从大约 64mb 变为 1.5gb。垃圾收集器的问题?【英文标题】:Massive memory hemorrhage that causes heap size to go from about 64mb, to 1.5gb in roughly 8 seconds. Issue with garbage collector? 【发布时间】:2013-02-14 23:12:44 【问题描述】:问题来了:
如您所见,内存使用膨胀失控!我不得不向 JVM 添加参数以增加堆大小,以避免在我弄清楚发生了什么时出现内存不足错误。不好!
基本应用总结(针对上下文)
这个应用程序(最终)将用于基本的屏幕简历和模板匹配类型的东西,以实现自动化目的。我想实现尽可能高的帧速率来观看屏幕,并通过一系列单独的消费者线程处理所有处理。
我很快发现现有的 Robot 类在速度方面真的很糟糕,所以我打开了源代码,去掉了所有重复的工作和浪费的开销,并将它重新构建为我自己的名为 FastRobot 的类。
班级代码:
public class FastRobot
private Rectangle screenRect;
private GraphicsDevice screen;
private final Toolkit toolkit;
private final Robot elRoboto;
private final RobotPeer peer;
private final Point gdloc;
private final DirectColorModel screenCapCM;
private final int[] bandmasks;
public FastRobot() throws HeadlessException, AWTException
this.screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
this.screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
toolkit = Toolkit.getDefaultToolkit();
elRoboto = new Robot();
peer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);
gdloc = screen.getDefaultConfiguration().getBounds().getLocation();
this.screenRect.translate(gdloc.x, gdloc.y);
screenCapCM = new DirectColorModel(24,
/* red mask */ 0x00FF0000,
/* green mask */ 0x0000FF00,
/* blue mask */ 0x000000FF);
bandmasks = new int[3];
bandmasks[0] = screenCapCM.getRedMask();
bandmasks[1] = screenCapCM.getGreenMask();
bandmasks[2] = screenCapCM.getBlueMask();
Toolkit.getDefaultToolkit().sync();
public void autoResetGraphicsEnv()
this.screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
this.screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
public void manuallySetGraphicsEnv(Rectangle screenRect, GraphicsDevice screen)
this.screenRect = screenRect;
this.screen = screen;
public BufferedImage createBufferedScreenCapture(int pixels[]) throws HeadlessException, AWTException
// BufferedImage image;
DataBufferInt buffer;
WritableRaster raster;
pixels = peer.getRGBPixels(screenRect);
buffer = new DataBufferInt(pixels, pixels.length);
raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
return new BufferedImage(screenCapCM, raster, false, null);
public int[] createArrayScreenCapture() throws HeadlessException, AWTException
return peer.getRGBPixels(screenRect);
public WritableRaster createRasterScreenCapture(int pixels[]) throws HeadlessException, AWTException
// BufferedImage image;
DataBufferInt buffer;
WritableRaster raster;
pixels = peer.getRGBPixels(screenRect);
buffer = new DataBufferInt(pixels, pixels.length);
raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
// SunWritableRaster.makeTrackable(buffer);
return raster;
本质上,我对原来所做的更改只是从函数体中移动了许多分配,并将它们设置为类的属性,这样它们就不会每次都被调用。这样做实际上对帧速率有显着的影响。即使在我的电源严重不足的笔记本电脑上,它也从库存机器人类的约 4 fps 到我的 FastRobot 类的约 30 fps。
第一次测试:
当我在主程序中开始出现内存不足错误时,我设置了这个非常简单的测试来监视 FastRobot。注意:这是生成上述堆配置文件的代码。
public class TestFBot
public static void main(String[] args)
try
FastRobot fbot = new FastRobot();
double startTime = System.currentTimeMillis();
for (int i=0; i < 1000; i++)
fbot.createArrayScreenCapture();
System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);
catch (AWTException e)
e.printStackTrace();
已检查:
它不会每次都这样做,这真的很奇怪(而且令人沮丧!)。事实上,它很少用上面的代码做到这一点。但是,如果我有多个背靠背的 for 循环,内存问题就会很容易重现。
测试 2
public class TestFBot
public static void main(String[] args)
try
FastRobot fbot = new FastRobot();
double startTime = System.currentTimeMillis();
for (int i=0; i < 1000; i++)
fbot.createArrayScreenCapture();
System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);
startTime = System.currentTimeMillis();
for (int i=0; i < 500; i++)
fbot.createArrayScreenCapture();
System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);
startTime = System.currentTimeMillis();
for (int i=0; i < 200; i++)
fbot.createArrayScreenCapture();
System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);
startTime = System.currentTimeMillis();
for (int i=0; i < 1500; i++)
fbot.createArrayScreenCapture();
System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);
catch (AWTException e)
e.printStackTrace();
检查
失控堆现在可以重现我会说大约 80% 的时间。我查看了探查器的所有内容,最值得注意的事情(我认为)是垃圾收集器似乎在第四个也是最后一个循环开始时停止了 right。
上述代码的输出形式给出了以下时间:
Time taken: 24.282 //Loop1
Time taken: 11.294 //Loop2
Time taken: 7.1 //Loop3
Time taken: 70.739 //Loop4
现在,如果将前三个循环相加,则它加起来为 42.676,这可疑地对应于垃圾收集器停止和内存峰值的确切时间。
现在,这是我第一次进行性能分析的牛仔竞技表演,更不用说我第一次想到垃圾收集——它总是在后台神奇地发挥作用——所以,我不确定我发现了什么,如果有的话。
其他个人资料信息
Augusto 建议查看内存配置文件。有 1500 多个 int[]
被列为“无法访问,但尚未收集”。这些肯定是peer.getRGBPixels()
创建的int[]
数组,但由于某种原因,它们没有被销毁。不幸的是,这些附加信息只会增加我的困惑,因为我不确定 为什么 GC 不会收集它们
使用小堆参数 -Xmx256m 的配置文件:
根据不可靠和 Hot Licks 的建议,我将最大堆大小设置为明显更小。虽然这确实阻止了它在内存使用量上的 1gb 跳跃,但它仍然没有解释为什么程序在进入第 4 次迭代时会膨胀到其最大堆大小。
如您所见,确切的问题仍然存在,只是变小了。 ;) 这个解决方案的问题是,由于某种原因,程序仍在消耗它所能消耗的所有内存——从第一次迭代开始,fps 性能也有显着变化,消耗的内存非常少,并且最后一次迭代,它会消耗尽可能多的内存。
问题仍然存在为什么它会膨胀?
点击“强制垃圾回收”按钮后的结果:
在 jtahlborn 的建议下,我点击了强制垃圾回收按钮。它工作得很好。它从 1gb 的内存使用量下降到 60mb 左右的基线。
所以,这似乎是治疗方法。现在的问题是,我如何以编程方式强制 GC 执行此操作?
将本地 Peer 添加到函数作用域后的结果:
在 David Waters 的建议下,我修改了 createArrayCapture()
函数,使其拥有一个本地 Peer
对象。
不幸的是,内存使用模式没有变化。
在第 3 次或第 4 次迭代时仍然会变得很大。
内存池分析:
来自不同内存池的屏幕截图
所有池:
伊甸园池:
老一代:
几乎所有的内存使用似乎都在这个池中。
注意:PS Survivor Space 的使用率(显然)为 0
我还有几个问题:
(a) Garbage Profiler 图的意思是我认为的意思吗?还是我混淆了因果关系的相关性?正如我所说,我在这些问题的未知领域。
(b) 如果它是垃圾收集器...我该怎么办...?为什么它完全停止,然后在程序的其余部分以降低的速度运行?
(c) 我该如何解决这个问题?
这里发生了什么?
【问题讨论】:
您正在使用您的工具包,为什么不分析内存并查看您正在创建的对象以及它们有多大。看起来您正在内存中创建一些图像,您可能不会从内存中删除这些图像(您从哪里调用createRasterScreenCapture()
)?
@Augusto 我已经用其他个人资料信息编辑了我的帖子。 1500 int[]
s 未被 GC 收集,并列为“无法访问”。我不确定我是否遵循您的最后一个问题.. 所有createArraycap..
调用都是从同一个FastRobot
对象完成的。是这个意思吗?
首先,我们需要明确指出,将“存储使用率”上升到堆大小限制点不是问题。直到可用的堆空间几乎耗尽时,GC 才会启动,一旦存储被堆“殖民”,它将在 JVM 运行期间保持分配状态。
尝试较小的最大内存。
其次,Java 中的“泄漏”(在使用 JNI 的代码之外)几乎总是(99.999% 的时间)由于程序员“挂在”旧数据上的编码错误(尽管经常是无意的)。当不再使用这些大对象时,始终清除引用其他大对象的对象中的指针(咳咳,“对象引用”)是一种很好的做法。例如,如果您有一个 10K 元素数组用于某事,并且您已经完成了该数组,请清除可能在其他对象中的所有指向它的指针。
【参考方案1】:
您说您将对象创建从方法转移到类上的字段。您移动的依赖项之一是“对等”吗?例如
peer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);
很可能对等点会保留对象生命周期内拍摄的所有屏幕截图,当对等点超出范围、Robot 中的方法结束、FastRobot 类的生命周期时,这将被清除。
尝试将 peer 的创建和范围移回您的方法,看看有什么不同。
public int[] createArrayScreenCapture() throws HeadlessException, AWTException
RobotPeer localPeer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);
return localPeer.getRGBPixels(screenRect);
尝试 2
所以,这似乎是治疗方法。现在的问题是,我如何亲 在语法上强制 GC 这样做?
您可以调用 System.gc() 来请求垃圾回收。请注意,这是一个请求,而不是一个要求。 JVM 只会在认为值得时才运行垃圾收集。
如您所见,确切的问题仍然存在,它刚刚被提出 更小。 ;) 这个解决方案的问题是该程序,对于某些 原因,它仍在吞噬所有的记忆——有 与第一次迭代相比,fps 性能也有显着变化, 它消耗很少的内存,最后的迭代,它 尽可能多地消耗内存。
问题仍然是为什么它会膨胀?
JVM 将尝试仅在绝对必要时运行主要垃圾收集(大部分堆已使用)。 (阅读年轻一代与老一代以及在年轻一代、伊甸园空间和幸存者空间内)。因此,期望长期存在或需要内存的 Java 应用程序位于最大堆大小附近。值得注意的是,要让内存进入老一代,它必须在 3 次次要 GC 运行中幸存下来(Eden => Survivor 1 => Survivor 2 => Old Generation [取决于您正在运行的 JVM 和您选择的 GC 方案在命令行参数上。])
至于为什么这种行为会发生变化,可能是很多事情。最后一个循环是最长的,System.getCurrentTimeMillis() 是否阻塞足够长的时间让 GC 可以在不同的线程上运行?所以问题只显示在更长的循环中?截屏过程对我来说听起来很低级,我假设通过调用操作系统内核来实现,这是否会阻止内核空间中的进程阻止其他线程运行? (这将停止 gc 在后台线程中运行)。
查看http://www.javacodegeeks.com/2012/01/practical-garbage-collection-part-1.html 了解垃圾收集世界的介绍。或Java Memory explained (SUN JVM) 获取更多链接。
希望有所帮助。
【讨论】:
我只运行了几次,但不幸的是我仍然遇到同样的现象。在第三次或第四次迭代中,内存使用量激增。如果您想查看分析器输出,我已经编辑了问题。【参考方案2】:尝试手动指定垃圾收集器。
并发标记扫描是一种很好的通用标记,它在低暂停和合理的吞吐量之间提供了良好的平衡。
如果您使用的是 Java 7 或更高版本的 Java 6,G1 收集器可能会更好,因为它还能够防止内存碎片。
您可以查看Java SE Hotspot Virtual Machine Garbage Collection Tuning 页面以获取更多信息和指针:-D
【讨论】:
以上是关于大量内存溢出导致堆大小在大约 8 秒内从大约 64mb 变为 1.5gb。垃圾收集器的问题?的主要内容,如果未能解决你的问题,请参考以下文章
axios在20秒内给出响应,但具有相同请求有效载荷的相同api在6秒内从邮递员那里得到响应