弱引用和`OutOfMemoryError`s
我有一个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.8s),在得到OutOfMemoryError
之前,我只能获得大约2100个调用:
#没有足够的内存让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());
这也允许我在程序结束时迭代并停止所有声音(在applet中,这并不总是自动完成的)。
但是,在发生相同的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
)被收集时,流也将被关闭。
用每个请求的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:
由“”加载的2,062个“com.sun.media.sound.DirectAudioDevice $ DirectClip”实例占用230,207,264(93.19%)个字节。
关键字com.sun.media.sound.DirectAudioDevice $ DirectClip
这些Clip
对象被Sound
对象强烈引用,但Sound
对象仅在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次以上),我会收到一条“耗尽”消息,但仍然会得到相同的错误。 用flush
代替drain
也没有效果。 使用close
可以使线路稍后不能打开(即使在侦听START
并呼叫open
和start
)。
我怀疑问题是你没有明确关闭音频流。 你不应该依靠垃圾收集器来关闭它们。
在本地分配中,分配看起来没有在正常的Java分配中失败,我怀疑“GC在抛出OOME之前运行”的正常行为适用于这种情况。
无论哪种方式,最好的做法是明确地关闭流( finally
使用或使用Java 7 try
资源)。 这适用于涉及外部资源或堆内存缓冲区的任何类型的流。
请原谅,如果这是完全关闭的,但我想检查两个基本原理,我无法从您的代码中随意阅读。
1)你的声音文件有多久? 给定特定数量的文件,以毫秒为单位的长度,采样率和编码(例如,16位,立体声),您应该能够计算预期消耗的内存量。 这个数额是多少?
2)剪辑真正常见的错误是每次播放它们时重新创建它们,而不是重新使用现有的剪辑。 我在评论中看到:“sounds.get(”zap.wav“)。start()”,这让我想知道你是否犯了这个基本错误。 您应该只制作一个剪辑一次,然后在想要再次播放时将帧位置重置为0。 如果您以极高的速率重新创建剪辑,则会很快填满内存,因为每次播放都会使用自己的PCM数据副本创建一个附加对象。
另外,正如一位评论者所说,关闭各种流是非常重要的。 不这样做会导致内存泄漏。
另一种方法:放弃使用Java的剪辑并编写自己的。 我做到了。 我创建了一个将数据存储在内存中的对象,以及另外两个为存储器提供“光标”的对象,以用于两种不同类型的播放。 一个用于循环播放,另一个用于重叠播放。 两者都可以设置为以不同的播放速度运行,因此您可以通过加快或减慢播放速度来获得不同的效果。 两者都将它们的输出指向我写的混音器,其中数据被合并到一个SourceDataLine中。
代码在这里介绍,包括在第一篇文章中链接的jar中:http://www.java-gaming.org/topics/simple-audio-mixer-2nd-pass/27943/view.html
我期待着重新开始工作,可能会把它放在GitHub上。
此外,TinySound是一个非常有能力的声音管理器。 你可以在这里了解它。 这种方法与我所做的非常相似,混合到一个输出。 TinySound为Ogg等提供支持。 我不认为它提供了变速播放。
http://www.java-gaming.org/topics/need-a-really-simple-library-for-playing-sounds-and-music-try-tinysound/25974/view.html
链接地址: http://www.djcxy.com/p/12625.html