关于java:Preventing a heap out of memory exception 大概是由于线程

Preventing a heap out of memory exception presumably due to threads

目前我正在处理我的第一次内存泄漏,我似乎无法修复它。我的程序应该通过一个名为 JukeBox 的类来播放 .wav 音频文件。 JukeBox 工作正常,但每次我播放新声音时,java 正在使用的内存量在任务管理器中都会增加,一旦我点击 ~316,500K,我会在 System.err 终端中收到以下错误:

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
public void play(int pitch){
    jukez = new JukeBox("pianoNote.wav");

    Thread clipkill = new Thread() {
        public void run() {
            try {
                Thread.sleep(jukez.getMicrosecondLength()*1000);
            } catch (Exception e){System.out.println(e);}
            jukez.killTheTunes();
        }
    };
    jukez.play();
    clipkill.start();
}

而 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
Thread.sleep(jukez.getMicrosecondLength()*1000);

应该是:

1
Thread.sleep(jukez.getMicrosecondLength()/1000);

您的代码中可能还存在其他问题,但目前,您永远无法到达 killTheTunez() 调用,因为您等待的时间太长了一百万次。


所以我解决了我的问题。它有一些优点和缺点,但总的来说它有效。这就是我所做的。

我在 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[]。

它有点滞后,并且顺序声音有点剪辑。为了解决这个问题,我采用了让玩家矩形移动得更慢的老生常谈的方法,加上增加游戏的暂停时间,让更少的投球可以让玩家更快,给他们关闭的时间。从好的方面来说,没有更多的内存泄漏。