防止堆内存异常可能是由于线程造成的

时间:2016-05-15 03:09:01

标签: java multithreading memory memory-leaks heap-memory

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

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();
}

在我的调试器中,线程6是不断寻找键盘输入的两个线程之一(特别是触发声音的线程)。

以下是错误后我的调试器的屏幕截图:image from debugger of threads

所以我很确定我的问题是由于有太多的开放剪辑线程引起的,但那是我真的很困惑的地方,因为我以为我正在关闭它们,而实际上它们看起来就像是挂在那里,等候。

这是我的代码,我认为我正在关闭剪辑:

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;
    }
}

和JukeBox在这里:

    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();

我不知道为什么我仍然遇到堆内存问题。谢谢

编辑:

我的代码结构有点复杂,所以我会尝试解释它。运行游戏的主要方法如下:

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) {}
}
}

这是Canvas类,可以检测关键动作。显示基本上只是初始化一个JFrame并向其添加一个Canvas。

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();
}
}

最后有导体在这里管理乐器:

class Book:
    title = ''
    pages = 0

    def __init__(self, title='', pages=0):
        self.title = title
        self.pages = pages

    def __str__(self):
        return self.title

    def __gt__(self, other):
        return self.pages > other

    def __ge__(self, other):
        return self.pages >= other

因此,对于结构的整体下降:

控制玩家的线程检测他们各自的玩家是否击中了触发键。如果是这样,他们会打电话给售票员演奏乐器。它们还会改变该玩家矩形的颜色。当指挥播放乐器时,它使用乐器的播放方法,该方法调用乐器的JukeBox方法,该方法使用导致崩溃的剪辑。

2 个答案:

答案 0 :(得分:3)

Thread.sleep()以毫秒为参数,所以行:

Thread.sleep(jukez.getMicrosecondLength()*1000);

应该是:

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

您的代码中可能还有其他问题,但目前,由于您等待的时间太长,您永远无法进入killTheTunez()来电。

答案 1 :(得分:0)

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

我在Instrument中改变了我的play方法,所以每次都没有创建一个新的JukeBox,而是Instrument有一个JukeBox []持有JukeBoxes。我最初正在重新创建它们,因为我只能播放一次,但我发现这是因为我必须将Clip的读取帧移回0.所以现在play方法很简单,就是这样:

    public void play(int pitch){
    sounds[pitch-1].clip.setFramePosition(0);
    sounds[pitch-1].play();
}

声音是JukeBox []持有声音。

有点滞后,声音顺序有点剪裁。为了解决这个问题,我采取了让玩家长方形移动得更慢的hacky方法,加上游戏的暂停时间,因此玩家的投球速度可以更快,让他们有时间关闭。从好的方面来说,没有更多的内存泄漏。