【问题标题】:JavaFx and memory consumptionJavaFx 和内存消耗
【发布时间】:2014-04-10 14:01:04
【问题描述】:

我刚刚使用 java fx 创建了一个应用程序,可以循环文件夹中的视频。 我必须设置下一个视频触发 setonendofmedia 事件,所以它们处于一个循环中,但问题是应用程序在开始时加载每个视频,所以过了一会儿它会填满内存并崩溃。 是否有其他方法可以循环播放视频而不预先加载它们或每隔一段时间刷新内存?

这是我的代码:

 package application;


    import java.io.File;
    import java.io.FilenameFilter;
    import java.util.ArrayList;
    import java.util.List;

    import javax.swing.JOptionPane;

    import javafx.application.Platform;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.layout.StackPane;
    import javafx.scene.layout.VBoxBuilder;
    import javafx.scene.media.Media;
    import javafx.scene.media.MediaPlayer;
    import javafx.scene.media.MediaView;

    class SceneGenerator {    

        private int xSize;
        private int ySize;

        public SceneGenerator(int xSize,int ySize){
            this.xSize=xSize;
            this.ySize=ySize;
        }

      public Scene createScene() {
        final StackPane layout = new StackPane();

        // determine the source directory for the playlist
        final File dir = new File(System.getProperty("user.dir")+"\\video");
        if (!dir.exists() || !dir.isDirectory()) {
          JOptionPane.showMessageDialog(null,"Cannot find video source directory: " + dir);
          Platform.exit();
          System.exit(0);
          return null;
        }

        //inizializzo il toolkit


        // create some media players.
        final List<MediaPlayer> players = new ArrayList<MediaPlayer>();
        for (String file : dir.list(new FilenameFilter() {
          @Override public boolean accept(File dir, String name) {
            return name.endsWith(".mp4")||name.endsWith(".flv");
          }
        })) players.add(createPlayer("file:///" + (dir + "\\" + file).replace("\\", "/").replaceAll(" ", "%20")));
        if (players.isEmpty()) {
          System.out.println("No video found in " + dir);
          Platform.exit();
          System.exit(0);
          return null;
        }    

        // create a view to show the mediaplayers.
        final MediaView mediaView = new MediaView(players.get(0));

            mediaView.setPreserveRatio(false);
            mediaView.setFitHeight(ySize-((ySize/100)*(JavaPlayer.panelSouthYDimension+1)-JavaPlayer.pixelAdattamento));
            mediaView.setFitWidth(xSize);


        // play each audio file in turn.
        for (int i = 0; i < players.size(); i++) {
          final MediaPlayer player     = players.get(i);
          final MediaPlayer nextPlayer = players.get((i + 1) % players.size());



          player.setOnEndOfMedia(new Runnable() {
            @Override public void run() {
              mediaView.setMediaPlayer(nextPlayer);
              nextPlayer.seek(nextPlayer.getStartTime());
              nextPlayer.play();
            }
          });
        }






        // start playing the first track.
        mediaView.setMediaPlayer(players.get(0));
        mediaView.getMediaPlayer().play();



        // layout the scene.
        layout.setStyle("-fx-background-color: black; -fx-font-size: 20; -fx-padding: 0; -fx-alignment: center;");
        layout.getChildren().addAll(
          VBoxBuilder.create().spacing(10).alignment(Pos.CENTER).children(
            mediaView).build()
        );

        return new Scene(layout);
      }

      /** sets the currently playing label to the label of the new media player and updates the progress monitor. */


      /** @return a MediaPlayer for the given source which will report any errors it encounters */
      private MediaPlayer createPlayer(String aMediaSrc) {
    //    System.out.println("Creating player for: " + aMediaSrc);
        final MediaPlayer player = new MediaPlayer(new Media(aMediaSrc));
        player.setOnError(new Runnable() {
          @Override public void run() {
            System.out.println("Media error occurred: " + player.getError());
          }
        });
        return player;
      }
    }

