
This article is a case study to simplify the IntelliJ Platform to introduce the IntelliJ Platform in the disposer.
In fact, the SDK documentation already has a more detailed description of this: Disposer and Disposable, before we get started, it might be a good idea to take a look here.
Scene Description
I have modelled a scenario here. There is a ToolWindow layout as follows,Here we use the UI Inspector to analyse the layout:
A rootPanel with BorderLayout, top (North) is a Toolbar, center (Center) is a JPanel. Click ➕ will add a random code snippet to Center’s JPanel.
The scenario, as above, is a very simple ToolWindow with very simple functionality.
Memory leak
Here is the key part of the code snippet:
import com.intellij.openapi.project.Project;import com.intellij.openapi.wm.ToolWindow;import com.intellij.openapi.wm.ToolWindowFactory;import com.intellij.ui.content.Content;import com.intellij.ui.content.ContentFactory;import com.obiscr.template.dispose.MainPanel;import org.jetbrains.annotations.NotNull;
public class MyToolWindowFactory implements ToolWindowFactory { @Override public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { ContentFactory contentFactory = ContentFactory.getInstance(); MainPanel panel = new MainPanel(project); Content content = contentFactory.createContent(panel.getComponent(), "Dispose", false); toolWindow.getContentManager().addContent(content); }}
and
import com.intellij.icons.AllIcons;import com.intellij.openapi.actionSystem.AnAction;import com.intellij.openapi.actionSystem.AnActionEvent;import com.intellij.openapi.actionSystem.DefaultActionGroup;import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;import com.intellij.openapi.editor.Editor;import com.intellij.openapi.editor.EditorFactory;import com.intellij.openapi.editor.EditorSettings;import com.intellij.openapi.editor.colors.EditorColorsManager;import com.intellij.openapi.editor.colors.EditorColorsScheme;import com.intellij.openapi.editor.ex.EditorEx;import com.intellij.openapi.editor.highlighter.EditorHighlighter;import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;import com.intellij.openapi.project.Project;import com.intellij.testFramework.LightVirtualFile;import com.intellij.ui.components.panels.VerticalLayout;import com.intellij.util.ui.JBUI;import com.intellij.util.ui.UIUtil;import org.jetbrains.annotations.NotNull;
import javax.swing.*;import java.awt.*;import java.io.File;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;import java.util.UUID;
public class MainPanel {
private final JPanel rootPanel; private final JPanel centerPanel; private final Project myProject;
public MainPanel(Project project) { myProject = project; rootPanel = new JPanel(new BorderLayout()); centerPanel = new JPanel(new VerticalLayout(JBUI.scale(10))); centerPanel.setBorder(JBUI.Borders.empty(10));
DefaultActionGroup actionGroup = new DefaultActionGroup(); AnAction addAction = new AnAction(() -> "Add Item", AllIcons.General.Add) { @Override public void actionPerformed(@NotNull AnActionEvent e) { String text = "// Code snippet at " + LocalDateTime.now(). format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + "\n"; text += HelloWorld.getRandomHelloWorldCode(); Editor editor = createEditor(myProject, text); centerPanel.add(editor.getComponent()); centerPanel.revalidate(); centerPanel.repaint(); } }; actionGroup.add(addAction); ActionToolbarImpl actionToolbar = new ActionToolbarImpl("MyPanelToolbar", actionGroup, true); actionToolbar.setTargetComponent(rootPanel); actionToolbar.setBorder(JBUI.Borders.customLine(UIUtil.getBoundsColor(), 1,0,1,0)); rootPanel.add(actionToolbar, BorderLayout.NORTH); rootPanel.add(centerPanel, BorderLayout.CENTER); }
public JPanel getComponent() { return rootPanel; }
private Editor createEditor(Project project, String text) { EditorFactory editorFactory = EditorFactory.getInstance(); EditorEx editor = (EditorEx) editorFactory.createViewer( editorFactory.createDocument(text), project); // Others settings // ... return editor; }}
Here we just create the Editor object, but we don’t clean up the memory it occupies when the program exits.
When closing the IDE, relevant exception messages are displayed (Of course, this information is also displayed in the idea.log
):
According to the error log, we can know that it is because the created Editor object is not released when the application is closed and there is a risk of memory overflow.
Solving
Ok, now we already know where the problem is, the following we solve the problem, the solution is also simple, just need to close the IDE, release in this object can be.
First, make MainPanel implement the Disposable interface and override the dispose()
method. Then after creating editor
, register Disposer
with parent this
to indicate that the editor will be destroyed when this is disposed.
public class MainPanel implements Disposable { @Override public void dispose() { centerPanel.removeAll(); rootPanel.removeAll(); }
private Editor createEditor(Project project, String text) { EditorEx editor = ...; Disposer.register(this, () -> EditorFactory.getInstance().releaseEditor(editor)); return editor; }}
In addition, since in this case the components are all based on Content, we register the disposer for content as MainPanel
public class MyToolWindowFactory implements ToolWindowFactory {
@Override public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { MainPanel panel = new MainPanel(project); Content content = ...; content.setDisposer(panel); ... }}
The source code for ContentImpl.java looks like this, and you can see that when content
is destroyed, it calls Disposer.dispose(myDisposer);
to destroy the set myDisposer, which is the MainPanel above.
@Override public void dispose() { if (myShouldDisposeContent && myComponent instanceof Disposable) { Disposer.dispose((Disposable)myComponent); }
if (myDisposer != null) { Disposer.dispose(myDisposer); myDisposer = null; }
myFocusRequest = null; clearUserData(); }
So now the whole process is as follows:
content.dispose() --(call)--> panel.dispose() --(call)--> EditorFactory.getInstance().releaseEditor(editor);
So, when content
is closed, the editor object is also destroyed.