• ベストアンサー
※ ChatGPTを利用し、要約された質問です(原文:メモリやCPU使用率が高すぎる?)

メモリやCPU使用率が高すぎる?

このQ&Aのポイント
  • Java 1.6で動作するアナログ時計のようなものを作っていますが、メモリやCPUの使用率が常駐に向かない程度まで高くなってしまいます。
  • 具体的には、メモリが100MB前後、CPUが10%前後で安定しています。Intel Core i7 の10%はかなりの割合です。
  • ソースが全てではないので、大きな間違いや代替案等があれば教えていただきたいです。

質問者が選んだベストアンサー

  • ベストアンサー
  • mpro-gram
  • ベストアンサー率74% (170/228)
回答No.4

私の考えつく単純な構成とは、かなり違うようです。 とりあえず、試したのは、クラス一つで構成しちゃいました。 時計描画用 JPanel に 時計背景、3つの針用にBufferedImageを保持して、このクラスをRunnableとして、描画専用にする。 このクラスには各画像の入れ替え用に setterメソッドも作っておく。あ、画像変更中にrepaint が行われないように、invokeLater しておくほうが無難なのか。 アプリケーション全体の方には、Menubar などのボタンに、画像切り替えなどのイベントを登録して、時計描画用クラスのsetterメソッドを呼ぶ。 時計のスタートは、時計描画用クラスの start()を呼んで開始、でもスレッドまわりは別クラスでスレッドを作って、このクラスをinvokeLaterの引数にセットするのでもメモリ消費や実行時間に影響はなさそうにみえました。 メモリ消費は保持する画像にもよるだろうけど、以下のようなクラスだと、そんなに、CPU時間は取らないみたいなので、どこで時間かかってるのかな?と思った次第です。 // import 文省略 public class AnalogClock extends JPanel implements Runnable{ private BufferedImage dial, hourNeedle, minNeedle, secNeedle; private java.awt.geom.AffineTransform af = new java.awt.geom.AffineTransform(); private int w,h,ox,oy, secX,minX,hourX, secY,minY,hourY; /* 回転中心変更用データを計算済みにして保持 */ public AnalogClock(){ /* default 画像作成または取り込み */ w = dial.getWidth(); h= dial.getHeight(); ox = w/2; oy = h/2; secX = secNeedle.getWidth() /2; secY = secNeedle.getHeight() /12; // hour,min も同様に } public void start(){ while( true ){ try{Thread.sleep(sleepTime);}catch(InterruptedException ie){} javax.swing.SwingUtilities.invokeLater(this); } } public void run(){ repaint(); } public void paintComponent(Graphics g){ super.paintComponent(g); g.drawImage(dial, 0,0, this); Graphics2D g2 = (Graphics2D)g; drawN(g2); } void drawN(Graphics2D g2){ java.util.Calendar rightNow = java.util.Calendar.getInstance(); /* 各時刻ごとに 新規取得 以下3つの針描画 */ double s = (double)rightNow.get(java.util.Calendar.SECOND) + rightNow.get(java.util.Calendar.MILLISECOND)/1000.0; double angle= s*6.0 -180 ; /* (角度の算出処理) 360*(sec/60) */ drawR(g2,secNeedle,angle,secX,secY); /* 同様に、分針、時針をdraw */ } void drawR(Graphics2D g2, BufferedImage img, double angle , int dx,int dy){ af.setToRotation(Math.toRadians(angle), dx, dy); /* dx,dy 中心に回転 */ double[] flat = new double[6]; af.getMatrix(flat); af.setTransform(flat[0], flat[1], flat[2], flat[3], flat[4]+ox-dx, flat[5]+oy-dy); g2.drawImage(img, af, this); } public void setSecNeedle(java.awt.image.BufferedImage img){ /* secNeedle に適宜描画し secX,secY を再計算 */ } /* 同様に長針短針文字盤用setterメソッド作成 */ }

django13
質問者

お礼