您好,感谢您的代码,我目前使用的是 java 7,一切正常,但是一旦加载了视频,它就永远不会释放,因此内存消耗仍然很高。 我尝试使用 system.gc,但似乎还不够。有什么建议吗?提前致谢。

class SceneGenerator {    

private int xSize;
private int ySize;

public SceneGenerator(int xSize,int ySize){
    this.xSize=xSize;
    this.ySize=ySize;
}

公共场景 createScene() { 最终 StackPane 布局 = new StackPane();

// determine the source directory for the playlist
final File dir = new File(System.getProperty("user.dir")+"\\video");
if (!dir.exists() || !dir.isDirectory()) {
  JOptionPane.showMessageDialog(null,"Cannot find video source directory: " + dir);
  Platform.exit();
  System.exit(0);
  return null;
}

//inizializzo il toolkit


final MediaView mediaView = new MediaView();

    mediaView.setPreserveRatio(false);
    mediaView.setFitHeight(ySize-((ySize/100)*(JavaPlayer.panelSouthYDimension+1)-JavaPlayer.pixelAdattamento));
    mediaView.setFitWidth(xSize);


    final int QUEUE_SIZE = 2 ; // should be enough
    final BlockingQueue<MediaPlayer> playerQueue = new ArrayBlockingQueue<>(QUEUE_SIZE);


    final Thread createPlayerThread = new Thread(new Runnable() {
        @Override
        public void run() {
             // create some media players.
            List<String> videoFiles = new ArrayList<String>();
            for (String file : dir.list(new FilenameFilter() {
              @Override public boolean accept(File dir, String name) {
                return name.endsWith(".mp4")||name.endsWith(".flv");
              }
            })) videoFiles.add("file:///" + (dir + "\\" + file).replace("\\", "/").replaceAll(" ", "%20"));
            int nextFileIndex = 0 ;

            while (true) {
                System.gc();
                MediaPlayer player = new MediaPlayer(new Media(videoFiles.get(nextFileIndex).toString())); // create
                player.setOnEndOfMedia(new Runnable() {
                    @Override
                    public void run() {
                        final Task<MediaPlayer> nextPlayerTask = new Task<MediaPlayer>() {
                            @Override
                            public MediaPlayer call()  {
                                try {
                                    return playerQueue.take();
                                } catch (InterruptedException e) {
                                    JOptionPane.showMessageDialog(null, "Error in SceneGenerator() : "+e.getMessage());
                                    return null;
                                }
                            }
                        };
                        nextPlayerTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
                            @Override
                            public void handle(WorkerStateEvent event) {
                                MediaPlayer player = nextPlayerTask.getValue();
                                mediaView.setMediaPlayer(player);
                                player.play();
                            }
                        });
                        new Thread(nextPlayerTask).start();
                    }
                });
                try {
                    playerQueue.put(player); // this will block if the queue is full...
                } catch (InterruptedException exc ) { // shouldn't happen...
                    exc.printStackTrace();
                    return ;
                }
                nextFileIndex = (nextFileIndex + 1) % videoFiles.size();
            }
        }
    });
    createPlayerThread.setDaemon(true); // won't block application exit

    // Layout etc

    createPlayerThread.start();
    // start first player. In theory, this could block, so call it before you show the stage:
    MediaPlayer firstPlayer;
    try {
        firstPlayer = playerQueue.take();
        mediaView.setMediaPlayer(firstPlayer);
        firstPlayer.play();
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        JOptionPane.showMessageDialog(null, "Error in SceneGenerator() : "+e.getMessage());
    }



// layout the scene.
layout.setStyle("-fx-background-color: black; -fx-font-size: 20; -fx-padding: 0; -fx-alignment: center;");
layout.getChildren().addAll(
  VBoxBuilder.create().spacing(10).alignment(Pos.CENTER).children(
    mediaView).build()
);

return new Scene(layout);

}

}

