【发布时间】:2013-04-01 03:26:32
【问题描述】:
我有一个SoundManager 类,用于轻松管理声音。本质上:
public class SoundManager {
public static class Sound {
private Clip clip; // for internal use
public void stop() {...}
public void start() {...}
public void volume(float) {...}
// etc.
}
public Sound get(String filename) {
// Gets a Sound for the given clip
}
// moar stuff
}
this的大部分用途如下:
sounds.get("zap.wav").start();
据我了解,这应该不在内存中保留对新创建声音的引用,并且应该相当快地对它进行垃圾收集。但是,对于一个简短的声音文件(108 KB,以惊人的 00:00:00 秒打卡,实际上大约 0.8 秒),我只能进行大约 2100 次调用,然后才能收到OutOfMemoryError:
# 内存不足,Java 运行时环境无法继续。
# 本机内存分配(malloc)未能为 C:\BUILD_AREA\jdk6_34\hotspot\src\share\vm\prims\jni.cpp 中的 jbyte 分配 3874172 字节
# 包含更多信息的错误报告文件保存为:
# [路径]
我尝试在SoundManager.Sound 类中实现private static final Vector<WeakReference<Sound>>,将以下内容添加到构造函数中:
// Add to the sound list.
allSounds.add(new WeakReference<SoundManager.Sound>(this));
System.out.println(allSounds.size());
这也允许我在程序结束时进行迭代并停止所有声音(在小程序中,这并不总是自动完成)。
但是,在同样的OutOfMemoryError 发生之前,我仍然只得到了大约 10 次调用。
如果重要的话,对于每个文件名,我会将文件内容缓存为byte[],但每个文件只执行一次,因此不应累积。
那么为什么要保留这些引用,以及如何在不增加堆大小的情况下阻止它?
编辑:“包含更多信息的错误报告”在第 32 行包含:
Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
J com.sun.media.sound.DirectAudioDevice.nWrite(J[BIIIFF)I
J com.sun.media.sound.DirectAudioDevice$DirectDL.write([BII)I
j com.sun.media.sound.DirectAudioDevice$DirectClip.run()V+163
j java.lang.Thread.run()V+11
v ~StubRoutines::call_stub
这是否意味着这个问题完全不受我的控制? javasound 需要时间“冷却”吗?出于调试目的,我以 300/秒的速度喷出这些声音。
编辑更多关于我使用 JavaSound 的信息。
我第一次调用sounds.get("zap.wav"),它看到之前没有加载“zap.wav”。它将文件写入byte[] 并存储它。然后它就像之前被缓存一样继续进行。
第一次和所有后续时间(缓存后),该方法获取存储在内存中的byte[],创建一个新的ByteArrayInputStream,并在所述流上使用AudioSystem.getAudioInputStream(bais)。难道是这些流持有内存?我认为当Sound(以及Clip)被收集时,流也会被关闭。
EDIT 每个请求使用 get 方法。这是public Sound get(String name)。
-
byteCache是HashMap<String, byte[]> -
clazz是Class<?>
byteCache 是 HashMap<String, byte[]> 和 clazz 是 Class<?>
try {
// Create a clip.
Clip clip = AudioSystem.getClip();
// Find the full name.
final String fullPath = prefix + name;
// See what we have already.
byte[] theseBytes = byteCache.get(fullPath);
// Have we found the bytes yet?
if (theseBytes == null) {
// Nope. Read it in.
InputStream is = clazz.getResourceAsStream(fullPath);
// Credit for this goes to Evgeniy Dorofeev:
// http://stackoverflow.com/a/15725969/732016
// Output to a temporary stream.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Loop.
for (int b; (b = is.read()) != -1;) {
// Write it.
baos.write(b);
}
// Close the input stream now.
is.close();
// Create a byte array.
theseBytes = baos.toByteArray();
// Put in map for later reference.
byteCache.put(fullPath, theseBytes);
}
// Get a BAIS.
ByteArrayInputStream bais = new ByteArrayInputStream(theseBytes);
// Convert to an audio stream.
AudioInputStream ais = AudioSystem.getAudioInputStream(bais);
// Open the clip.
clip.open(ais);
// Create a new Sound and return it.
return new Sound(clip);
} catch (Exception e) {
// If they're watching, let them know.
e.printStackTrace();
// Nothing to do here.
return null;
}
EDIT 在堆分析之后。
在崩溃前大约 5 秒进行了堆转储。这说明了:
问题嫌疑人#1:
“com.sun.media.sound.DirectAudioDevice$DirectClip”的 2,062 个实例, 由 "" 加载占用 230,207,264 (93.19%) 个字节。
关键字 com.sun.media.sound.DirectAudioDevice$DirectClip
这些Clip 对象被Sound 对象强引用,但Sound 对象仅在Vector<WeakReference<Sound>> 中被弱引用。
我还可以看到每个Clip 对象都包含byte[] 的副本。
编辑每个菲尔的 cmets:
我已经改变了这个:
// Convert to an audio stream.
AudioInputStream ais = AudioSystem.getAudioInputStream(bais);
// Open the clip.
clip.open(ais);
到这里:
// Convert to an audio stream.
AudioInputStream ais = AudioSystem.getAudioInputStream(bais);
// Close the stream to prevent a memory leak.
ais.close();
// Open the clip.
clip.open(ais);
clip.close();
这会修复错误,但不会播放任何声音。
如果我省略clip.close(),错误仍然会发生。如果我将ais.close() 移动到clip.open 之后,错误仍然会发生。
我还尝试在创建剪辑时添加LineListener:
@Override
public void update(LineEvent le) {
if (le.getType() == LineEvent.Type.STOP) {
if (le.getLine() instanceof Clip) {
System.out.println("draining");
((Clip)le.getLine()).drain();
}
}
}
每次剪辑结束或停止时(即开始发生后 30 多次/秒),我都会收到一条“正在耗尽”的消息,但仍然收到相同的错误。将drain 替换为flush 也无效。使用close 会使线路稍后无法打开(即使在监听START 并调用open 和start 时)。
【问题讨论】:
-
在我看来,您正在使用一些 JNI 库,该库正在占用大量资源。
-
那会是 javasound 问题吗?我所做的一切都是香草。
-
我对 JavaSound 一无所知。但是您可能会以某种方式“滥用”它,例如不重置某些内容等。
-
OutOfMemoryError 在这种情况下意味着与堆栈相关的东西。你不是在某个地方隐藏了某种循环调用吗?
-
而对于流,你永远不应该在内存中存储那种东西,你输入流来读取需要的内容。能不能展示一下get方法的内容?
标签: java garbage-collection out-of-memory javasound weak-references