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 ランタイム環境が続行するためのメモリが不足しています。
# ネイティブ メモリ割り当て (malloc) は、C:\BUILD_AREA\jdk6_34\hotspot\src\share\vm\prims\jni.cpp の jbyte に 3874172 バイトを割り当てることができませんでした
# 詳細情報を含むエラー レポート ファイルは次のように保存されます:
# [path ]
コンストラクターに以下を追加してprivate static final Vector<WeakReference<Sound>>
、クラスにa を実装しようとしました。SoundManager.Sound
// Add to the sound list.
allSounds.add(new WeakReference<SoundManager.Sound>(this));
System.out.println(allSounds.size());
これにより、プログラムの最後まで反復処理を行い、すべてのサウンドを停止することもできます (アプレットでは、これは常に自動的に行われるとは限りません)。
ただし、同じことが発生する前に、まだ約 10 回の呼び出ししか取得できませんOutOfMemoryError
。
問題がある場合は、ファイル名ごとにファイルの内容を としてキャッシュしていますbyte[]
が、これはファイルごとに 1 回しか行われないため、蓄積されません。
では、なぜこれらの参照が保持されているのでしょうか? また、ヒープ サイズを増やすだけでそれを停止するにはどうすればよいでしょうか?
編集:「詳細情報を含むエラー レポート」には、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」が以前にロードされていないことがわかります。ファイルを a に書き込み、保存します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:
「」によってロードされた「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[]
。
フィルのコメントごとに編集:
私はこれを変更しました:
// 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();
}
}
}
クリップが終了または停止するたびに (つまり、開始後 1 秒あたり 30 回以上)、「排水中」というメッセージが表示されますが、それでも同じエラーが発生します。に置き換えdrain
てflush
も効果はありません。を使用close
すると、後で行を開くことができなくなります ( をリッスンしSTART
て呼び出しopen
ている場合でもstart
)。