ScheduledThreadPoolExecutor 计划清除内存泄漏

ScheduledThreadPoolExecutor scheduled purging memory leak

我正在尝试编写一个封闭的 class 来从 Web 下载每 30 秒刷新一次的图像。我可能想下载 1 张图片,也可能想下载 N 张图片。我可能想随时停止下载某个图像。我写了下面的 class 效果很好,除非我停止下载图像内存不会为该任务释放。或者,如果我停止下载所有图像,则不会释放内存(这不会在生产中发生)。我尝试了几种不同的方法来实现这一目标。我最后一次尝试是每 30 秒使用同一个执行程序或使用单独执行程序下面的代码从 ScheduledThreadPoolExecutor 中清除任务。我还提供了测试释放内存的代码,尽管我的示例在实际使用中停止了所有图像的下载,我应该只能停止一个图像并从该任务中释放内存。

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;


public class ImageLoadTask implements Runnable {

    private static ScheduledThreadPoolExecutor taskExecutorService = new ScheduledThreadPoolExecutor(500);
    private static ScheduledThreadPoolExecutor purgeExecutorService = new ScheduledThreadPoolExecutor(500);
    private static Runnable purgeRunnable = () -> purge();
    private ScheduledFuture<?> scheduledFuture;
    private URL pictureURL;
    private Consumer<BufferedImage> successMethod;
    private Consumer<String> failMethod;
    private ImageURLStreamHandler streamHandler = new ImageURLStreamHandler();

    private boolean displaySuccess = false;
    private boolean displayError = false;
    private boolean isCancelled = false;

    static {
        taskExecutorService.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
        taskExecutorService.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
        taskExecutorService.setRemoveOnCancelPolicy(true);

        purgeExecutorService.scheduleWithFixedDelay(purgeRunnable, 30L, 30L, TimeUnit.SECONDS);
    }

    public ImageLoadTask(String url) {
        try {
            this.pictureURL = new URL(url);
        } catch (MalformedURLException e) {
            if(failMethod != null) {
                failMethod.accept(e.getMessage()); ;
            }
            if(displayError) {
                System.out.println("(ImageLoadTask) URL is malformed: " + url+"\n"+ e.getMessage());
            }
        }
    }

    public ImageLoadTask(String url, Consumer<BufferedImage> successMethod) {
        this(url);
        this.successMethod = successMethod;
    }

    public ImageLoadTask(String url, Consumer<BufferedImage> successMethod, Consumer<String> failMethod) {
        this(url, successMethod);
        this.failMethod = failMethod;
    }

    public void start() {
        scheduledFuture = taskExecutorService.scheduleAtFixedRate(this, 0L, 30L, TimeUnit.SECONDS);
    }

    public void stop() {
        if(isCancelled)
            return;

        isCancelled = true;
        scheduledFuture.cancel(true);
        scheduledFuture = null;
        pictureURL = null;
        successMethod = null;
        failMethod = null;
        streamHandler = null;

        taskExecutorService.remove(this);
        taskExecutorService.purge();
    }

    public static void purge() {
        System.out.println("Purging");
        taskExecutorService.purge();
    }

    @Override
    public void run() {
        if(!isCancelled) {
            try {
                BufferedImage image = loadImage(pictureURL);
                if(displaySuccess) {
                    System.out.println("Image received for url " + pictureURL);
                }
                if(successMethod != null && !isCancelled) {
                    successMethod.accept(image); ;
                }
            } catch (IOException e) {
                if(failMethod != null && !isCancelled) {
                    failMethod.accept(e.getMessage());
                }
                if(displayError) {
                    System.out.println("Error occured retrieving image for url: " + pictureURL +"\n"+ e.getMessage());
                }
            }
        }
    }

    public void displayError(boolean displayError) {
        this.displayError = displayError;
    }

    public void displaySuccess(boolean displaySuccess) {
        this.displaySuccess = displaySuccess;
    }

    private BufferedImage loadImage(URL input) throws IOException {
        if (input == null) {
            throw new IllegalArgumentException("input == null!");
        }

        InputStream istream = null;
        try {
            istream = streamHandler.openConnection(input).getInputStream();
        } catch (IOException e) {
            throw new IIOException("Can't get input stream from URL!", e);
        }
        ImageInputStream stream = ImageIO.createImageInputStream(istream);
        BufferedImage bi;
        try {
            bi = ImageIO.read(stream);
            if (bi == null) {
                stream.close();
            }
        } finally {
            istream.close();
        }
        return bi;
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize");
    }

    class ImageURLStreamHandler extends URLStreamHandler {

        @Override
        protected URLConnection openConnection(URL url) throws IOException {
            URL target = new URL(url.toString());
            URLConnection connection = target.openConnection();
            // Connection settings
            connection.setConnectTimeout(60000); // 1 min
            connection.setReadTimeout(60000); // 1 min
            return connection;
        }
    }
}

测试应用程序:

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class ImageLoadTaskTest {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                Gui gui = new Gui();
            }
        });
    }

    static class Gui extends JFrame {
        private static final long serialVersionUID = 1L;

        private List<ImageLoadTask> tasks = new ArrayList<>();
        private boolean running = false;

        private JButton startStopButton = new JButton("Start");
        private JButton purgeButton = new JButton("Purge");

        private ActionListener startStopListener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if(running) {
                    stopTasks();
                } else {
                    startTasks();
                }
            }
        };


        private ActionListener purgeListener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ImageLoadTask.purge();
            }
        };

        public Gui() {
            setTitle("Image Load Task Test");
            setBounds(250, 250, 300, 150); // Size
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            JPanel contentPanel = new JPanel();
            setContentPane(contentPanel);

            startStopButton.addActionListener(startStopListener);
            contentPanel.add(startStopButton);


            purgeButton.addActionListener(purgeListener);
            contentPanel.add(purgeButton);

            setVisible(true);
        }

        private void startTasks() {
            running = true;
            System.out.println("Starting tasks");
            for(int i = 0; i < 2500; i++) {
                ImageLoadTask task = new ImageLoadTask("http://placehold.it/120x120&text=image" + i, this::success, this::fail);
                task.start();
                tasks.add(task);
            }
            startStopButton.setText("Stop");
        }

        private void stopTasks() {
            running = false;
            System.out.println("Stopping " + tasks.size() + " tasks");
            for(ImageLoadTask task : tasks) {
                task.stop();
            }
            tasks.clear();
            startStopButton.setText("Start");
            System.out.println("Stopped tasks ");
            //ImageLoadTask.purge();
        }

        private void success(BufferedImage image) {
            //System.out.println("Success!");
        }

        private void fail(String message) {
            //System.out.println("Fail! "+ message);
        }
    }
}

当您打断 ImageIO.read(stream) 时,您不会关闭 'stream'。