代码之家  ›  专栏  ›  技术社区  ›  teraspora

我的方法是耗尽堆空间。我需要将某个值设置为空吗?

  •  3
  • teraspora  · 技术社区  · 9 年前

    我创建了这个方法,以幻灯片形式显示图像列表。 它工作得很好,直到它有超过50或60张图像需要处理。然后它崩溃了

    java.lang.OutOfMemoryError: Java heap space

    所以我想知道我是否应该在每次循环时都将某个值设置为空?我在这里和网上搜索过,但没有找到答案。

    方法如下:

    private void createSlideshow(Stage stage, ArrayList<BufferedImage> slideList, int durationInSecs) throws InterruptedException {
    
                stage.show();
                SequentialTransition slideshow = new SequentialTransition();
                int i = 0;
                for (BufferedImage bi: slideList) {
                    System.out.println(" Iteration " + (i++));
                    ImageView slide = new ImageView(SwingFXUtils.toFXImage(bi, null));    // LINE 108
                    FadeTransition fadeIn =  new FadeTransition(Duration.millis(durationInSecs * 1000), slide);
                    fadeIn.setFromValue(0.0);
                    fadeIn.setToValue(1.0);
    
                    PauseTransition stayOn = new PauseTransition(Duration.millis(durationInSecs * 1000));
    
                    FadeTransition fadeOut =  new FadeTransition(Duration.millis(durationInSecs * 1000), slide);
                    fadeOut.setFromValue(1.0);
                    fadeOut.setToValue(0.0);
    
                    SequentialTransition fadeInOut = new SequentialTransition();
                    fadeInOut.getChildren().addAll(fadeIn, stayOn, fadeOut);
                    slide.setOpacity(0.0);              
                    root.getChildren().add(slide);            
                    slideshow.getChildren().add(fadeInOut);
                }
                slideshow.play();
            }
    

    完整的运行时消息如下:

    Exception in Application start method
    java.lang.reflect.InvocationTargetException
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
            at java.lang.reflect.Method.invoke(Unknown Source)
            at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(Unknown Source)
            at com.sun.javafx.application.LauncherImpl.launchApplication(Unknown Source)
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
            at java.lang.reflect.Method.invoke(Unknown Source)
            at sun.launcher.LauncherHelper$FXHelper.main(Unknown Source)
    Caused by: java.lang.RuntimeException: Exception in Application start method
            at com.sun.javafx.application.LauncherImpl.launchApplication1(Unknown Source)
            at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$155(Unknown Source)
            at java.lang.Thread.run(Unknown Source)
    Caused by: java.lang.OutOfMemoryError: Java heap space
            at java.nio.HeapByteBuffer.<init>(Unknown Source)
            at java.nio.ByteBuffer.allocate(Unknown Source)
            at com.sun.javafx.tk.quantum.QuantumToolkit.createPlatformImage(Unknown Source)
            at javafx.scene.image.Image.<init>(Unknown Source)
            at javafx.scene.image.WritableImage.<init>(Unknown Source)
            at javafx.embed.swing.SwingFXUtils.toFXImage(Unknown Source)
            at Slideshow.createSlideshow(Slideshow.java:108)
            at Slideshow.start(Slideshow.java:52)
            at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(Unknown Source)
            at com.sun.javafx.application.LauncherImpl$$Lambda$64/1581649247.run(Unknown Source)
            at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(Unknown Source)
            at com.sun.javafx.application.PlatformImpl$$Lambda$49/1915503092.run(Unknown Source)
            at com.sun.javafx.application.PlatformImpl.lambda$null$173(Unknown Source)
            at com.sun.javafx.application.PlatformImpl$$Lambda$51/1557268138.run(Unknown Source)
            at java.security.AccessController.doPrivileged(Native Method)
            at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(Unknown Source)
            at com.sun.javafx.application.PlatformImpl$$Lambda$50/1567581361.run(Unknown Source)
            at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
            at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
            at com.sun.glass.ui.win.WinApplication.lambda$null$148(Unknown Source)
            at com.sun.glass.ui.win.WinApplication$$Lambda$39/1645995473.run(Unknown Source)
            ... 1 more
    Exception running application Slideshow
    
    1 回复  |  直到 9 年前
        1
  •  3
  •   James_D    9 年前

    你的主要 SequentialTransition 正在加载所有图像并在开始播放之前保留对它们的引用。因此,这种技术不会随着图像数量的增加而扩展(这意味着,如果您有足够的图像,那么一定会耗尽堆空间)。

    如果您的图像从 BufferedImage s到FX Image 很快,您可以使用时间线来完成这项工作:

    private void createSlideshow(Stage stage, ArrayList<BufferedImage> slideList, int durationInSecs) throws InterruptedException {
    
        stage.show();
        Timeline slideshow = new Timeline();
    
        ImageView slide = new ImageView();
    
        for (int i = 0; i < slideList.size(); i++) {
            BufferedImage bi = slideList.get(i);
            KeyFrame newImageFrame = new KeyFrame(Duration.seconds(durationInSeconds * 3 * i), e -> 
                slide.setImage(SwingFXUtils.toFXImage(bi, null)));
    
            KeyFrame startFadeIn = new KeyFrame(Duration.seconds(durationInSeconds * 3 * i), 
                new KeyValue(slide.opacityProperty(), 0));
    
            KeyFrame endFadeIn = new KeyFrame(Duration.seconds(durationsInSeconds * (3 * i + 1)), 
                new KeyValue(slide.opacityProperty(), 1));
    
            KeyFrame startFadeOut = new KeyFrame(Duration.seconds(durationInSeconds * (3 * i + 2)), 
                new KeyValue(slide.opacityProperty(), 1));
    
            slideshow.getKeyFrames().addAll(newImageFrame, startFadeIn, endFadeIn, startFadeOut);
    
        }
        slideshow.play();
    }
    

    在这个实现中,您创建了一个时间线,每个图像有四个关键帧。第一个转换电流 缓冲图像 到JavaFX 形象 ,而注册的下一个(在同一时间点)将不透明度设置为零。下一个关键帧的不透明度设置为1(因此时间轴将在这两个关键帧之间插入不透明度)。最后一个关键帧的不透明度也为1,因此它将在时间线的该部分保持不变。在循环的下一次迭代中,将添加一个不透明度为0的新关键帧,因此,一次迭代的最后一个关键帧和循环的下个迭代的关键帧之间的插值将创建淡出。

    如果图像需要一些时间来转换,那么这个实现可能会由于图像出现之前的延迟而出现一些抖动,从而破坏“淡入”效果。解决此问题的一种方法是使用后台线程来转换图像并将其放入绑定的阻塞队列:

    private void createSlideshow(Stage stage, ArrayList<BufferedImage> slideList, int durationInSecs) throws InterruptedException {
    
        stage.show();
    
        int numImages = slideList.size();
    
        BlockingQueue<Image> images = new ArrayBlockingQueue<>(3);
    
        Thread conversionThread = new Thread(() -> {
            for (BufferedImage bi : slideList) {
                try {
                    images.put(SwingFXUtils.toFXImage(bi, null));
                } catch (InterruptedException exc) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        conversionThread.setDaemon(true);
        conversionThread.start();
    
        Timeline slideshow = new Timeline();
    
        ImageView slide = new ImageView();
    
        for (int i = 0; i < slideList.size(); i++) {
    
            KeyFrame newImageFrame = new KeyFrame(Duration.seconds(durationInSeconds * 3 * i), e -> 
                slide.setImage(images.poll())); 
    
            KeyFrame startFadeIn = new KeyFrame(Duration.seconds(durationInSeconds * 3 * i), 
                new KeyValue(slide.opacityProperty(), 0));
    
            KeyFrame endFadeIn = new KeyFrame(Duration.seconds(durationsInSeconds * (3 * i + 1)), 
                new KeyValue(slide.opacityProperty(), 1));
    
            KeyFrame startFadeOut = new KeyFrame(Duration.seconds(durationInSeconds * (3 * i + 2)), 
                new KeyValue(slide.opacityProperty(), 1));
    
            slideshow.getKeyFrames().addAll(newImageFrame, startFadeIn, endFadeIn, startFadeOut);
    
        }
        slideshow.play();
    }
    

    最后请注意,您的原始代码以及这两个实现都需要一个列表 缓冲图像 s传递给方法。这已经消耗了大量内存:在进入方法体之前,您基本上已经在内存中保存了所有图像。根据这些图像的来源,例如,您可以传递 File 对象,并在这里使用相同的技术根据需要动态加载每个图像(或像第二个示例中那样创建一个小队列)。这将基本上缩放到任意数量的图像。