問題は次のとおりです。
ご覧のとおり、メモリ使用量が制御不能に膨れ上がります! 何が起こっているのかを把握している間にメモリ不足エラーを回避するためだけに、JVM に引数を追加してヒープサイズを増やす必要がありました。良くない!
基本的なアプリケーションの概要 (コンテキスト用)
このアプリケーションは、(最終的には)自動化の目的で、基本的な画面上の CV とテンプレート マッチング タイプのものに使用される予定です。画面を見るためにできるだけ高いフレームレートを達成し、一連の個別の消費者スレッドを介してすべての処理を処理したいと考えています。
標準の Robot クラスはスピードに関しては本当にひどいことがすぐにわかったので、ソースを開き、重複した労力と無駄なオーバーヘッドをすべて取り除き、FastRobot という名前の独自のクラスとして再構築しました。
クラスのコード:
public class FastRobot {
private Rectangle screenRect;
private GraphicsDevice screen;
private final Toolkit toolkit;
private final Robot elRoboto;
private final RobotPeer peer;
private final Point gdloc;
private final DirectColorModel screenCapCM;
private final int[] bandmasks;
public FastRobot() throws HeadlessException, AWTException {
this.screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
this.screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
toolkit = Toolkit.getDefaultToolkit();
elRoboto = new Robot();
peer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);
gdloc = screen.getDefaultConfiguration().getBounds().getLocation();
this.screenRect.translate(gdloc.x, gdloc.y);
screenCapCM = new DirectColorModel(24,
/* red mask */ 0x00FF0000,
/* green mask */ 0x0000FF00,
/* blue mask */ 0x000000FF);
bandmasks = new int[3];
bandmasks[0] = screenCapCM.getRedMask();
bandmasks[1] = screenCapCM.getGreenMask();
bandmasks[2] = screenCapCM.getBlueMask();
Toolkit.getDefaultToolkit().sync();
}
public void autoResetGraphicsEnv() {
this.screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
this.screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
}
public void manuallySetGraphicsEnv(Rectangle screenRect, GraphicsDevice screen) {
this.screenRect = screenRect;
this.screen = screen;
}
public BufferedImage createBufferedScreenCapture(int pixels[]) throws HeadlessException, AWTException {
// BufferedImage image;
DataBufferInt buffer;
WritableRaster raster;
pixels = peer.getRGBPixels(screenRect);
buffer = new DataBufferInt(pixels, pixels.length);
raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
return new BufferedImage(screenCapCM, raster, false, null);
}
public int[] createArrayScreenCapture() throws HeadlessException, AWTException {
return peer.getRGBPixels(screenRect);
}
public WritableRaster createRasterScreenCapture(int pixels[]) throws HeadlessException, AWTException {
// BufferedImage image;
DataBufferInt buffer;
WritableRaster raster;
pixels = peer.getRGBPixels(screenRect);
buffer = new DataBufferInt(pixels, pixels.length);
raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
// SunWritableRaster.makeTrackable(buffer);
return raster;
}
}
本質的に、私がオリジナルから変更したのは、多くの割り当てを関数本体から移動し、それらをクラスの属性として設定して、毎回呼び出されないようにしたことだけです。これを行うと、実際にフレームレートに大きな影響がありました。電力が著しく不足しているラップトップでも、標準の Robot クラスでは最大 4 fps でしたが、FastRobot クラスでは最大 30 fps になりました。
最初のテスト:
メイン プログラムでメモリ不足エラーが発生したとき、FastRobot を監視するためにこの非常に単純なテストをセットアップしました。注: これは、上記のヒープ プロファイルを生成したコードです。
public class TestFBot {
public static void main(String[] args) {
try {
FastRobot fbot = new FastRobot();
double startTime = System.currentTimeMillis();
for (int i=0; i < 1000; i++)
fbot.createArrayScreenCapture();
System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);
} catch (AWTException e) {
e.printStackTrace();
}
}
}
調べた:
毎回これを行うわけではありませんが、これは本当に奇妙です (そしてイライラします!)。実際、上記のコードでそれを行うことはめったにありません。ただし、複数の for ループが連続している場合 、メモリの問題は簡単に再現できます。
テスト 2
public class TestFBot {
public static void main(String[] args) {
try {
FastRobot fbot = new FastRobot();
double startTime = System.currentTimeMillis();
for (int i=0; i < 1000; i++)
fbot.createArrayScreenCapture();
System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);
startTime = System.currentTimeMillis();
for (int i=0; i < 500; i++)
fbot.createArrayScreenCapture();
System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);
startTime = System.currentTimeMillis();
for (int i=0; i < 200; i++)
fbot.createArrayScreenCapture();
System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);
startTime = System.currentTimeMillis();
for (int i=0; i < 1500; i++)
fbot.createArrayScreenCapture();
System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);
} catch (AWTException e) {
e.printStackTrace();
}
}
}
調べた
制御不能なヒープは現在、約 80% の確率で再現可能です。私はプロファイラーをすべて調べましたが、最も注目すべき (私が思うに) ことは、ガベージ コレクターが 4 番目の最後のループが始まるとすぐに停止するように見えることです。
上記のコードからの出力は、次の時間を示しました。
Time taken: 24.282 //Loop1
Time taken: 11.294 //Loop2
Time taken: 7.1 //Loop3
Time taken: 70.739 //Loop4
ここで、最初の 3 つのループを合計すると 42.676 になります。これは、ガベージ コレクターが停止し、メモリが急増した正確な時間に疑わしいことに対応しています。
さて、これはプロファイリングを使った初めてのロデオです。言うまでもなく、ガベージ コレクションについて考えたのも初めてです。それは常にバックグラウンドで魔法のように機能するものでした。どちらかといえば、私は見つけました。
追加のプロフィール情報
Augusto は、メモリ プロファイルを調べることを提案しました。int[]
「到達不能だが、まだ収集されていない」とリストされているものは 1500 以上あります。これらは確かに が作成するint[]
配列ですpeer.getRGBPixels()
が、何らかの理由で破棄されていません。残念ながら、 GC がそれらを収集しない 理由がわからないため、この追加情報は私の混乱を招くだけです。
小さなヒープ引数 -Xmx256m を使用したプロファイル:
評判の悪い Hot Licks の提案で、私は最大ヒープ サイズをかなり小さい値に設定しました。これにより、メモリ使用量が1GBジャンプするのを防ぎますが、4回目の反復に入ったときにプログラムが最大ヒープサイズに膨らむ理由はまだ説明されていません。
ご覧のとおり、正確な問題はまだ存在しており、サイズが小さくなっています。;) この解決策の問題点は、何らかの理由で、プログラムがまだすべてのメモリを食い尽くしていることです。また、最初の反復から fps パフォーマンスに顕著な変化があり、メモリをほとんど消費しません。可能な限り多くのメモリを消費する最後の反復。
なぜそれが膨らんでいるの かという疑問が残ります。
「Force Garbage Collection」ボタンを押した後の結果:
jtahlborn の提案で、Force Garbage Collectionボタンを押しました。それは美しく働きました。1 GB のメモリ使用量から 60 MB 程度のベースラインまで下がります。
ということで、これで治りそうです。問題は、プログラムで GC にこれを強制するにはどうすればよいかということです。
関数のスコープにローカル ピアを追加した後の結果:
David Waters の提案でcreateArrayCapture()
、ローカルPeer
オブジェクトを保持するように関数を変更しました。
残念ながら、メモリ使用パターンに変化はありません。
3 回目または 4 回目の繰り返しではまだ巨大になります。
メモリ プールの分析:
さまざまなメモリ プールのスクリーンショット
すべてのプール:
エデンプール:
古い世代:
ほぼすべてのメモリ使用量がこのプールに収まるようです。
注: PS Survivor Space の使用量は (どうやら) 0 でした
私はいくつかの質問が残っています:
(a) Garbage Profiler グラフは、私が考えていることを意味していますか? それとも、相関関係と因果関係を混同していますか? 私が言ったように、私はこれらの問題で未知の領域にいます。
(b)ガベージ コレクタの場合...どうすればよいでしょうか.. ? 完全に停止し、プログラムの残りの部分で速度を落として実行するのはなぜですか?
(c) これを修正するにはどうすればよいですか?
何が起きてる?