Move AndroidUtil.isAndroidVM into DebuggerUtils
[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         final int keyCode = e.getKeyCode();
230         if (myTable.isInClickableMode() && (KeyboardUtils.isCharacter(keyCode) || KeyboardUtils.isEnterKey(keyCode))) {
231           myTable.exitClickableMode();
232           updateClassesAndCounts(true);
233         }
234         else if (KeyboardUtils.isUpDownKey(keyCode) || KeyboardUtils.isEnterKey(keyCode)) {
235           myTable.dispatchEvent(e);
236         }
237       }
238     });
239
240     myFilterTextField.addDocumentListener(new DocumentAdapter() {
241       @Override
242       protected void textChanged(DocumentEvent e) {
243         myTable.setFilterPattern(myFilterTextField.getText());
244       }
245     });
246
247     final MemoryViewManagerListener memoryViewManagerListener = state -> {
248       myTable.setFilteringByDiffNonZero(state.isShowWithDiffOnly);
249       myTable.setFilteringByInstanceExists(state.isShowWithInstancesOnly);
250       myTable.setFilteringByTrackingState(state.isShowTrackedOnly);
251       if (state.isAutoUpdateModeOn && myTable.isInClickableMode()) {
252         updateClassesAndCounts(true);
253       }
254     };
255
256     MemoryViewManager.getInstance().addMemoryViewManagerListener(memoryViewManagerListener, this);
257
258     myDebugSessionListener = new MyDebuggerSessionListener();
259     debugSession.addSessionListener(myDebugSessionListener, this);
260
261     mySingleAlarm = new SingleAlarmWithMutableDelay(suspendContext -> {
262       ApplicationManager.getApplication().invokeLater(() -> myTable.setBusy(true));
263       suspendContext.getDebugProcess().getManagerThread().schedule(new MyUpdateClassesCommand(suspendContext));
264     }, this);
265
266     mySingleAlarm.setDelay((int)TimeUnit.MILLISECONDS.toMillis(500));
267
268     myTable.addMouseListener(new PopupHandler() {
269       @Override
270       public void invokePopup(Component comp, int x, int y) {
271         ActionPopupMenu menu = createContextMenu();
272         menu.getComponent().show(comp, x, y);
273       }
274     });
275
276     final JScrollPane scroll = ScrollPaneFactory.createScrollPane(myTable, SideBorder.TOP);
277     final DefaultActionGroup group = (DefaultActionGroup)ActionManager.getInstance().getAction("MemoryView.SettingsPopupActionGroup");
278     group.setPopup(true);
279     final Presentation actionsPresentation = new Presentation("Memory View Settings");
280     actionsPresentation.setIcon(AllIcons.General.SecondaryGroup);
281
282     final ActionButton button = new ActionButton(group, actionsPresentation, ActionPlaces.UNKNOWN, new JBDimension(25, 25));
283     final BorderLayoutPanel topPanel = new BorderLayoutPanel();
284     topPanel.addToCenter(myFilterTextField);
285     topPanel.addToRight(button);
286     addToTop(topPanel);
287     addToCenter(scroll);
288   }
289
290   @Nullable
291   TrackerForNewInstances getStrategy(@NotNull ReferenceType ref) {
292     return myConstructorTrackedClasses.getOrDefault(ref, null);
293   }
294
295   private void trackClass(@NotNull XDebugSession session,
296                           @NotNull ReferenceType ref,
297                           @NotNull TrackingType type,
298                           boolean isTrackerEnabled) {
299     LOG.assertTrue(DebuggerManager.getInstance(myProject).isDebuggerManagerThread());
300     if (type == TrackingType.CREATION) {
301       final ConstructorInstancesTracker old = myConstructorTrackedClasses.getOrDefault(ref, null);
302       if (old != null) {
303         Disposer.dispose(old);
304       }
305
306       final ConstructorInstancesTracker tracker = new ConstructorInstancesTracker(ref, session, myInstancesTracker);
307       tracker.setBackgroundMode(!myIsActive);
308       if (isTrackerEnabled) {
309         tracker.enable();
310       }
311       else {
312         tracker.disable();
313       }
314
315       myConstructorTrackedClasses.put(ref, tracker);
316     }
317   }
318
319   private void handleClassSelection(@Nullable ReferenceType ref) {
320     final XDebugSession debugSession = XDebuggerManager.getInstance(myProject).getCurrentSession();
321     if (ref != null && debugSession != null && debugSession.isSuspended()) {
322       if (!ref.virtualMachine().canGetInstanceInfo()) {
323         XDebuggerManagerImpl.NOTIFICATION_GROUP
324           .createNotification("The virtual machine implementation does not provide an ability to get instances",
325                               NotificationType.INFORMATION).notify(debugSession.getProject());
326         return;
327       }
328
329       new InstancesWindow(debugSession, limit -> {
330         final List<ObjectReference> instances = ref.instances(limit);
331         return instances == null ? Collections.emptyList() : instances;
332       }, ref.name()).show();
333     }
334   }
335
336   private void commitAllTrackers() {
337     myConstructorTrackedClasses.values().forEach(ConstructorInstancesTracker::commitTracked);
338   }
339
340   private void updateClassesAndCounts(boolean immediate) {
341     ApplicationManager.getApplication().invokeLater(() -> {
342       final XDebugSession debugSession = XDebuggerManager.getInstance(myProject).getCurrentSession();
343       if (debugSession != null) {
344         final DebugProcess debugProcess = DebuggerManager.getInstance(myProject)
345           .getDebugProcess(debugSession.getDebugProcess().getProcessHandler());
346         if (debugProcess != null && debugProcess.isAttached() && debugProcess instanceof DebugProcessImpl) {
347           final DebugProcessImpl process = (DebugProcessImpl)debugProcess;
348           final SuspendContextImpl context = process.getDebuggerContext().getSuspendContext();
349           if (context != null) {
350             if (immediate) {
351               mySingleAlarm.cancelAndRequestImmediate(context);
352             }
353             else {
354               mySingleAlarm.cancelAndRequest(context);
355             }
356           }
357         }
358       }
359     }, myProject.getDisposed());
360   }
361
362   private static ActionPopupMenu createContextMenu() {
363     final ActionGroup group = (ActionGroup)ActionManager.getInstance().getAction("MemoryView.ClassesPopupActionGroup");
364     return ActionManager.getInstance().createActionPopupMenu("MemoryView.ClassesPopupActionGroup", group);
365   }
366
367   @Override
368   public void dispose() {
369     myConstructorTrackedClasses.clear();
370   }
371
372   public void setActive(boolean active, @NotNull DebuggerManagerThreadImpl managerThread) {
373     if (myIsActive == active) {
374       return;
375     }
376
377     myIsActive = active;
378
379     managerThread.schedule(new DebuggerCommandImpl() {
380       @Override
381       protected void action() {
382         if (active) {
383           doActivate();
384         }
385         else {
386           doPause();
387         }
388       }
389     });
390   }
391
392   private void doActivate() {
393     myDebugSessionListener.setActive(true);
394     myConstructorTrackedClasses.values().forEach(x -> x.setBackgroundMode(false));
395
396     if (isNeedUpdateView()) {
397       if (MemoryViewManager.getInstance().isAutoUpdateModeEnabled()) {
398         updateClassesAndCounts(true);
399       }
400       else {
401         makeTableClickable();
402       }
403     }
404   }
405
406   private void makeTableClickable() {
407     ApplicationManager.getApplication().invokeLater(
408       () -> myTable.makeClickable(CLICKABLE_TABLE_CONTENT, () -> updateClassesAndCounts(true)));
409   }
410
411   private void doPause() {
412     myDebugSessionListener.setActive(false);
413     mySingleAlarm.cancelAllRequests();
414     myConstructorTrackedClasses.values().forEach(x -> x.setBackgroundMode(true));
415   }
416
417   private boolean isNeedUpdateView() {
418     return myLastUpdatingTime.get() != myTime.get();
419   }
420
421   private void viewUpdated() {
422     myLastUpdatingTime.set(myTime.get());
423   }
424
425   private final class MyUpdateClassesCommand extends LowestPriorityCommand {
426
427     MyUpdateClassesCommand(@Nullable SuspendContextImpl suspendContext) {
428       super(suspendContext);
429     }
430
431     @Override
432     public void contextAction(@NotNull SuspendContextImpl suspendContext) {
433       handleTrackers();
434
435       final VirtualMachineProxyImpl proxy = suspendContext.getDebugProcess().getVirtualMachineProxy();
436       final List<ReferenceType> classes = proxy.allClasses();
437
438       if (!classes.isEmpty()) {
439         final VirtualMachine vm = classes.get(0).virtualMachine();
440         if (proxy.canGetInstanceInfo()) {
441           final Map<ReferenceType, Long> counts = getInstancesCounts(classes, vm);
442           ApplicationManager.getApplication().invokeLater(() -> myTable.updateContent(counts));
443         }
444         else {
445           ApplicationManager.getApplication().invokeLater(() -> myTable.updateClassesOnly(classes));
446         }
447       }
448
449       ApplicationManager.getApplication().invokeLater(() -> myTable.setBusy(false));
450       viewUpdated();
451     }
452
453     private void handleTrackers() {
454       if (!myIsTrackersActivated.get()) {
455         myConstructorTrackedClasses.values().forEach(ConstructorInstancesTracker::enable);
456         myIsTrackersActivated.set(true);
457       }
458       else {
459         commitAllTrackers();
460       }
461     }
462
463     private Map<ReferenceType, Long> getInstancesCounts(@NotNull List<ReferenceType> classes, @NotNull VirtualMachine vm) {
464       final int batchSize = DebuggerUtils.isAndroidVM(vm)
465                             ? AndroidUtil.ANDROID_COUNT_BY_CLASSES_BATCH_SIZE
466                             : DEFAULT_BATCH_SIZE;
467
468       final int size = classes.size();
469       final Map<ReferenceType, Long> result = new LinkedHashMap<>();
470
471       for (int begin = 0, end = Math.min(batchSize, size);
472            begin != size;
473            begin = end, end = Math.min(end + batchSize, size)) {
474         final List<ReferenceType> batch = classes.subList(begin, end);
475
476         final long start = System.nanoTime();
477         final long[] counts = vm.instanceCounts(batch);
478         final long delay = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
479
480         for (int i = 0; i < batch.size(); i++) {
481           result.put(batch.get(i), counts[i]);
482         }
483
484         final int waitTime = (int)Math.min(DELAY_BEFORE_INSTANCES_QUERY_COEFFICIENT * delay, MAX_DELAY_MILLIS);
485         mySingleAlarm.setDelay(waitTime);
486         LOG.debug(String.format("Instances query time = %d ms. Count of classes = %d", delay, batch.size()));
487       }
488
489       return result;
490     }
491   }
492
493   private static class FilterTextField extends SearchTextField {
494     FilterTextField() {
495       super(false);
496     }
497
498     @Override
499     protected void showPopup() {
500     }
501
502     @Override
503     protected boolean hasIconsOutsideOfTextField() {
504       return false;
505     }
506   }
507
508   private class MyOpenNewInstancesListener extends MouseAdapter {
509     @Override
510     public void mouseClicked(MouseEvent e) {
511       if (e.getClickCount() != 1 || e.getButton() != MouseEvent.BUTTON1 || !isShowNewInstancesEvent(e)) {
512         return;
513       }
514
515       final ReferenceType ref = myTable.getSelectedClass();
516       final TrackerForNewInstances strategy = ref == null ? null : getStrategy(ref);
517       XDebugSession debugSession = XDebuggerManager.getInstance(myProject).getCurrentSession();
518       if (strategy != null && debugSession != null) {
519         final DebugProcess debugProcess =
520           DebuggerManager.getInstance(myProject).getDebugProcess(debugSession.getDebugProcess().getProcessHandler());
521         final MemoryViewDebugProcessData data = debugProcess.getUserData(MemoryViewDebugProcessData.KEY);
522         if (data != null) {
523           final List<ObjectReference> newInstances = strategy.getNewInstances();
524           data.getTrackedStacks().pinStacks(ref);
525           final InstancesWindow instancesWindow = new InstancesWindow(debugSession, limit -> newInstances, ref.name());
526           Disposer.register(instancesWindow.getDisposable(), () -> data.getTrackedStacks().unpinStacks(ref));
527           instancesWindow.show();
528         }
529         else {
530           LOG.warn("MemoryViewDebugProcessData not found in debug session user data");
531         }
532       }
533     }
534   }
535
536   private class MyDoubleClickListener extends DoubleClickListener {
537     @Override
538     protected boolean onDoubleClick(MouseEvent event) {
539       if (!isShowNewInstancesEvent(event)) {
540         handleClassSelection(myTable.getSelectedClass());
541         return true;
542       }
543
544       return false;
545     }
546   }
547
548   private class MyMouseMotionListener implements MouseMotionListener {
549     @Override
550     public void mouseDragged(MouseEvent e) {
551     }
552
553     @Override
554     public void mouseMoved(MouseEvent e) {
555       if (myTable.isInClickableMode()) return;
556
557       if (isShowNewInstancesEvent(e)) {
558         myTable.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
559       }
560       else {
561         myTable.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
562       }
563     }
564   }
565
566   private boolean isShowNewInstancesEvent(@NotNull MouseEvent e) {
567     final int col = myTable.columnAtPoint(e.getPoint());
568     final int row = myTable.rowAtPoint(e.getPoint());
569     if (col == -1 || row == -1 || myTable.convertColumnIndexToModel(col) != DIFF_COLUMN_INDEX) {
570       return false;
571     }
572
573     final int modelRow = myTable.convertRowIndexToModel(row);
574
575     final ReferenceType ref = (ReferenceType)myTable.getModel().getValueAt(modelRow, CLASSNAME_COLUMN_INDEX);
576     final ConstructorInstancesTracker tracker = myConstructorTrackedClasses.getOrDefault(ref, null);
577
578     return tracker != null && tracker.isReady() && tracker.getCount() > 0;
579   }
580
581   private class MyDebuggerSessionListener implements XDebugSessionListener {
582     private volatile boolean myIsActive = false;
583
584     void setActive(boolean value) {
585       myIsActive = value;
586     }
587
588     @Override
589     public void sessionResumed() {
590       if (myIsActive) {
591         myConstructorTrackedClasses.values().forEach(ConstructorInstancesTracker::obsolete);
592         ApplicationManager.getApplication().invokeLater(() -> myTable.hideContent(EMPTY_TABLE_CONTENT_WHEN_RUNNING));
593
594         mySingleAlarm.cancelAllRequests();
595       }
596     }
597
598     @Override
599     public void sessionStopped() {
600       myConstructorTrackedClasses.values().forEach(Disposer::dispose);
601       myConstructorTrackedClasses.clear();
602       mySingleAlarm.cancelAllRequests();
603       ApplicationManager.getApplication().invokeLater(() -> myTable.clean(EMPTY_TABLE_CONTENT_WHEN_STOPPED));
604     }
605
606     @Override
607     public void sessionPaused() {
608       myTime.incrementAndGet();
609       if (myIsActive) {
610         if (MemoryViewManager.getInstance().isAutoUpdateModeEnabled()) {
611           updateClassesAndCounts(false);
612         }
613         else {
614           makeTableClickable();
615         }
616       }
617     }
618   }
619 }