java - 防止可能由于线程导致的堆内存不足异常

标签 java multithreading memory memory-leaks heap-memory

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

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

这是我的调试器出现错误后的屏幕截图:image from debugger of threads

所以我相当确定我的问题是由太多打开的剪辑线程引起的,但这就是我真正困惑的地方,因为我以为我正在关闭它们,而实际上它们看起来只是卡在那里,等待。

这是我认为要关闭剪辑的代码:

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

点唱机在这里:

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

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

编辑:

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

    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 类,它检测关键 Action 。 Display 基本上只是初始化一个 JFrame 并向其添加一个 Canvas。

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

最后还有 Conductor 来管理这里的乐器:

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() 以毫秒为参数,因此该行:

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

应该是:

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

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

关于java - 防止可能由于线程导致的堆内存不足异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37234143/

相关文章:

java - 使用 Maven 构建 Californium 失败

java - 暂停和恢复正在 hibernate 的 Java 线程

iOS 应用程序因内存限制而被杀死

memory - 0xffff0 和 BIOS

java - 如何在不提供完整路径名的情况下从 intelliJ IDEA 中的资源文件夹访问图像

java - Spring Integration - 跨步骤共享锁

java - 通过源覆盖/实现方法粘贴方法调用来覆盖 Eclipse 中的方法。为什么?

c# - 暂停使用 IOCP 的应用程序时创建的线程过多

java - 线程没有通过通知唤醒

java - 为什么 Runtime.freeMemory() 在构造对象后显示更多的内存?