Preventing a heap out of memory exception presumably due to threads
目前我正在处理我的第一次内存泄漏,我似乎无法修复它。我的程序应该通过一个名为
1 2 3 4 5 6 | Exception in thread"Thread-6" java.lang.OutOfMemoryError: Java heap space at com.sun.media.sound.DirectAudioDevice$DirectClip.open(DirectAudioDevice.java:1135) at JukeBox.play(JukeBox.java:53) at Instrument.play(Instrument.java:65) at Conductor.play(Conductor.java:78) at Game$2.run(Game.java:42) |
在我的调试器中,线程 6 是不断寻找键盘输入的两个线程之一(特别是触发声音的那个)。
这是错误后我的调试器的屏幕截图:图像来自线程的调试器
所以我很确定我的问题是由太多打开的剪辑线程引起的,但这就是我真正感到困惑的地方,因为我以为我正在关闭它们,而实际上它们看起来像只是挂在那里,等待。
这是我认为我要关闭剪辑的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
而 JukeBox 就在这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | public class JukeBox{ private AudioInputStream stream; private AudioFormat format; private DataLine.Info info; private Clip clip; private String name; /** * Constructor for objects of the class Jukebox * @param str the filename of the wave file to pla */ public JukeBox(String str){ try{ stream = AudioSystem.getAudioInputStream(new File(str)); name = str; format = stream.getFormat(); info = new DataLine.Info(Clip.class, format); clip = (Clip) AudioSystem.getLine(info); } catch(Exception E){ System.out.println(E); } } /** * Returns the clip's length in microseconds. * @return the clip's length in microseconds. */ public long getMicrosecondLength(){return clip.getMicrosecondLength();} /** * Plays the music on its own thread, enabling the game to run while music plays */ public void play(){ try { clip.open(stream); clip.start(); } catch (Exception e) { System.out.println(e); } } /** * Ends the music and it's thread as well */ public void killTheTunes(){ try{stream.close();}catch (Exception e) { System.out.println(e); } clip.close(); } /** * Returns the filename the jukebox was initially set to play * @return the filename the jukebox was initially set to play */ public String toString(){ return name; } } |
我不确定为什么我仍然遇到堆内存问题。谢谢
编辑:
我的代码结构有点复杂,所以我会试着解释一下。运行游戏的主要方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | display = new Display(); copper = new Conductor(); display.main(); //sets up all the jframe things and graphics Thread p1 = new Thread() { public void run() { while(true){ if(display.panel.playerClicked(1)){ display.panel.p1 = Color.black; display.panel.p1 = copper.play(4,copper.determinePitch(display.panel.getY(1))); display.panel.pause(250); display.panel.p1 = Color.black; } } } }; Thread p2 = new Thread() { public void run() { while(true){ if(display.panel.playerClicked(2)){ display.panel.p2 = Color.black; display.panel.p2 = copper.play(0,copper.determinePitch(display.panel.getY(2))); display.panel.pause(250); display.panel.p2 = Color.black; } } } }; p1.start(); p2.start(); |
这里是 Canvas 类,它检测关键动作。 Display 基本上只是初始化一个 JFrame 并为其添加一个 Canvas。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import javax.swing.JPanel; import java.util.ArrayList; import java.awt.image.BufferedImage; import java.io.*; import javax.imageio.ImageIO; import java.awt.Image; import javax.swing.JLabel; import javax.swing.ImageIcon; public class Canvas extends JPanel { // attributes private Rectangle player1; private Rectangle player2; public final int x = 800 + 200; public final int y = 1100/2 + 200; private boolean[] player1bools = new boolean[5]; private boolean[] player2bools = new boolean[5]; BufferedImage dimg; Color p1 = Color.black; //these are intentionally public so game can change them Color p2 = Color.black; // constructor public Canvas() { // initialize object player1 = new Rectangle(250, 50, 50, 50); player2 = new Rectangle(50, 50, 50, 50); // set canavs background colour // add the key listener in the constructor of your canavas/panel addKeyListener(new myKeyListener()); BufferedImage img = null; try { img = ImageIO.read(new File("Pencils.jpg")); } catch (IOException e) {} dimg = resize(img, x, y-20); // ensure focus is on this canavas/panel for key operations. setFocusable(true); setBackground(new Color(0,0,0,0)); setOpaque(true); Thread gameLoop = new Thread() { public void run() { while(true){ updatePlayers(); repaint(); pause(20); } } }; gameLoop.start(); } public int getY(int i){ if(i == 1){return player1.y;} if(i == 2){return player2.y;} return -1; } public boolean isOptimizedDrawingEnabled(){return false;} /** * This method was made by David Kroukamp on * http://stackoverflow.com/questions/14548808/scale-the-imageicon-automatically-to-label-size */ public static BufferedImage resize(BufferedImage image, int width, int height) { BufferedImage bi = new BufferedImage(width, height, BufferedImage.TRANSLUCENT); Graphics2D g2d = (Graphics2D) bi.createGraphics(); g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)); g2d.drawImage(image, 0, 0, width, height, null); g2d.dispose(); return bi; } private void updatePlayers() { if (player1bools[0]) { moveX(-5, player1); } if (player1bools[2]) { moveX(5, player1); } if (player1bools[1]) { moveY(-5, player1); } if (player1bools[3]) { moveY(5, player1); } if (player2bools[0]) { moveX(-5, player2); } if (player2bools[2]) { moveX(5, player2); } if (player2bools[1]) { moveY(-5, player2); } if (player2bools[3]) { moveY(5, player2); } } // painting public void paintComponent(Graphics graphics) { super.paintComponent(graphics); graphics.clearRect(0, 0, getWidth(), getHeight()); //clears the trails graphics.drawImage(dimg,0,0,null); Graphics2D graphics2d = (Graphics2D) graphics; graphics.setColor(p1); graphics2d.fill(player1); graphics.setColor(p2); graphics2d.fill(player2); // */ } // function which essentially re-creates rectangle with varying x // orientations. (x-movement) public void moveX(int mutationDistance, Rectangle sampleObject) { //I don't want horizontal movement to work /* sampleObject.setBounds(sampleObject.x + mutationDistance, sampleObject.y, sampleObject.width, sampleObject.height); */ } // function which essentially re-creates rectangle with varying y // orientations. (y-movement) public void moveY(int mutationDistance, Rectangle sampleObject) { if(sampleObject.y < 0) { sampleObject.y = 0; } else if(sampleObject.y > 670) { sampleObject.y = 670; } sampleObject.setBounds(sampleObject.x, sampleObject.y + mutationDistance, sampleObject.width, sampleObject.height); } public boolean playerClicked(int i){ if(i == 1){return player1bools[4];} if(i == 2){return player2bools[4];} return false; } // listener private class myKeyListener implements KeyListener { // implement all the possible actions on keys public void keyPressed(final KeyEvent keyEvent) { if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) { System.exit(0); } if (keyEvent.getKeyCode() == KeyEvent.VK_RIGHT) { player1bools[2] = true; } if (keyEvent.getKeyCode() == KeyEvent.VK_LEFT) { player1bools[0] = true; } if (keyEvent.getKeyCode() == KeyEvent.VK_UP) { player1bools[1] = true; } if (keyEvent.getKeyCode() == KeyEvent.VK_DOWN) { player1bools[3] = true; } if (keyEvent.getKeyCode() == KeyEvent.VK_SLASH) { player1bools[4] = true; } if (keyEvent.getKeyCode() == KeyEvent.VK_D) { player2bools[2] = true; } if (keyEvent.getKeyCode() == KeyEvent.VK_A) { player2bools[0] = true; } if (keyEvent.getKeyCode() == KeyEvent.VK_W) { player2bools[1] = true; } if (keyEvent.getKeyCode() == KeyEvent.VK_S) { player2bools[3] = true; } if (keyEvent.getKeyCode() == KeyEvent.VK_F) { player2bools[4] = true; } } public void keyReleased(KeyEvent keyEvent) { if (keyEvent.getKeyCode() == KeyEvent.VK_RIGHT) { player1bools[2] = false; } if (keyEvent.getKeyCode() == KeyEvent.VK_LEFT) { player1bools[0] = false; } if (keyEvent.getKeyCode() == KeyEvent.VK_UP) { player1bools[1] = false; } if (keyEvent.getKeyCode() == KeyEvent.VK_DOWN) { player1bools[3] = false; } if (keyEvent.getKeyCode() == KeyEvent.VK_SLASH) { player1bools[4] = false; } if (keyEvent.getKeyCode() == KeyEvent.VK_D) { player2bools[2] = false; } if (keyEvent.getKeyCode() == KeyEvent.VK_A) { player2bools[0] = false; } if (keyEvent.getKeyCode() == KeyEvent.VK_W) { player2bools[1] = false; } if (keyEvent.getKeyCode() == KeyEvent.VK_S) { player2bools[3] = false; } if (keyEvent.getKeyCode() == KeyEvent.VK_F) { player2bools[4] = false; } } public void keyTyped(KeyEvent keyEvent) { } } public static void pause(int secs) { try { Thread.sleep(secs); } catch (Exception e) {} } } |
最后是指挥这里管理仪器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | public class Conductor { private ArrayList<Instrument> symphony; /** * Constructor for objects of class Conductor, initializes the instruments */ public Conductor() { ArrayList<String> pianoList = new ArrayList<String>(); for(int i = 1; i <= 11; i++){ pianoList.add("piano"+i); } ArrayList<String> bassList = new ArrayList<String>(); for(int i = 1; i <= 11; i++){ bassList.add("bass"+i); } ArrayList<String> guitarList = new ArrayList<String>(); for(int i = 1; i <= 11; i++){ guitarList.add("guitar"+i); } ArrayList<String> percList = new ArrayList<String>(); for(int i = 1; i <= 11; i++){ percList.add("perc"+i); } ArrayList<String> vibeList = new ArrayList<String>(); for(int i = 1; i <= 11; i++){ vibeList.add("vibe"+i); } Instrument piano = new Instrument(pianoList, new Color(19,130,206)); Instrument bass = new Instrument(bassList, new Color(100,83,38)); Instrument guitar = new Instrument(guitarList, new Color(244,184,16)); Instrument perc = new Instrument(percList, new Color(24,57,165)); Instrument vibe = new Instrument(vibeList, new Color(57,24,135)); symphony = new ArrayList<Instrument>(); symphony.add(piano); symphony.add(bass); symphony.add(guitar); symphony.add(perc); symphony.add(vibe); } public static int determinePitch(int i){ if(i == -1){return i;} if(i>600){return 1;} if(i>535){return 2;} if(i>470){return 3;} if(i>410){return 4;} //these numbers correspond to the approximate heights of the colors in the background image if(i>345){return 5;} if(i>275){return 6;} if(i>215){return 7;} if(i>155){return 8;} if(i>90){return 9;} if(i>25){return 10;} return 11; } /** * Calls an instrument and makes it play a noise * @param inst the instrument index to play * @param pitch the pitch for the instrument to play */ public Color play(int inst, int pitch){ symphony.get(inst).play(pitch); return symphony.get(inst).getColor(); } } |
所以对于结构的整体运行:
控制播放器的线程检测它们各自的播放器是否按下了触发键。如果是这样,他们会打电话给指挥演奏乐器。它们还会改变该玩家矩形的颜色。当指挥演奏乐器时,它使用 Instrument\\ 的 play 方法,该方法调用该乐器的 JukeBox 方法,该方法使用导致崩溃的剪辑。
Thread.sleep() 以毫秒为参数,因此行:
1 |
应该是:
1 |
您的代码中可能还存在其他问题,但目前,您永远无法到达
所以我解决了我的问题。它有一些优点和缺点,但总的来说它有效。这就是我所做的。
我在 Instrument 中改变了我的播放方法,所以它不是每次都创建一个新的 JukeBox,而是 Instrument 有一个 JukeBox[] 来保存 JukeBox。原来是因为只能播放一次,后来发现是因为要把Clip的读取帧移回0。所以现在播放方法很简单,就是这样:
1 2 3 4 | public void play(int pitch){ sounds[pitch-1].clip.setFramePosition(0); sounds[pitch-1].play(); } |
其中sounds 是一个保存噪音的JukeBox[]。
它有点滞后,并且顺序声音有点剪辑。为了解决这个问题,我采用了让玩家矩形移动得更慢的老生常谈的方法,加上增加游戏的暂停时间,让更少的投球可以让玩家更快,给他们关闭的时间。从好的方面来说,没有更多的内存泄漏。