我有一个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
}
它的大部分用途如下:
sounds.get("zap.wav").start();
据我了解,这不应该在内存中保留对新创建声音的引用,并且应该很快地对它进行垃圾收集。但是,对于一个简短的声音文件(108 KB,以惊人的 00:00:00 秒打卡,实际上大约 0.8 秒),我只能进行大约 2100 次调用,然后才能获得OutOfMemoryError
:
# Java 运行时环境没有足够的内存来继续。
# Native memory allocation (malloc) failed to allocate 3874172 bytes for jbyte in C:\BUILD_AREA\jdk6_34\hotspot\src\share\vm\prims\jni.cpp
# 包含更多信息的错误报告文件保存为:
# [path ]
我尝试private static final Vector<WeakReference<Sound>>
在SoundManager.Sound
类中实现 a ,将以下内容添加到构造函数中:
// Add to the sound list.
allSounds.add(new WeakReference<SoundManager.Sound>(this));
System.out.println(allSounds.size());
这也允许我在程序结束时迭代并停止所有声音(在小程序中,这并不总是自动完成)。
但是,在同样的情况发生之前,我仍然只得到了大约 10 次调用OutOfMemoryError
。
如果重要的话,对于每个文件名,我将文件内容缓存为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”之前没有加载过。它将文件写入 abyte[]
并存储它。然后它继续进行,就好像它以前被缓存过一样。
第一次和所有后续时间(在缓存之后),该方法将byte[]
存储在内存中,创建一个新的ByteArrayInputStream
,并AudioSystem.getAudioInputStream(bais)
在所述流上使用。难道是这些流持有内存?我认为当Sound
(以及因此Clip
)被收集时,流也会被关闭。
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;
}
堆分析后编辑。
在崩溃前大约 5 秒进行了堆转储。好吧,这说明了:
问题嫌疑人#1:
由“”加载的“com.sun.media.sound.DirectAudioDevice$DirectClip”的2062个实例占用了230207264(93.19%)字节。
关键词 com.sun.media.sound.DirectAudioDevice$DirectClip
这些Clip
对象被对象强引用,Sound
但Sound
对象仅在 a 中被弱引用Vector<WeakReference<Sound>>
。
我还可以看到每个Clip
对象都包含byte[]
.
根据菲尔的评论编辑:
我改变了这个:
// 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
也没有效果。Usingclose
使线路稍后无法打开(即使在监听START
和调用open
and时start
)。