我正在使用OpenCV的java包装器。我试图在电影的帧上写一个迭代器。我的问题是迭代器是一个巨大的内存泄漏。这是迭代器的一个非常简化的版本,它有这个漏洞:
public static final class SimpleIt implements Iterator<Mat> {
private final VideoCapture capture;
boolean hasNext;
public SimpleIt(final VideoCapture capture) {
this.capture = capture;
hasNext = capture.grab();
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public Mat next() {
final Mat mat = new Mat();
capture.retrieve(mat);
hasNext = capture.grab();
return mat;
}
}
我使用这个循环迭代这段代码:
final VideoCapture vc = new VideoCapture("/path/to/file");
final SimpleIt it = new SimpleIt(vc);
while (it.hasNext) {
it.next();
}
只需迭代就会增加线性内存消耗。我看到问题是next() - Method中的第一行。它总是创造一个新的垫子。但是只谈到java,只要迭代代码迭代到下一个图像,这个Mat就会超出范围。
我可以通过不每次使用新的Mat来克服这个问题,但是总是覆盖相同的Mat-Object,如下所示:
private final VideoCapture capture;
private final Mat mat = new Mat();
boolean hasNext;
@Override
public Mat next() {
capture.retrieve(mat);
hasNext = capture.grab();
return mat;
}
但是现在迭代器给出的最后一帧将被覆盖。因此,如果我对这个单帧感兴趣,我不能把它放在外面供以后使用。当然,我可以复制它,但这也很昂贵。
我认为问题在于垃圾收集器不会破坏Mat对象,因为它不识别内存消耗,因为它不是java堆空间。在循环中调用mat.release()会有所帮助,但当然在实际代码中这意味着我的Mat对象没有垃圾收集。
有人知道怎么做吗?
编辑:
由于我的第二个解决方案的问题似乎并不清楚,我会更明确地写下来。使用迭代器考虑以下代码:
final VideoCapture vc = new VideoCapture("/path/to/file");
final SimpleIt it = new SimpleIt(vc);
int i = 0;
Mat save = null;
while (it.hasNext) {
final Mat next = it.next();
if (i == 10) {
save = next;
Highgui.imwrite("/path/to/10.png", save);
} else if (i == 30) {
Highgui.imwrite("/path/to/30.png", save);
}
i++;
}
使用迭代器的第二个版本,10.png和30.png将是不同的图像。但这显然不是预期的。
答案 0 :(得分:15)
您应该致电mat.release()
。
我的申请中遇到了与你非常相似的问题。帧速率非常高,以至于java堆长大到可用的总系统内存,这有时会导致JVM崩溃。 GC太慢了,我没有任何检查可用内存的机制,等待这是不够的。
一种解决方案是通过简单地使用Thread.sleep()
来降低帧速率,这当然似乎是不可接受的。但它帮助GC按时完成了工作。
最后使用mat.release()
解决了问题。
您不必担心Mat对象的垃圾收集,因为此调用仅释放基础数据。当正确的时间到来时,GC将处理Java对象包装器。
答案 1 :(得分:15)
我只想添加我的0.02美元,因为我在编写将运行很长时间的应用程序时遇到了这个问题。
当Java Mat-wrapper被垃圾收集时,会自动调用Mat.release()。但是,由于Java包装器与本机分配的对象相比非常小,因此可能不会足够快地进行垃圾收集。因此,当您知道自己已完成某个对象时,可以执行Mat.release()
或定期调用System.gc()
以强制删除未使用的对象。
答案 2 :(得分:6)
的System.gc();不适合我。
我添加了一行:
System.runFinalization();
A Codesnippet:
startGC--;
if (startGC==0) {
System.gc();
System.runFinalization();
startGC=100;
}
答案 3 :(得分:5)
看来,没有好的解决方案。我现在已经试验了好几个小时。我想出的最好的是定期调用垃圾收集器:
int count = 0;
@Override
public Mat next() {
final Mat result = mat;
mat = new Mat();
capture.retrieve(mat);
hasNext = capture.grab();
if (++count % 200 == 0) {
System.gc();
}
return result;
由于这有效,它表明我的假设是正确的,java无法识别从C分配的RAM,因此不会调用GC,即使机器的RAM用完了。
这不是一个很好的解决方案,因为它可能不是很稳定。如果其他人有更好的想法,我很感兴趣。
答案 4 :(得分:1)
我会将您的.hasNext
方法修改为:
public boolean hasNext() {
return hasNext;
}
然后你所描述的方法,在下面复制,应该可以正常工作......你将迭代直到没有剩下任何东西,此时你可以将最后一个图像分配给一个新的Mat对象......
public Mat next() {
capture.retrieve(mat);
hasNext = capture.grab();
return mat;
}
然后:
final VideoCapture vc = new VideoCapture("/path/to/file");
final SimpleIt it = new SimpleIt(vc);
final Mat lastFrame = new Mat();
while (it.hasNext) {
lastFrame = it.next();
}
我确实意识到这会产生额外的内存使用量。可能有一种解决方法,但它应该可以正常工作......