針ごとにオブジェクトを分けていない点やtranslate()を使っていない点を除けばおおまかな流れは極端にかけ離れてもいないようですね。 ためしに現状のソースから秒針以外のオブジェクトを取り除いて実質1クラスで動いているような状態にしてみましたがさほど大きな変化は見られないようでした。 他に負荷をかける要因として考えられるのは、バイリニアによる補間、倍率を指定しての描画、AWTUtilities.setWindowOpacity()とsetOpacity()によって非矩形ウィンドウにしている点ですが、外すわけにはいかない機能です。 他の機能といえばXMLから起動時の表示位置を読み出していることやコンテキストメニュー、タスクトレイアイコンくらいですがループ外にあるものなのでここでは省略しています。 C#で全く同じ機能を持つものを作ると1/6程度の負荷まで抑え込めたので結局はネイティブに近いフレームワークで作るしか無いのかもしれません。 Win/Mac対応が目的だったので残念ですがObjective-Cを勉強してみることにします。ありがとうございました。

その他の回答 (3)

  • mpro-gram
  • ベストアンサー率74% (170/228)
回答No.3

paintComponent() メソッド内部で、文字盤部分も毎回全部計算して描き直してると、1描画ごとに時間を喰う。それで、わざわざinvokeLaterなどしてるのだろうが、文字盤は変化しないのだから、BufferedImage に文字盤を作ってprivate propatyで保持しておいて、それを、paintComponentでは、drawImage1回で ボンとJPanelにコピーして上に針だけ書き直しするだけなら、各runにおける所用時間はかなり短縮すると思われる。メモリ的には、バッファー画像の分増えるかもだけど、他のところのobject多重生成が不要になって相殺されるんじゃないかな。 [Javaパフォーマンスチューニング:マルチスレッドによるリソース競合] の記事も読んで見られることをお薦めします。 http://www.atmarkit.co.jp/ait/articles/0505/14/news015.html  記事の日付は古いが、考慮すべきポイントはそう変わらないと思う。

django13
質問者

お礼

回答ありがとうございます。 文字盤自体は現状BufferedImageとしてプロパティに読み込んだPNGファイルをdrawImageで表示する構造を取っています。 ClockItem dial = new ClockItem(); // 部品用のクラス dial.loadImage("dial.png"); // 画像の読み込み dial.type = "dial"; // 役割の指定 針と盤面には共通のクラスにしてあり、描画時にtypeによって文字盤か長針、短針などを区別して、回転させるかそのままかを分けています。 g2d = (Graphics2D)g; dial.draw(g2d); // 役割に応じてBufferedImageをdrawImageします shorthand.draw(g2d); longhand.draw(g2d); secondhand.draw(g2d); 背景となる文字盤だけを一回のdrawImageで済ませるには背景用のJPanelに針用のJPanelを重ねたりするのでしょうか?

  • teketon
  • ベストアンサー率65% (141/215)
回答No.2

No.1の方も言ってるのに加えて >Graphics2D g2d = (Graphics2D) g.create(); この処理も初回だけでいいのでは?

django13
質問者

お礼

回答ありがとうございます。 create()自体をやめてGraphics2Dへのキャストするだけにして針側の処理でもそのGraphics2Dを再利用する形に変えてみました。フットプリントが100~300KB前後ですが減少したようです。 グラフ化してみると最初の2分ほどでピークに達し、徐々に減少して10分ほどでほぼ水平になりそれ以降はずっと水平です。2つの修正を施す前のソースでも同じようなグラフになりました。 青い線がヒープ使用量で、赤い線がヒープ合計です。 http://kie.nu/1_2A freeMemory()で得られるJava内のメモリは小さくなっているにもかかわらずWindowsやMacのリソースモニターで見るメモリ使用量は100MB前後まで増え続けてしまいます。 VMが使用している部分もあるとは思いますが、このようなものなのでしょうか?

  • hitomura
  • ベストアンサー率48% (325/664)
回答No.1

ざっと見ですが、 while(true){ try{Thread.sleep(sleepTime);}catch(InterruptedException ie){} SwingUtilities.invokeLater(new Runnable(){ public void run(){ paintPane.repaint(); } }); } で、while 中で毎回匿名インスタンスを生成しているのは生成・破棄の手間がかかるように見えます。 Runnable r = new Runnable(){ public void run(){ paintPane.repaint(); } }; while(true){ try{Thread.sleep(sleepTime);}catch(InterruptedException ie){} SwingUtilities.invokeLater(r); } という風にループ外に出したらわずかながら改善するかもしれません。

django13
質問者

お礼

回答有難うございます。 劇的な改善とまでは行きませんでしたが、確かに無駄のある書き方をしていたので教えていただいたように修正しました。

関連するQ&A