a2844d19647ab795cfad13ffd7449aafe7f78f64
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / memory / ui / ClassesFilteredView.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.debugger.memory.ui;
3
4 import com.intellij.debugger.DebuggerManager;
5 import com.intellij.debugger.engine.*;
6 import com.intellij.debugger.engine.events.DebuggerCommandImpl;
7 import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
8 import com.intellij.debugger.memory.component.InstancesTracker;
9 import com.intellij.debugger.memory.component.MemoryViewDebugProcessData;
10 import com.intellij.debugger.memory.component.MemoryViewManager;
11 import com.intellij.debugger.memory.component.MemoryViewManagerState;
12 import com.intellij.debugger.memory.event.InstancesTrackerListener;
13 import com.intellij.debugger.memory.event.MemoryViewManagerListener;
14 import com.intellij.debugger.memory.tracking.ConstructorInstancesTracker;
15 import com.intellij.debugger.memory.tracking.TrackerForNewInstances;
16 import com.intellij.debugger.memory.tracking.TrackingType;
17 import com.intellij.debugger.memory.utils.AndroidUtil;
18 import com.intellij.debugger.memory.utils.KeyboardUtils;
19 import com.intellij.debugger.memory.utils.LowestPriorityCommand;
20 import com.intellij.debugger.memory.utils.SingleAlarmWithMutableDelay;
21 import com.intellij.debugger.requests.ClassPrepareRequestor;
22 import com.intellij.icons.AllIcons;
23 import com.intellij.notification.NotificationType;
24 import com.intellij.openapi.Disposable;
25 import com.intellij.openapi.actionSystem.*;
26 import com.intellij.openapi.actionSystem.impl.ActionButton;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.Disposer;
31 import com.intellij.openapi.wm.IdeFocusManager;
32 import com.intellij.ui.*;
33 import com.intellij.util.ui.JBDimension;
34 import com.intellij.util.ui.components.BorderLayoutPanel;
35 import com.intellij.xdebugger.XDebugSession;
36 import com.intellij.xdebugger.XDebugSessionListener;
37 import com.intellij.xdebugger.XDebuggerManager;
38 import com.intellij.xdebugger.impl.XDebuggerManagerImpl;
39 import com.sun.jdi.ObjectReference;
40 import com.sun.jdi.ReferenceType;
41 import com.sun.jdi.VirtualMachine;
42 import com.sun.jdi.request.ClassPrepareRequest;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
45
46 import javax.swing.*;
47 import javax.swing.event.DocumentEvent;
48 import java.awt.*;
49 import java.awt.event.*;
50 import java.util.Collections;
51 import java.util.LinkedHashMap;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.concurrent.ConcurrentHashMap;
55 import java.util.concurrent.TimeUnit;
56 import java.util.concurrent.atomic.AtomicBoolean;
57 import java.util.concurrent.atomic.AtomicInteger;
58
59 import static com.intellij.debugger.memory.ui.ClassesTable.DiffViewTableModel.CLASSNAME_COLUMN_INDEX;
60 import static com.intellij.debugger.memory.ui.ClassesTable.DiffViewTableModel.DIFF_COLUMN_INDEX;
61
62 public class ClassesFilteredView extends BorderLayoutPanel implements Disposable {
63   private static final Logger LOG = Logger.getInstance(ClassesFilteredView.class);
64   private static final double DELAY_BEFORE_INSTANCES_QUERY_COEFFICIENT = 0.5;
65   private static final double MAX_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(2);
66   private static final int DEFAULT_BATCH_SIZE = Integer.MAX_VALUE;
67   private static final String EMPTY_TABLE_CONTENT_WHEN_RUNNING = "The application is running";
68   private static final String EMPTY_TABLE_CONTENT_WHEN_STOPPED = "Classes are not available";
69   private static final String CLICKABLE_TABLE_CONTENT = "Click to load the classes list";
70
71   private final Project myProject;
72   private final SingleAlarmWithMutableDelay mySingleAlarm;
73
74   private final SearchTextField myFilterTextField = new FilterTextField();
75   private final ClassesTable myTable;
76   private final InstancesTracker myInstancesTracker;
77   private final Map<ReferenceType, ConstructorInstancesTracker> myConstructorTrackedClasses = new ConcurrentHashMap<>();
78   private final MyDebuggerSessionListener myDebugSessionListener;
79
80   // tick on each session paused event
81   private final AtomicInteger myTime = new AtomicInteger(0);
82
83   private final AtomicInteger myLastUpdatingTime = new AtomicInteger(Integer.MIN_VALUE);
84
85   /**
86    * Indicates that the debug session had been stopped at least once.
87    * <p>
88    * State: false to true
89    */
90   private final AtomicBoolean myIsTrackersActivated = new AtomicBoolean(false);
91
92   /**
93    * Indicates that view is visible
94    */
95   private volatile boolean myIsActive;
96
97   public ClassesFilteredView(@NotNull XDebugSession debugSession,
98                              @NotNull DebugProcessImpl debugProcess,
99                              @NotNull InstancesTracker tracker) {
100     myProject = debugSession.getProject();
101
102     final DebuggerManagerThreadImpl managerThread = debugProcess.getManagerThread();
103     myInstancesTracker = tracker;
104     final InstancesTrackerListener instancesTrackerListener = new InstancesTrackerListener() {
105       @Override
106       public void classChanged(@NotNull String name, @NotNull TrackingType type) {
107         ReferenceType ref = myTable.getClassByName(name);
108         if (ref != null) {
109           final boolean activated = myIsTrackersActivated.get();
110           managerThread.schedule(new DebuggerCommandImpl() {
111             @Override
112             protected void action() {
113               trackClass(debugSession, ref, type, activated);
114             }
115           });
116         }
117         myTable.repaint();
118       }
119
120       @Override
121       public void classRemoved(@NotNull String name) {
122         ReferenceType ref = myTable.getClassByName(name);
123         if (ref != null && myConstructorTrackedClasses.containsKey(ref)) {
124           ConstructorInstancesTracker removed = myConstructorTrackedClasses.remove(ref);
125           Disposer.dispose(removed);
126           myTable.getRowSorter().allRowsChanged();
127         }
128       }
129     };
130
131     debugSession.addSessionListener(new XDebugSessionListener() {
132       @Override
133       public void sessionStopped() {
134         debugSession.removeSessionListener(this);
135         myInstancesTracker.removeTrackerListener(instancesTrackerListener);
136       }
137     });
138
139     debugProcess.addDebugProcessListener(new DebugProcessListener() {
140       @Override
141       public void processAttached(DebugProcess process) {
142         debugProcess.removeDebugProcessListener(this);
143         managerThread.invoke(new DebuggerCommandImpl() {
144           @Override
145           protected void action() {
146             final boolean activated = myIsTrackersActivated.get();
147             final VirtualMachineProxyImpl proxy = debugProcess.getVirtualMachineProxy();
148             tracker.getTrackedClasses().forEach((className, type) -> {
149               List<ReferenceType> classes = proxy.classesByName(className);
150               if (classes.isEmpty()) {
151                 trackWhenPrepared(className, debugSession, debugProcess, type);
152               }
153               else {
154                 for (ReferenceType ref : classes) {
155                   trackClass(debugSession, ref, type, activated);
156                 }
157               }
158             });
159
160             tracker.addTrackerListener(instancesTrackerListener);
161           }
162         });
163       }
164
165       private void trackWhenPrepared(@NotNull String className,
166                                      @NotNull XDebugSession session,
167                                      @NotNull DebugProcessImpl process,
168                                      @NotNull TrackingType type) {
169         final ClassPrepareRequestor request = new ClassPrepareRequestor() {
170           @Override
171           public void processClassPrepare(DebugProcess debuggerProcess, ReferenceType referenceType) {
172             process.getRequestsManager().deleteRequest(this);
173             trackClass(session, referenceType, type, myIsTrackersActivated.get());
174           }
175         };
176
177         final ClassPrepareRequest classPrepareRequest = process.getRequestsManager()
178           .createClassPrepareRequest(request, className);
179         if (classPrepareRequest != null) {
180           classPrepareRequest.enable();
181         }
182         else {
183           LOG.warn("Cannot create a 'class prepare' request. Class " + className + " not tracked.");
184         }
185       }
186     });
187
188     final MemoryViewManagerState memoryViewManagerState = MemoryViewManager.getInstance().getState();
189
190     myTable = new ClassesTable(tracker, this, memoryViewManagerState.isShowWithDiffOnly,
191                                memoryViewManagerState.isShowWithInstancesOnly, memoryViewManagerState.isShowTrackedOnly);
192     myTable.getEmptyText().setText(EMPTY_TABLE_CONTENT_WHEN_RUNNING);
193     Disposer.register(this, myTable);
194
195     myTable.addMouseMotionListener(new MyMouseMotionListener());
196     myTable.addMouseListener(new MyOpenNewInstancesListener());
197     new MyDoubleClickListener().installOn(myTable);
198
199     myTable.addKeyListener(new KeyAdapter() {
200       @Override
201       public void keyReleased(KeyEvent e) {
202         final int keyCode = e.getKeyCode();
203         if (KeyboardUtils.isEnterKey(keyCode)) {
204           handleClassSelection(myTable.getSelectedClass());
205         }
206         else if (KeyboardUtils.isCharacter(keyCode) || KeyboardUtils.isBackSpace(keyCode)) {
207           final String text = myFilterTextField.getText();
208           final String newText = KeyboardUtils.isBackSpace(keyCode)
209                                  ? text.substring(0, text.length() - 1)
210                                  : text + e.getKeyChar();
211           myFilterTextField.setText(newText);
212           IdeFocusManager.getInstance(myProject).requestFocus(myFilterTextField, false);
213         }
214       }
215     });
216
217     myFilterTextField.addKeyboardListener(new KeyAdapter() {
218       @Override
219       public void keyPressed(KeyEvent e) {
220         dispatch(e);
221       }
222
223       @Override
224       public void keyReleased(KeyEvent e) {
225         dispatch(e);
226       }
227
228       private void dispatch(KeyEvent e) {
229         if (KeyboardUtils.isUpDownKey(e.getKeyCode()) || KeyboardUtils.isEnterKey(e.getKeyCode())) {
230           myTable.dispatchEvent(e);
231         }
232       }
233     });
234
235     myFilterTextField.addDocumentListener(new DocumentAdapter() {
236       @Override
237       protected void textChanged(DocumentEvent e) {
238         myTable.setFilterPattern(myFilterTextField.getText());
239       }
240     });
241
242     final MemoryViewManagerListener memoryViewManagerListener = state -> {
243       myTable.setFilteringByDiffNonZero(state.isShowWithDiffOnly);
244       myTable.setFilteringByInstanceExists(state.isShowWithInstancesOnly);
245       myTable.setFilteringByTrackingState(state.isShowTrackedOnly);
246       if (state.isAutoUpdateModeOn && myTable.isInClickableMode()) {
247         updateClassesAndCounts(true);
248       }
249     };
250
251     MemoryViewManager.getInstance().addMemoryViewManagerListener(memoryViewManagerListener, this);
252
253     myDebugSessionListener = new MyDebuggerSessionListener();
254     debugSession.addSessionListener(myDebugSessionListener, this);
255
256     mySingleAlarm = new SingleAlarmWithMutableDelay(suspendContext -> {
257       ApplicationManager.getApplication().invokeLater(() -> myTable.setBusy(true));
258       suspendContext.getDebugProcess().getManagerThread().schedule(new MyUpdateClassesCommand(suspendContext));
259     }, this);
260
261     mySingleAlarm.setDelay((int)TimeUnit.MILLISECONDS.toMillis(500));
262
263     myTable.addMouseListener(new PopupHandler() {
264       @Override
265       public void invokePopup(Component comp, int x, int y) {
266         ActionPopupMenu menu = createContextMenu();
267         menu.getComponent().show(comp, x, y);
268       }
269     });
270
271     final JScrollPane scroll = ScrollPaneFactory.createScrollPane(myTable, SideBorder.TOP);
272     final DefaultActionGroup group = (DefaultActionGroup)ActionManager.getInstance().getAction("MemoryView.SettingsPopupActionGroup");
273     group.setPopup(true);
274     final Presentation actionsPresentation = new Presentation("Memory View Settings");
275     actionsPresentation.setIcon(AllIcons.General.SecondaryGroup);
276
277     final ActionButton button = new ActionButton(group, actionsPresentation, ActionPlaces.UNKNOWN, new JBDimension(25, 25));
278     final BorderLayoutPanel topPanel = new BorderLayoutPanel();
279     topPanel.addToCenter(myFilterTextField);
280     topPanel.addToRight(button);
281     addToTop(topPanel);
282     addToCenter(scroll);
283   }
284
285   @Nullable
286   TrackerForNewInstances getStrategy(@NotNull ReferenceType ref) {
287     return myConstructorTrackedClasses.getOrDefault(ref, null);
288   }
289
290   private void trackClass(@NotNull XDebugSession session,
291                           @NotNull ReferenceType ref,
292                           @NotNull TrackingType type,
293                           boolean isTrackerEnabled) {
294     LOG.assertTrue(DebuggerManager.getInstance(myProject).isDebuggerManagerThread());
295     if (type == TrackingType.CREATION) {
296       final ConstructorInstancesTracker old = myConstructorTrackedClasses.getOrDefault(ref, null);
297       if (old != null) {
298         Disposer.dispose(old);
299       }
300
301       final ConstructorInstancesTracker tracker = new ConstructorInstancesTracker(ref, session, myInstancesTracker);
302       tracker.setBackgroundMode(!myIsActive);
303       if (isTrackerEnabled) {
304         tracker.enable();
305       }
306       else {
307         tracker.disable();
308       }
309
310       myConstructorTrackedClasses.put(ref, tracker);
311     }
312   }
313
314   private void handleClassSelection(@Nullable ReferenceType ref) {
315     final XDebugSession debugSession = XDebuggerManager.getInstance(myProject).getCurrentSession();
316     if (ref != null && debugSession != null && debugSession.isSuspended()) {
317       if (!ref.virtualMachine().canGetInstanceInfo()) {
318         XDebuggerManagerImpl.NOTIFICATION_GROUP
319           .createNotification("The virtual machine implementation does not provide an ability to get instances",
320                               NotificationType.INFORMATION).notify(debugSession.getProject());
321         return;
322       }
323
324       new InstancesWindow(debugSession, limit -> {
325         final List<ObjectReference> instances = ref.instances(limit);
326         return instances == null ? Collections.emptyList() : instances;
327       }, ref.name()).show();
328     }
329   }
330
331   private void commitAllTrackers() {
332     myConstructorTrackedClasses.values().forEach(ConstructorInstancesTracker::commitTracked);
333   }
334
335   private void updateClassesAndCounts(boolean immediate) {
336     ApplicationManager.getApplication().invokeLater(() -> {
337       final XDebugSession debugSession = XDebuggerManager.getInstance(myProject).getCurrentSession();
338       if (debugSession != null) {
339         final DebugProcess debugProcess = DebuggerManager.getInstance(myProject)
340           .getDebugProcess(debugSession.getDebugProcess().getProcessHandler());
341         if (debugProcess != null && debugProcess.isAttached() && debugProcess instanceof DebugProcessImpl) {
342           final DebugProcessImpl process = (DebugProcessImpl)debugProcess;
343           final SuspendContextImpl context = process.getDebuggerContext().getSuspendContext();
344           if (context != null) {
345             if (immediate) {
346               mySingleAlarm.cancelAndRequestImmediate(context);
347             }
348             else {
349               mySingleAlarm.cancelAndRequest(context);
350             }
351           }
352         }
353       }
354     }, myProject.getDisposed());
355   }
356
357   private static ActionPopupMenu createContextMenu() {
358     final ActionGroup group = (ActionGroup)ActionManager.getInstance().getAction("MemoryView.ClassesPopupActionGroup");
359     return ActionManager.getInstance().createActionPopupMenu("MemoryView.ClassesPopupActionGroup", group);
360   }
361
362   @Override
363   public void dispose() {
364     myConstructorTrackedClasses.clear();
365   }
366
367   public void setActive(boolean active, @NotNull DebuggerManagerThreadImpl managerThread) {
368     if (myIsActive == active) {
369       return;
370     }
371
372     myIsActive = active;
373
374     managerThread.schedule(new DebuggerCommandImpl() {
375       @Override
376       protected void action() {
377         if (active) {
378           doActivate();
379         }
380         else {
381           doPause();
382         }
383       }
384     });
385   }
386
387   private void doActivate() {
388     myDebugSessionListener.setActive(true);
389     myConstructorTrackedClasses.values().forEach(x -> x.setBackgroundMode(false));
390
391     if (isNeedUpdateView()) {
392       if (MemoryViewManager.getInstance().isAutoUpdateModeEnabled()) {
393         updateClassesAndCounts(true);
394       }
395       else {
396         makeTableClickable();
397       }
398     }
399   }
400
401   private void makeTableClickable() {
402     ApplicationManager.getApplication().invokeLater(
403       () -> myTable.makeClickable(CLICKABLE_TABLE_CONTENT, () -> updateClassesAndCounts(true)));
404   }
405
406   private void doPause() {
407     myDebugSessionListener.setActive(false);
408     mySingleAlarm.cancelAllRequests();
409     myConstructorTrackedClasses.values().forEach(x -> x.setBackgroundMode(true));
410   }
411
412   private boolean isNeedUpdateView() {
413     return myLastUpdatingTime.get() != myTime.get();
414   }
415
416   private void viewUpdated() {
417     myLastUpdatingTime.set(myTime.get());
418   }
419
420   private final class MyUpdateClassesCommand extends LowestPriorityCommand {
421
422     MyUpdateClassesCommand(@Nullable SuspendContextImpl suspendContext) {
423       super(suspendContext);
424     }
425
426     @Override
427     public void contextAction(@NotNull SuspendContextImpl suspendContext) {
428       handleTrackers();
429
430       final List<ReferenceType> classes = suspendContext.getDebugProcess().getVirtualMachineProxy().allClasses();
431
432       if (!classes.isEmpty()) {
433         final VirtualMachine vm = classes.get(0).virtualMachine();
434         if (vm.canGetInstanceInfo()) {
435           final Map<ReferenceType, Long> counts = getInstancesCounts(classes, vm);
436           ApplicationManager.getApplication().invokeLater(() -> myTable.updateContent(counts));
437         }
438         else {
439           ApplicationManager.getApplication().invokeLater(() -> myTable.updateClassesOnly(classes));
440         }
441       }
442
443       ApplicationManager.getApplication().invokeLater(() -> myTable.setBusy(false));
444       viewUpdated();
445     }
446
447     private void handleTrackers() {
448       if (!myIsTrackersActivated.get()) {
449         myConstructorTrackedClasses.values().forEach(ConstructorInstancesTracker::enable);
450         myIsTrackersActivated.set(true);
451       }
452       else {
453         commitAllTrackers();
454       }
455     }
456
457     private Map<ReferenceType, Long> getInstancesCounts(@NotNull List<ReferenceType> classes, @NotNull VirtualMachine vm) {
458       final int batchSize = AndroidUtil.isAndroidVM(vm)
459                             ? AndroidUtil.ANDROID_COUNT_BY_CLASSES_BATCH_SIZE
460                             : DEFAULT_BATCH_SIZE;
461
462       final int size = classes.size();
463       final Map<ReferenceType, Long> result = new LinkedHashMap<>();
464
465       for (int begin = 0, end = Math.min(batchSize, size);
466            begin != size;
467            begin = end, end = Math.min(end + batchSize, size)) {
468         final List<ReferenceType> batch = classes.subList(begin, end);
469
470         final long start = System.nanoTime();
471         final long[] counts = vm.instanceCounts(batch);
472         final long delay = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
473
474         for (int i = 0; i < batch.size(); i++) {
475           result.put(batch.get(i), counts[i]);
476         }
477
478         final int waitTime = (int)Math.min(DELAY_BEFORE_INSTANCES_QUERY_COEFFICIENT * delay, MAX_DELAY_MILLIS);
479         mySingleAlarm.setDelay(waitTime);
480         LOG.debug(String.format("Instances query time = %d ms. Count of classes = %d", delay, batch.size()));
481       }
482
483       return result;
484     }
485   }
486
487   private static class FilterTextField extends SearchTextField {
488     FilterTextField() {
489       super(false);
490     }
491
492     @Override
493     protected void showPopup() {
494     }
495
496     @Override
497     protected boolean hasIconsOutsideOfTextField() {
498       return false;
499     }
500   }
501
502   private class MyOpenNewInstancesListener extends MouseAdapter {
503     @Override
504     public void mouseClicked(MouseEvent e) {
505       if (e.getClickCount() != 1 || e.getButton() != MouseEvent.BUTTON1 || !isShowNewInstancesEvent(e)) {
506         return;
507       }
508
509       final ReferenceType ref = myTable.getSelectedClass();
510       final TrackerForNewInstances strategy = ref == null ? null : getStrategy(ref);
511       XDebugSession debugSession = XDebuggerManager.getInstance(myProject).getCurrentSession();
512       if (strategy != null && debugSession != null) {
513         final DebugProcess debugProcess =
514           DebuggerManager.getInstance(myProject).getDebugProcess(debugSession.getDebugProcess().getProcessHandler());
515         final MemoryViewDebugProcessData data = debugProcess.getUserData(MemoryViewDebugProcessData.KEY);
516         if (data != null) {
517           final List<ObjectReference> newInstances = strategy.getNewInstances();
518           data.getTrackedStacks().pinStacks(ref);
519           final InstancesWindow instancesWindow = new InstancesWindow(debugSession, limit -> newInstances, ref.name());
520           Disposer.register(instancesWindow.getDisposable(), () -> data.getTrackedStacks().unpinStacks(ref));
521           instancesWindow.show();
522         }
523         else {
524           LOG.warn("MemoryViewDebugProcessData not found in debug session user data");
525         }
526       }
527     }
528   }
529
530   private class MyDoubleClickListener extends DoubleClickListener {
531     @Override
532     protected boolean onDoubleClick(MouseEvent event) {
533       if (!isShowNewInstancesEvent(event)) {
534         handleClassSelection(myTable.getSelectedClass());
535         return true;
536       }
537
538       return false;
539     }
540   }
541
542   private class MyMouseMotionListener implements MouseMotionListener {
543     @Override
544     public void mouseDragged(MouseEvent e) {
545     }
546
547     @Override
548     public void mouseMoved(MouseEvent e) {
549       if (myTable.isInClickableMode()) return;
550
551       if (isShowNewInstancesEvent(e)) {
552         myTable.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
553       }
554       else {
555         myTable.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
556       }
557     }
558   }
559
560   private boolean isShowNewInstancesEvent(@NotNull MouseEvent e) {
561     final int col = myTable.columnAtPoint(e.getPoint());
562     final int row = myTable.rowAtPoint(e.getPoint());
563     if (col == -1 || row == -1 || myTable.convertColumnIndexToModel(col) != DIFF_COLUMN_INDEX) {
564       return false;
565     }
566
567     final int modelRow = myTable.convertRowIndexToModel(row);
568
569     final ReferenceType ref = (ReferenceType)myTable.getModel().getValueAt(modelRow, CLASSNAME_COLUMN_INDEX);
570     final ConstructorInstancesTracker tracker = myConstructorTrackedClasses.getOrDefault(ref, null);
571
572     return tracker != null && tracker.isReady() && tracker.getCount() > 0;
573   }
574
575   private class MyDebuggerSessionListener implements XDebugSessionListener {
576     private volatile boolean myIsActive = false;
577
578     void setActive(boolean value) {
579       myIsActive = value;
580     }
581
582     @Override
583     public void sessionResumed() {
584       if (myIsActive) {
585         myConstructorTrackedClasses.values().forEach(ConstructorInstancesTracker::obsolete);
586         ApplicationManager.getApplication().invokeLater(() -> myTable.hideContent(EMPTY_TABLE_CONTENT_WHEN_RUNNING));
587
588         mySingleAlarm.cancelAllRequests();
589       }
590     }
591
592     @Override
593     public void sessionStopped() {
594       myConstructorTrackedClasses.values().forEach(Disposer::dispose);
595       myConstructorTrackedClasses.clear();
596       mySingleAlarm.cancelAllRequests();
597       ApplicationManager.getApplication().invokeLater(() -> myTable.clean(EMPTY_TABLE_CONTENT_WHEN_STOPPED));
598     }
599
600     @Override
601     public void sessionPaused() {
602       myTime.incrementAndGet();
603       if (myIsActive) {
604         if (MemoryViewManager.getInstance().isAutoUpdateModeEnabled()) {
605           updateClassesAndCounts(false);
606         }
607         else {
608           makeTableClickable();
609         }
610       }
611     }
612   }
613 }