"outOfMemoryError java heap space" 计算直方图,RAM 与时间的交易?
"outOfMemoryError java heap space" calculating histogram, trade of RAM vs Time?
我已经使用我的项目一段时间了,它在某些时候计算大图像的直方图(处理高达 40Mpx 的照片,但通常在 10-20Mpx 左右。
我一直在使用 16GB RAM 的笔记本电脑,但我没有发现任何问题。今天我换了一台 6GB 内存的笔记本电脑,当我计算直方图时,用 17Mpx 的照片开始出现这个异常。
我已经切换到这种计算方式,因为它比遍历所有像素并获取每个像素中的所有颜色更快。
您对我应该如何编写这样的代码有何建议?
如果我想让程序更快,我想我需要使用更多的 RAM(这个大的 double[] 对象)。如果 PC 有足够的 RAM,就不会有任何问题,程序会 运行 流畅,但如果 PC 没有这么多的 RAM,它就会崩溃,使程序无法使用。
那么我是否应该通过手动遍历所有像素将代码写在 "slower way" 中并使其成为 "safer"?
还是我做错了什么,两件事可以同时做?
这是发生 outOfMemoryError 时的代码片段:
// dataset
dataset = new HistogramDataset();
final int w = image.getWidth();
final int h = image.getHeight();
double[] r = new double[w * h]; //Here some PC's with not enough RAM will crash
double[] s = new double[w * h];
double[] t;
r = raster.getSamples(0, 0, w, h, 0, r);
s = r;
dataset.addSeries(lang.getString("HistogramRGB.String.red"), r, BINS);
r = raster.getSamples(0, 0, w, h, 1, r);
t = new double[r.length + s.length]; //Add R+G
System.arraycopy(s, 0, t, 0, s.length);
System.arraycopy(r, 0, t, s.length, r.length);
dataset.addSeries(lang.getString("HistogramRGB.String.green"), r, BINS);
r = raster.getSamples(0, 0, w, h, 2, r);
s = new double[r.length + t.length]; //Add R+G+B
System.arraycopy(t, 0, s, 0, t.length);
System.arraycopy(r, 0, s, t.length, r.length);
dataset.addSeries(lang.getString("HistogramRGB.String.blue"), r, BINS);
dataset.addSeries(lang.getString("HistogramRGB.String.brigthness"), s, BINS);
// chart
chart = ChartFactory.createHistogram(lang.getString("HistogramRGB.String.histogram"), "",
"", dataset, PlotOrientation.VERTICAL, false, true, false);
更新:
使用评论中建议的选项 -Xmx 可以解决问题。
使用@TheConstructor 优化的结果,在使用 windows 10 32 位和 3,5GB 内存的虚拟机中:
- 优化前小于-Xmx1444m会运行异常
- 优化后小于-Xmx824m会运行异常
这是我默认的:
java -XX:+PrintFlagsFinal -version | findstr HeapSize
uintx ErgoHeapSizeLimit = 0 {product}
uintx HeapSizePerGCThread = 67108864 {product}
uintx InitialHeapSize := 16777216 {product}
uintx LargePageHeapSizeThreshold = 134217728 {product}
uintx MaxHeapSize := 268435456 {product}
java version "1.8.0_111"
Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
Java HotSpot(TM) Client VM (build 25.111-b14, mixed mode, sharing)
也就是268MB左右,我在这台电脑上可以通过命令设置的最大值是1,5GB。我觉得很奇怪,没有任何其他东西打开整个 windows 与任何其他程序需要 2GB 的 3.5GB。
最终我猜想您需要为 java
调用指定一个正确大小的 -Xmx
或 -XX:MaxHeapSize
参数。默认值来自可用内存并限制内存量 Java 可以使用。试着找出一个工作尺寸。你可以尝试例如-Xmx2g
。关于 -Xmx
can be found inside documentation
的一些细节
查看您的代码,您可以消除 t
并跳过 s
的初始化。虽然我猜它不会解决所有问题这里是我的修改:
// dataset
dataset = new HistogramDataset();
final int w = image.getWidth();
final int h = image.getHeight();
double[] buffer = new double[w * h];
double[] rgb;
buffer = raster.getSamples(0, 0, w, h, 0, buffer);
rgb = Arrays.copyOf(buffer, buffer.length * 3); // copy as otherwise it gets overwritten in next getSamples
dataset.addSeries(lang.getString("HistogramRGB.String.red"), buffer, BINS);
buffer = raster.getSamples(0, 0, w, h, 1, buffer);
System.arraycopy(buffer, 0, rgb, buffer.length, buffer.length); //Add G
dataset.addSeries(lang.getString("HistogramRGB.String.green"), buffer, BINS);
buffer = raster.getSamples(0, 0, w, h, 2, buffer);
System.arraycopy(buffer, 0, rgb, buffer.length * 2, buffer.length); //Add B
dataset.addSeries(lang.getString("HistogramRGB.String.blue"), buffer, BINS);
dataset.addSeries(lang.getString("HistogramRGB.String.brigthness"), rgb, BINS);
// chart
chart = ChartFactory.createHistogram(lang.getString("HistogramRGB.String.histogram"), "", "", dataset,
PlotOrientation.VERTICAL, false, true, false);
根据 addSeries
是否创建所提供数据的副本,您可能需要在每次调用 getSamples
之前分配 buffer
一个新数组。如果我猜对了它是 Raster#getSamples 你也可以使用 (double[]) null
作为参数而不是 buffer
并让 getSamples
为你分配数组。
如果精度不太重要,您还可以将 double[]
切换为 float[]
,这样可以节省一半的内存。
如果可能,最好减少对象的创建,以便减少堆 space 内存。如果所有对象在您的应用程序中都是强制性的,那么在 运行 您的应用程序:
时使用命令行参数
java -Xms<size> set initial Java heap size
(OR)
java -Xmx<size> set maximum Java heap size
我已经使用我的项目一段时间了,它在某些时候计算大图像的直方图(处理高达 40Mpx 的照片,但通常在 10-20Mpx 左右。
我一直在使用 16GB RAM 的笔记本电脑,但我没有发现任何问题。今天我换了一台 6GB 内存的笔记本电脑,当我计算直方图时,用 17Mpx 的照片开始出现这个异常。
我已经切换到这种计算方式,因为它比遍历所有像素并获取每个像素中的所有颜色更快。
您对我应该如何编写这样的代码有何建议?
如果我想让程序更快,我想我需要使用更多的 RAM(这个大的 double[] 对象)。如果 PC 有足够的 RAM,就不会有任何问题,程序会 运行 流畅,但如果 PC 没有这么多的 RAM,它就会崩溃,使程序无法使用。
那么我是否应该通过手动遍历所有像素将代码写在 "slower way" 中并使其成为 "safer"?
还是我做错了什么,两件事可以同时做?
这是发生 outOfMemoryError 时的代码片段:
// dataset
dataset = new HistogramDataset();
final int w = image.getWidth();
final int h = image.getHeight();
double[] r = new double[w * h]; //Here some PC's with not enough RAM will crash
double[] s = new double[w * h];
double[] t;
r = raster.getSamples(0, 0, w, h, 0, r);
s = r;
dataset.addSeries(lang.getString("HistogramRGB.String.red"), r, BINS);
r = raster.getSamples(0, 0, w, h, 1, r);
t = new double[r.length + s.length]; //Add R+G
System.arraycopy(s, 0, t, 0, s.length);
System.arraycopy(r, 0, t, s.length, r.length);
dataset.addSeries(lang.getString("HistogramRGB.String.green"), r, BINS);
r = raster.getSamples(0, 0, w, h, 2, r);
s = new double[r.length + t.length]; //Add R+G+B
System.arraycopy(t, 0, s, 0, t.length);
System.arraycopy(r, 0, s, t.length, r.length);
dataset.addSeries(lang.getString("HistogramRGB.String.blue"), r, BINS);
dataset.addSeries(lang.getString("HistogramRGB.String.brigthness"), s, BINS);
// chart
chart = ChartFactory.createHistogram(lang.getString("HistogramRGB.String.histogram"), "",
"", dataset, PlotOrientation.VERTICAL, false, true, false);
更新: 使用评论中建议的选项 -Xmx 可以解决问题。
使用@TheConstructor 优化的结果,在使用 windows 10 32 位和 3,5GB 内存的虚拟机中:
- 优化前小于-Xmx1444m会运行异常
- 优化后小于-Xmx824m会运行异常
这是我默认的:
java -XX:+PrintFlagsFinal -version | findstr HeapSize
uintx ErgoHeapSizeLimit = 0 {product}
uintx HeapSizePerGCThread = 67108864 {product}
uintx InitialHeapSize := 16777216 {product}
uintx LargePageHeapSizeThreshold = 134217728 {product}
uintx MaxHeapSize := 268435456 {product}
java version "1.8.0_111"
Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
Java HotSpot(TM) Client VM (build 25.111-b14, mixed mode, sharing)
也就是268MB左右,我在这台电脑上可以通过命令设置的最大值是1,5GB。我觉得很奇怪,没有任何其他东西打开整个 windows 与任何其他程序需要 2GB 的 3.5GB。
最终我猜想您需要为 java
调用指定一个正确大小的 -Xmx
或 -XX:MaxHeapSize
参数。默认值来自可用内存并限制内存量 Java 可以使用。试着找出一个工作尺寸。你可以尝试例如-Xmx2g
。关于 -Xmx
can be found inside documentation
查看您的代码,您可以消除 t
并跳过 s
的初始化。虽然我猜它不会解决所有问题这里是我的修改:
// dataset
dataset = new HistogramDataset();
final int w = image.getWidth();
final int h = image.getHeight();
double[] buffer = new double[w * h];
double[] rgb;
buffer = raster.getSamples(0, 0, w, h, 0, buffer);
rgb = Arrays.copyOf(buffer, buffer.length * 3); // copy as otherwise it gets overwritten in next getSamples
dataset.addSeries(lang.getString("HistogramRGB.String.red"), buffer, BINS);
buffer = raster.getSamples(0, 0, w, h, 1, buffer);
System.arraycopy(buffer, 0, rgb, buffer.length, buffer.length); //Add G
dataset.addSeries(lang.getString("HistogramRGB.String.green"), buffer, BINS);
buffer = raster.getSamples(0, 0, w, h, 2, buffer);
System.arraycopy(buffer, 0, rgb, buffer.length * 2, buffer.length); //Add B
dataset.addSeries(lang.getString("HistogramRGB.String.blue"), buffer, BINS);
dataset.addSeries(lang.getString("HistogramRGB.String.brigthness"), rgb, BINS);
// chart
chart = ChartFactory.createHistogram(lang.getString("HistogramRGB.String.histogram"), "", "", dataset,
PlotOrientation.VERTICAL, false, true, false);
根据 addSeries
是否创建所提供数据的副本,您可能需要在每次调用 getSamples
之前分配 buffer
一个新数组。如果我猜对了它是 Raster#getSamples 你也可以使用 (double[]) null
作为参数而不是 buffer
并让 getSamples
为你分配数组。
如果精度不太重要,您还可以将 double[]
切换为 float[]
,这样可以节省一半的内存。
如果可能,最好减少对象的创建,以便减少堆 space 内存。如果所有对象在您的应用程序中都是强制性的,那么在 运行 您的应用程序:
时使用命令行参数java -Xms<size> set initial Java heap size
(OR)
java -Xmx<size> set maximum Java heap size