如何在 Java 的 RAM 中处理巨大的 data/images?
How to handle huge data/images in RAM in Java?
总结
- 我正在读取一个包含图像数据的大型二进制文件。
- 对数据进行Cumulative Count Cut分析[需要另一个与图像大小相同的数组]。
- 数据被拉伸到 0 到 255 之间存储在
BufferedImage
个像素中,以在 JPanel 上绘制图像。
- 在此图像上,使用
AffineTransform
执行缩放。
问题
小图片(<.5GB)
1.1 当我增加执行缩放的比例因子时,
点异常被抛出:-
java.lang.OutOfMemoryError: Java heap space.
下面是用于缩放的代码-
scaled = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g2d = (Graphics2D)scaled.createGraphics();
AffineTransform transformer = new AffineTransform();
transformer.scale(scaleFactor, scaleFactor);
g2d.setTransform(transformer);
- 大图(>1.5GB)
- 加载大图(>1.5GB)时,出现与
1.1,即使图片小到无法加载,有时也会出现同样的错误。
尝试过的解决方案
- 我尝试使用 BigBufferedImage 代替 BufferedImage 来存储拉伸数据。
BigBufferedImage image = BigBufferedImage.create(newCol,newRow, BufferedImage.TYPE_INT_ARGB);
但是无法传递给g2d.drawImage(image, 0, 0, this);
因为 JPanel 的 repaint 方法由于某种原因刚刚停止。
我尝试以低分辨率加载图像,其中像素被读取并且很少有列和行 jumped/skipped。但问题是如何决定随着图像大小的变化而跳过多少像素,因此我无法决定如何决定 jump 参数。
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY,0, inChannel.size());
buffer.order(ByteOrder.LITTLE_ENDIAN);
FloatBuffer floatBuffer = buffer.asFloatBuffer();
for(int i=0,k=0;i<nrow;i=i+jump) /*jump is the value to be skipped, nrow is height of image*/
{
for(int j=0,l=0;j<ncol1;j=j+jump) //ncol is width of image
{
index=(i*ncol)+j;
oneDimArray[(k*ncolLessRes)+l] = floatBuffer.get(index);//oneDimArray is initialised to size of Low Resolution image.
l++;
}
k++;
}
问题是决定要跳过多少列和行,即应该设置什么跳转值。
- 我尝试设置 Xmx,但图像大小不同,我们 cannot dynamically set Xmx 值。
这是一些值 -
table, th, td {
border: 1px solid black;
}
<table style="width:100%">
<tr>
<th>Image Size</th>
<th>Xmx</th>
<th>Xms</th>
<th>Problem</th>
</tr>
<tr>
<td>83Mb</td>
<td>512m</td>
<td>256m</td>
<td>working</td>
</tr>
<tr>
<td>83Mb</td>
<td>3096m</td>
<td>2048m</td>
<td>System hanged</td>
</tr>
<tr>
<td>3.84Gb</td>
<td>512m</td>
<td>256m</td>
<td>java.lang.OutOfMemoryError: Java heap space
</tr>
<tr>
<td>3.84Gb</td>
<td>3096m</td>
<td>512m</td>
<td>java.lang.OutOfMemoryError: Java heap space
</tr>
</table>
- 为此,我尝试查找分配给程序的内存:-
try(BufferedWriter bw= new BufferedWriter(new FileWriter(dtaFile,true))){
Runtime runtime=Runtime.getRuntime();
runtime.gc();
double oneMB=Math.pow(2,20);
long[] arr= Instream.range(0,(int)(10.432*long.BYTES*Math.pow(2,20))).asLongStream().toArray();
runtime.gc();
long freeMemory= runtime.freeMemory();
long totalMemory= runtime.totalMemory();
long usedMemory= totalMemory-freeMemory;
long maxMemory= runtime.maxMemory();
String fileLine= String.format(" %9.3f %9.3f %9.3f " , usedMemory/oneMb, freeMemory/oneMB, totalMemory/oneMb, maxMemory/oneMB);
bw.write();
}
获得了以下结果
Memory Allocation
这种方法失败了,因为可用内存随着我的代码的使用而增加。这样一来,我决定跳槽也没用了。
预期结果
一种在加载图像之前访问可用内存量的方法,以便我可以使用它来决定跳转的值。是否有任何其他选择来决定 jump 值(即,我可以降低多少分辨率?)。
OutOfMemoryError
这是不言自明的 - 你的记忆力不足。那不是你机器上的物理 RAM,而是 JVM 达到了 -xmx
setting 设置的内存分配上限
- 当您尝试将 3.8GB 大小的图像放入 512MB 内存块时,您的 xmx 设置测试毫无意义。它无法工作——您不能将 10 升水放入 5 升瓶中。对于内存使用,您至少需要图像 x3 的大小,因为您单独存储每个像素并且包含 3 个字节 (RGB)。这仅适用于纯图像数据。剩下的是整个应用程序和数据对象结构的开销 + 计算所需的额外 space,可能还有很多我没有提到甚至我都不知道的。
- 您不想 "dynamicly set" -xmx。将其设置为系统中的最大可能值(反复试验)。 JVM 不会占用那么多内存,除非它需要它。通过额外的 -X 设置,您可以告诉 JVM 释放未使用的内存,这样您就不必担心未使用的内存被 JVM 蜂鸣 "freezed"。
- 我从未从事过图像处理应用程序的工作。 Photoshop 或 Gimp 是否能够打开并处理如此大的图像?也许您应该寻找有关在那里处理那么多数据的线索(如果它正在工作)
- 如果以上观点只是天真,因为您出于科学目的需要它(这不是 Photoshop 或 Gimp 的用途,除非您是 flatearther :)),您将需要科学级硬件。
- 我想到的一件事根本不是将图像读入内存,而是即时处理它。这可以将内存消耗减少到兆字节的数量级。
仔细查看 ImageReader API 的建议(readTile
方法)可能只读取图像区域(例如放大)
您可以读取图像的特定部分,然后以降低的分辨率缩放它以供显示。
因此在您的情况下,您可以分块读取图像(读取图像部分就像我们逐行从数据库中读取数据一样)
例如:
// Define the portion / row size 50px or 100px
int rowHeight = 50;
int rowsToScan = imageHeight / rowHeight;
if(imageHeight % rowHeight > 0) rowsToScan++;
int x = 0;
int y = 0;
int w = imageWidth;
int h = rowHeight;
ArrayList<BufferedImage> scaledImagePortions = new ArrayList<>();
for(int i = 1; i <= rowsToScan; i++) {
// Read the portion of an image scale it
// and push the scaled version in lets say array
BufferedImage scalledPortionOfImage = this.getScaledPortionOfImage(img, x, y, w, h);
scaledImagePortions.add(scalledPortionOfImage);
y = (rowHeight * i);
}
// Create single image out of scaled images portions
可以帮助您获取图像的一部分的线程Read region from very large image file in Java
可以帮助您缩放图像的线程(我的快速搜索结果:))
how to resize Image in java?
可以帮助您合并缓冲图像的线程:Merging two images
您可以随时调整片段 :)
总结
- 我正在读取一个包含图像数据的大型二进制文件。
- 对数据进行Cumulative Count Cut分析[需要另一个与图像大小相同的数组]。
- 数据被拉伸到 0 到 255 之间存储在
BufferedImage
个像素中,以在 JPanel 上绘制图像。 - 在此图像上,使用
AffineTransform
执行缩放。
问题
小图片(<.5GB)
1.1 当我增加执行缩放的比例因子时,
点异常被抛出:-
java.lang.OutOfMemoryError: Java heap space.
下面是用于缩放的代码-
scaled = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g2d = (Graphics2D)scaled.createGraphics();
AffineTransform transformer = new AffineTransform();
transformer.scale(scaleFactor, scaleFactor);
g2d.setTransform(transformer);
- 大图(>1.5GB)
- 加载大图(>1.5GB)时,出现与 1.1,即使图片小到无法加载,有时也会出现同样的错误。
尝试过的解决方案
- 我尝试使用 BigBufferedImage 代替 BufferedImage 来存储拉伸数据。
BigBufferedImage image = BigBufferedImage.create(newCol,newRow, BufferedImage.TYPE_INT_ARGB);
但是无法传递给
g2d.drawImage(image, 0, 0, this);
因为 JPanel 的 repaint 方法由于某种原因刚刚停止。我尝试以低分辨率加载图像,其中像素被读取并且很少有列和行 jumped/skipped。但问题是如何决定随着图像大小的变化而跳过多少像素,因此我无法决定如何决定 jump 参数。
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY,0, inChannel.size());
buffer.order(ByteOrder.LITTLE_ENDIAN);
FloatBuffer floatBuffer = buffer.asFloatBuffer();
for(int i=0,k=0;i<nrow;i=i+jump) /*jump is the value to be skipped, nrow is height of image*/
{
for(int j=0,l=0;j<ncol1;j=j+jump) //ncol is width of image
{
index=(i*ncol)+j;
oneDimArray[(k*ncolLessRes)+l] = floatBuffer.get(index);//oneDimArray is initialised to size of Low Resolution image.
l++;
}
k++;
}
问题是决定要跳过多少列和行,即应该设置什么跳转值。
- 我尝试设置 Xmx,但图像大小不同,我们 cannot dynamically set Xmx 值。 这是一些值 -
table, th, td {
border: 1px solid black;
}
<table style="width:100%">
<tr>
<th>Image Size</th>
<th>Xmx</th>
<th>Xms</th>
<th>Problem</th>
</tr>
<tr>
<td>83Mb</td>
<td>512m</td>
<td>256m</td>
<td>working</td>
</tr>
<tr>
<td>83Mb</td>
<td>3096m</td>
<td>2048m</td>
<td>System hanged</td>
</tr>
<tr>
<td>3.84Gb</td>
<td>512m</td>
<td>256m</td>
<td>java.lang.OutOfMemoryError: Java heap space
</tr>
<tr>
<td>3.84Gb</td>
<td>3096m</td>
<td>512m</td>
<td>java.lang.OutOfMemoryError: Java heap space
</tr>
</table>
- 为此,我尝试查找分配给程序的内存:-
try(BufferedWriter bw= new BufferedWriter(new FileWriter(dtaFile,true))){
Runtime runtime=Runtime.getRuntime();
runtime.gc();
double oneMB=Math.pow(2,20);
long[] arr= Instream.range(0,(int)(10.432*long.BYTES*Math.pow(2,20))).asLongStream().toArray();
runtime.gc();
long freeMemory= runtime.freeMemory();
long totalMemory= runtime.totalMemory();
long usedMemory= totalMemory-freeMemory;
long maxMemory= runtime.maxMemory();
String fileLine= String.format(" %9.3f %9.3f %9.3f " , usedMemory/oneMb, freeMemory/oneMB, totalMemory/oneMb, maxMemory/oneMB);
bw.write();
}
获得了以下结果
Memory Allocation
这种方法失败了,因为可用内存随着我的代码的使用而增加。这样一来,我决定跳槽也没用了。
预期结果
一种在加载图像之前访问可用内存量的方法,以便我可以使用它来决定跳转的值。是否有任何其他选择来决定 jump 值(即,我可以降低多少分辨率?)。
OutOfMemoryError
这是不言自明的 - 你的记忆力不足。那不是你机器上的物理 RAM,而是 JVM 达到了-xmx
setting 设置的内存分配上限
- 当您尝试将 3.8GB 大小的图像放入 512MB 内存块时,您的 xmx 设置测试毫无意义。它无法工作——您不能将 10 升水放入 5 升瓶中。对于内存使用,您至少需要图像 x3 的大小,因为您单独存储每个像素并且包含 3 个字节 (RGB)。这仅适用于纯图像数据。剩下的是整个应用程序和数据对象结构的开销 + 计算所需的额外 space,可能还有很多我没有提到甚至我都不知道的。
- 您不想 "dynamicly set" -xmx。将其设置为系统中的最大可能值(反复试验)。 JVM 不会占用那么多内存,除非它需要它。通过额外的 -X 设置,您可以告诉 JVM 释放未使用的内存,这样您就不必担心未使用的内存被 JVM 蜂鸣 "freezed"。
- 我从未从事过图像处理应用程序的工作。 Photoshop 或 Gimp 是否能够打开并处理如此大的图像?也许您应该寻找有关在那里处理那么多数据的线索(如果它正在工作)
- 如果以上观点只是天真,因为您出于科学目的需要它(这不是 Photoshop 或 Gimp 的用途,除非您是 flatearther :)),您将需要科学级硬件。
- 我想到的一件事根本不是将图像读入内存,而是即时处理它。这可以将内存消耗减少到兆字节的数量级。
仔细查看 ImageReader API 的建议(readTile
方法)可能只读取图像区域(例如放大)
您可以读取图像的特定部分,然后以降低的分辨率缩放它以供显示。
因此在您的情况下,您可以分块读取图像(读取图像部分就像我们逐行从数据库中读取数据一样)
例如:
// Define the portion / row size 50px or 100px
int rowHeight = 50;
int rowsToScan = imageHeight / rowHeight;
if(imageHeight % rowHeight > 0) rowsToScan++;
int x = 0;
int y = 0;
int w = imageWidth;
int h = rowHeight;
ArrayList<BufferedImage> scaledImagePortions = new ArrayList<>();
for(int i = 1; i <= rowsToScan; i++) {
// Read the portion of an image scale it
// and push the scaled version in lets say array
BufferedImage scalledPortionOfImage = this.getScaledPortionOfImage(img, x, y, w, h);
scaledImagePortions.add(scalledPortionOfImage);
y = (rowHeight * i);
}
// Create single image out of scaled images portions
可以帮助您获取图像的一部分的线程Read region from very large image file in Java
可以帮助您缩放图像的线程(我的快速搜索结果:)) how to resize Image in java?
可以帮助您合并缓冲图像的线程:Merging two images
您可以随时调整片段 :)