【问题讨论】:

  • 我对 javaFX 不是很熟悉,但是我非常熟悉垃圾收集的工作原理。乍一看(也许我只是错过了它),但看起来你并没有摆脱玩家数组列表中的项目。我想这些项目会占用大量内存,并且通过在数组列表中保留对它们的引用,可以防止 GC 释放内存。也许您也可以等待创建播放器(并且只存储文件路径),直到它准备好播放(或队列中的下一个)。对不起,如果这没有帮助,就像我说的那样,我对 javaFX 不是很熟悉。

标签: java memory javafx


【解决方案1】:

我会通过创建一个BlockingQueue&lt;MediaPlayer&gt; 来存储有限数量的媒体播放器来解决这个问题。创建一个循环文件​​的后台线程,从每个文件创建一个媒体播放器,并将其放入队列中。媒体播放器应该注册了onEndOfMedia 监听器。该侦听器将或多或少与您一样,但是您从队列中取出下一个 MediaPlayer(这也应该在后台线程中)并播放它。它变得稍微复杂一些,因为您必须在阻塞调用时管理线程,但是像这样(这只是为您提供这个想法的近似值;我没有测试它或尝试编译或任何东西):

    final int QUEUE_SIZE = 2;
    final BlockingQueue<MediaPlayer> playerQueue = new ArrayBlockingQueue<>(QUEUE_SIZE);
    final MediaView mediaView = new MediaView();


    Thread createPlayerThread = new Thread( () -> {
        final List<File> videoFiles = ...
        IntStream.iterate(0, index -> (index + 1) % videoFiles.size())
            .mapToObj(videoFiles::get)
            .map(file -> createPlayer(file, mediaView, playerQueue))
            .forEach(player -> {
                try {
                    playerQueue.put(player);
                } catch (Exception e) {
                    e.printStackTrace();
                    return ;
                }
            });
    });
    createPlayerThread.setDaemon(true);
    createPlayerThread.start();

    // do layout, etc...

    try {
        mediaView.setMediaPlayer(playerQueue.take());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // ...

private MediaPlayer createPlayer(File file, MediaView mediaView, BlockingQueue<MediaPlayer> playerQueue) {
    URI uri = file.toURI();
    Media media = new Media(uri.toString());
    MediaPlayer player = new MediaPlayer(media);
    player.setAutoPlay(true);
    player.setOnEndOfMedia( () -> {
        Task<MediaPlayer> nextPlayerTask = new Task<MediaPlayer>() {
            @Override
            protected MediaPlayer call() throws Exception {
                return playerQueue.take();
            }
        };
        nextPlayerTask.setOnSucceeded(workerStateEvent -> 
            mediaView.setMediaPlayer(nextPlayerTask.getValue()));
    });
    return player ;
}

如果队列太大,则可能会出现内存不足的风险。如果它太小,那么理论上你可能会冒着队列中没有任何可用视频的风险(如果它们的加载时间比播放时间长);在实践中,这可能不太可能。如果队列太小,代码仍然可以工作,但视频之间会有停顿。

【讨论】:

  • 这在 Java 8 中会干净很多。如果您使用的是 Java 8,请告诉我,我会编辑代码。
  • 您好,谢谢您的回答!我试过你的代码,有一些错误,但修复它真的很好。之前程序消耗大约 600 MB 的 RAM,现在大约消耗 300 MB!如果您可以为 java 8 创建相同的代码,我可以对其进行测试。再次感谢
  • 您好,感谢您的代码,我目前正在使用 java 7 并且一切正常,但是一旦加载了视频,它就永远不会释放,因此内存消耗仍然很高。我尝试使用 system.gc,但似乎还不够。有什么建议吗?提前致谢。 (我在答案中添加了我的代码)
  • 嗯。没有把握。我想知道 endOfMedia 处理程序是否阻止播放器收集垃圾。我不太明白为什么会这样,但这是我最好的猜测。当不再需要它时,您可以尝试将其设置为 null。
  • 我无法解决。我在 jre8 中尝试了您的代码,但没有更改视频。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-15
  • 1970-01-01
  • 2010-10-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多