1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ddmuilib; 18 19 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; 20 import com.android.ddmlib.Client; 21 import com.android.ddmlib.ClientData; 22 import com.android.ddmlib.HeapSegment.HeapSegmentElement; 23 import com.android.ddmlib.Log; 24 25 import org.eclipse.jface.preference.IPreferenceStore; 26 import org.eclipse.swt.SWT; 27 import org.eclipse.swt.SWTException; 28 import org.eclipse.swt.custom.StackLayout; 29 import org.eclipse.swt.events.SelectionAdapter; 30 import org.eclipse.swt.events.SelectionEvent; 31 import org.eclipse.swt.graphics.Color; 32 import org.eclipse.swt.graphics.Font; 33 import org.eclipse.swt.graphics.FontData; 34 import org.eclipse.swt.graphics.GC; 35 import org.eclipse.swt.graphics.Image; 36 import org.eclipse.swt.graphics.ImageData; 37 import org.eclipse.swt.graphics.PaletteData; 38 import org.eclipse.swt.graphics.Point; 39 import org.eclipse.swt.graphics.RGB; 40 import org.eclipse.swt.layout.GridData; 41 import org.eclipse.swt.layout.GridLayout; 42 import org.eclipse.swt.widgets.Button; 43 import org.eclipse.swt.widgets.Combo; 44 import org.eclipse.swt.widgets.Composite; 45 import org.eclipse.swt.widgets.Control; 46 import org.eclipse.swt.widgets.Display; 47 import org.eclipse.swt.widgets.Group; 48 import org.eclipse.swt.widgets.Label; 49 import org.eclipse.swt.widgets.Table; 50 import org.eclipse.swt.widgets.TableColumn; 51 import org.eclipse.swt.widgets.TableItem; 52 import org.jfree.chart.ChartFactory; 53 import org.jfree.chart.JFreeChart; 54 import org.jfree.chart.axis.CategoryAxis; 55 import org.jfree.chart.axis.CategoryLabelPositions; 56 import org.jfree.chart.labels.CategoryToolTipGenerator; 57 import org.jfree.chart.plot.CategoryPlot; 58 import org.jfree.chart.plot.Plot; 59 import org.jfree.chart.plot.PlotOrientation; 60 import org.jfree.chart.renderer.category.CategoryItemRenderer; 61 import org.jfree.chart.title.TextTitle; 62 import org.jfree.data.category.CategoryDataset; 63 import org.jfree.data.category.DefaultCategoryDataset; 64 import org.jfree.experimental.chart.swt.ChartComposite; 65 import org.jfree.experimental.swt.SWTUtils; 66 67 import java.io.ByteArrayInputStream; 68 import java.io.IOException; 69 import java.io.InputStream; 70 import java.text.NumberFormat; 71 import java.util.ArrayList; 72 import java.util.Iterator; 73 import java.util.Map; 74 import java.util.Set; 75 76 77 /** 78 * Base class for our information panels. 79 */ 80 public final class HeapPanel extends BaseHeapPanel { 81 private static final String PREFS_STATS_COL_TYPE = "heapPanel.col0"; //$NON-NLS-1$ 82 private static final String PREFS_STATS_COL_COUNT = "heapPanel.col1"; //$NON-NLS-1$ 83 private static final String PREFS_STATS_COL_SIZE = "heapPanel.col2"; //$NON-NLS-1$ 84 private static final String PREFS_STATS_COL_SMALLEST = "heapPanel.col3"; //$NON-NLS-1$ 85 private static final String PREFS_STATS_COL_LARGEST = "heapPanel.col4"; //$NON-NLS-1$ 86 private static final String PREFS_STATS_COL_MEDIAN = "heapPanel.col5"; //$NON-NLS-1$ 87 private static final String PREFS_STATS_COL_AVERAGE = "heapPanel.col6"; //$NON-NLS-1$ 88 89 /* args to setUpdateStatus() */ 90 private static final int NOT_SELECTED = 0; 91 private static final int NOT_ENABLED = 1; 92 private static final int ENABLED = 2; 93 94 /** color palette and map legend. NATIVE is the last enum is a 0 based enum list, so we need 95 * Native+1 at least. We also need 2 more entries for free area and expansion area. */ 96 private static final int NUM_PALETTE_ENTRIES = HeapSegmentElement.KIND_NATIVE+2 +1; 97 private static final String[] mMapLegend = new String[NUM_PALETTE_ENTRIES]; 98 private static final PaletteData mMapPalette = createPalette(); 99 100 private static final boolean DISPLAY_HEAP_BITMAP = false; 101 private static final boolean DISPLAY_HILBERT_BITMAP = false; 102 103 private static final int PLACEHOLDER_HILBERT_SIZE = 200; 104 private static final int PLACEHOLDER_LINEAR_V_SIZE = 100; 105 private static final int PLACEHOLDER_LINEAR_H_SIZE = 300; 106 107 private static final int[] ZOOMS = {100, 50, 25}; 108 109 private static final NumberFormat sByteFormatter = NumberFormat.getInstance(); 110 private static final NumberFormat sLargeByteFormatter = NumberFormat.getInstance(); 111 private static final NumberFormat sCountFormatter = NumberFormat.getInstance(); 112 113 static { 114 sByteFormatter.setMinimumFractionDigits(0); 115 sByteFormatter.setMaximumFractionDigits(1); 116 sLargeByteFormatter.setMinimumFractionDigits(3); 117 sLargeByteFormatter.setMaximumFractionDigits(3); 118 119 sCountFormatter.setGroupingUsed(true); 120 } 121 122 private Display mDisplay; 123 124 private Composite mTop; // real top 125 private Label mUpdateStatus; 126 private Table mHeapSummary; 127 private Combo mDisplayMode; 128 129 //private ScrolledComposite mScrolledComposite; 130 131 private Composite mDisplayBase; // base of the displays. 132 private StackLayout mDisplayStack; 133 134 private Composite mStatisticsBase; 135 private Table mStatisticsTable; 136 private JFreeChart mChart; 137 private ChartComposite mChartComposite; 138 private Button mGcButton; 139 private DefaultCategoryDataset mAllocCountDataSet; 140 141 private Composite mLinearBase; 142 private Label mLinearHeapImage; 143 144 private Composite mHilbertBase; 145 private Label mHilbertHeapImage; 146 private Group mLegend; 147 private Combo mZoom; 148 149 /** Image used for the hilbert display. Since we recreate a new image every time, we 150 * keep this one around to dispose it. */ 151 private Image mHilbertImage; 152 private Image mLinearImage; 153 private Composite[] mLayout; 154 155 /* 156 * Create color palette for map. Set up titles for legend. 157 */ createPalette()158 private static PaletteData createPalette() { 159 RGB colors[] = new RGB[NUM_PALETTE_ENTRIES]; 160 colors[0] 161 = new RGB(192, 192, 192); // non-heap pixels are gray 162 mMapLegend[0] 163 = "(heap expansion area)"; 164 165 colors[1] 166 = new RGB(0, 0, 0); // free chunks are black 167 mMapLegend[1] 168 = "free"; 169 170 colors[HeapSegmentElement.KIND_OBJECT + 2] 171 = new RGB(0, 0, 255); // objects are blue 172 mMapLegend[HeapSegmentElement.KIND_OBJECT + 2] 173 = "data object"; 174 175 colors[HeapSegmentElement.KIND_CLASS_OBJECT + 2] 176 = new RGB(0, 255, 0); // class objects are green 177 mMapLegend[HeapSegmentElement.KIND_CLASS_OBJECT + 2] 178 = "class object"; 179 180 colors[HeapSegmentElement.KIND_ARRAY_1 + 2] 181 = new RGB(255, 0, 0); // byte/bool arrays are red 182 mMapLegend[HeapSegmentElement.KIND_ARRAY_1 + 2] 183 = "1-byte array (byte[], boolean[])"; 184 185 colors[HeapSegmentElement.KIND_ARRAY_2 + 2] 186 = new RGB(255, 128, 0); // short/char arrays are orange 187 mMapLegend[HeapSegmentElement.KIND_ARRAY_2 + 2] 188 = "2-byte array (short[], char[])"; 189 190 colors[HeapSegmentElement.KIND_ARRAY_4 + 2] 191 = new RGB(255, 255, 0); // obj/int/float arrays are yellow 192 mMapLegend[HeapSegmentElement.KIND_ARRAY_4 + 2] 193 = "4-byte array (object[], int[], float[])"; 194 195 colors[HeapSegmentElement.KIND_ARRAY_8 + 2] 196 = new RGB(255, 128, 128); // long/double arrays are pink 197 mMapLegend[HeapSegmentElement.KIND_ARRAY_8 + 2] 198 = "8-byte array (long[], double[])"; 199 200 colors[HeapSegmentElement.KIND_UNKNOWN + 2] 201 = new RGB(255, 0, 255); // unknown objects are cyan 202 mMapLegend[HeapSegmentElement.KIND_UNKNOWN + 2] 203 = "unknown object"; 204 205 colors[HeapSegmentElement.KIND_NATIVE + 2] 206 = new RGB(64, 64, 64); // native objects are dark gray 207 mMapLegend[HeapSegmentElement.KIND_NATIVE + 2] 208 = "non-Java object"; 209 210 return new PaletteData(colors); 211 } 212 213 /** 214 * Sent when an existing client information changed. 215 * <p/> 216 * This is sent from a non UI thread. 217 * @param client the updated client. 218 * @param changeMask the bit mask describing the changed properties. It can contain 219 * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME} 220 * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE}, 221 * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE}, 222 * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA} 223 * 224 * @see IClientChangeListener#clientChanged(Client, int) 225 */ 226 @Override clientChanged(final Client client, int changeMask)227 public void clientChanged(final Client client, int changeMask) { 228 if (client == getCurrentClient()) { 229 if ((changeMask & Client.CHANGE_HEAP_MODE) == Client.CHANGE_HEAP_MODE || 230 (changeMask & Client.CHANGE_HEAP_DATA) == Client.CHANGE_HEAP_DATA) { 231 try { 232 mTop.getDisplay().asyncExec(new Runnable() { 233 @Override 234 public void run() { 235 clientSelected(); 236 } 237 }); 238 } catch (SWTException e) { 239 // display is disposed (app is quitting most likely), we do nothing. 240 } 241 } 242 } 243 } 244 245 /** 246 * Sent when a new device is selected. The new device can be accessed 247 * with {@link #getCurrentDevice()} 248 */ 249 @Override deviceSelected()250 public void deviceSelected() { 251 // pass 252 } 253 254 /** 255 * Sent when a new client is selected. The new client can be accessed 256 * with {@link #getCurrentClient()}. 257 */ 258 @Override clientSelected()259 public void clientSelected() { 260 if (mTop.isDisposed()) 261 return; 262 263 Client client = getCurrentClient(); 264 265 Log.d("ddms", "HeapPanel: changed " + client); 266 267 if (client != null) { 268 ClientData cd = client.getClientData(); 269 270 if (client.isHeapUpdateEnabled()) { 271 mGcButton.setEnabled(true); 272 mDisplayMode.setEnabled(true); 273 setUpdateStatus(ENABLED); 274 } else { 275 setUpdateStatus(NOT_ENABLED); 276 mGcButton.setEnabled(false); 277 mDisplayMode.setEnabled(false); 278 } 279 280 fillSummaryTable(cd); 281 282 int mode = mDisplayMode.getSelectionIndex(); 283 if (mode == 0) { 284 fillDetailedTable(client, false /* forceRedraw */); 285 } else { 286 if (DISPLAY_HEAP_BITMAP) { 287 renderHeapData(cd, mode - 1, false /* forceRedraw */); 288 } 289 } 290 } else { 291 mGcButton.setEnabled(false); 292 mDisplayMode.setEnabled(false); 293 fillSummaryTable(null); 294 fillDetailedTable(null, true); 295 setUpdateStatus(NOT_SELECTED); 296 } 297 298 // sizes of things change frequently, so redo layout 299 //mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT, 300 // SWT.DEFAULT)); 301 mDisplayBase.layout(); 302 //mScrolledComposite.redraw(); 303 } 304 305 /** 306 * Create our control(s). 307 */ 308 @Override createControl(Composite parent)309 protected Control createControl(Composite parent) { 310 mDisplay = parent.getDisplay(); 311 312 GridLayout gl; 313 314 mTop = new Composite(parent, SWT.NONE); 315 mTop.setLayout(new GridLayout(1, false)); 316 mTop.setLayoutData(new GridData(GridData.FILL_BOTH)); 317 318 mUpdateStatus = new Label(mTop, SWT.NONE); 319 setUpdateStatus(NOT_SELECTED); 320 321 Composite summarySection = new Composite(mTop, SWT.NONE); 322 summarySection.setLayout(gl = new GridLayout(2, false)); 323 gl.marginHeight = gl.marginWidth = 0; 324 325 mHeapSummary = createSummaryTable(summarySection); 326 mGcButton = new Button(summarySection, SWT.PUSH); 327 mGcButton.setText("Cause GC"); 328 mGcButton.setEnabled(false); 329 mGcButton.addSelectionListener(new SelectionAdapter() { 330 @Override 331 public void widgetSelected(SelectionEvent e) { 332 Client client = getCurrentClient(); 333 if (client != null) { 334 client.executeGarbageCollector(); 335 } 336 } 337 }); 338 339 Composite comboSection = new Composite(mTop, SWT.NONE); 340 gl = new GridLayout(2, false); 341 gl.marginHeight = gl.marginWidth = 0; 342 comboSection.setLayout(gl); 343 344 Label displayLabel = new Label(comboSection, SWT.NONE); 345 displayLabel.setText("Display: "); 346 347 mDisplayMode = new Combo(comboSection, SWT.READ_ONLY); 348 mDisplayMode.setEnabled(false); 349 mDisplayMode.add("Stats"); 350 if (DISPLAY_HEAP_BITMAP) { 351 mDisplayMode.add("Linear"); 352 if (DISPLAY_HILBERT_BITMAP) { 353 mDisplayMode.add("Hilbert"); 354 } 355 } 356 357 // the base of the displays. 358 mDisplayBase = new Composite(mTop, SWT.NONE); 359 mDisplayBase.setLayoutData(new GridData(GridData.FILL_BOTH)); 360 mDisplayStack = new StackLayout(); 361 mDisplayBase.setLayout(mDisplayStack); 362 363 // create the statistics display 364 mStatisticsBase = new Composite(mDisplayBase, SWT.NONE); 365 //mStatisticsBase.setLayoutData(new GridData(GridData.FILL_BOTH)); 366 mStatisticsBase.setLayout(gl = new GridLayout(1, false)); 367 gl.marginHeight = gl.marginWidth = 0; 368 mDisplayStack.topControl = mStatisticsBase; 369 370 mStatisticsTable = createDetailedTable(mStatisticsBase); 371 mStatisticsTable.setLayoutData(new GridData(GridData.FILL_BOTH)); 372 373 createChart(); 374 375 //create the linear composite 376 mLinearBase = new Composite(mDisplayBase, SWT.NONE); 377 //mLinearBase.setLayoutData(new GridData()); 378 gl = new GridLayout(1, false); 379 gl.marginHeight = gl.marginWidth = 0; 380 mLinearBase.setLayout(gl); 381 382 { 383 mLinearHeapImage = new Label(mLinearBase, SWT.NONE); 384 mLinearHeapImage.setLayoutData(new GridData()); 385 mLinearHeapImage.setImage(ImageLoader.createPlaceHolderArt(mDisplay, 386 PLACEHOLDER_LINEAR_H_SIZE, PLACEHOLDER_LINEAR_V_SIZE, 387 mDisplay.getSystemColor(SWT.COLOR_BLUE))); 388 389 // create a composite to contain the bottom part (legend) 390 Composite bottomSection = new Composite(mLinearBase, SWT.NONE); 391 gl = new GridLayout(1, false); 392 gl.marginHeight = gl.marginWidth = 0; 393 bottomSection.setLayout(gl); 394 395 createLegend(bottomSection); 396 } 397 398 /* 399 mScrolledComposite = new ScrolledComposite(mTop, SWT.H_SCROLL | SWT.V_SCROLL); 400 mScrolledComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); 401 mScrolledComposite.setExpandHorizontal(true); 402 mScrolledComposite.setExpandVertical(true); 403 mScrolledComposite.setContent(mDisplayBase); 404 */ 405 406 407 // create the hilbert display. 408 mHilbertBase = new Composite(mDisplayBase, SWT.NONE); 409 //mHilbertBase.setLayoutData(new GridData()); 410 gl = new GridLayout(2, false); 411 gl.marginHeight = gl.marginWidth = 0; 412 mHilbertBase.setLayout(gl); 413 414 if (DISPLAY_HILBERT_BITMAP) { 415 mHilbertHeapImage = new Label(mHilbertBase, SWT.NONE); 416 mHilbertHeapImage.setLayoutData(new GridData()); 417 mHilbertHeapImage.setImage(ImageLoader.createPlaceHolderArt(mDisplay, 418 PLACEHOLDER_HILBERT_SIZE, PLACEHOLDER_HILBERT_SIZE, 419 mDisplay.getSystemColor(SWT.COLOR_BLUE))); 420 421 // create a composite to contain the right part (legend + zoom) 422 Composite rightSection = new Composite(mHilbertBase, SWT.NONE); 423 gl = new GridLayout(1, false); 424 gl.marginHeight = gl.marginWidth = 0; 425 rightSection.setLayout(gl); 426 427 Composite zoomComposite = new Composite(rightSection, SWT.NONE); 428 gl = new GridLayout(2, false); 429 zoomComposite.setLayout(gl); 430 431 Label l = new Label(zoomComposite, SWT.NONE); 432 l.setText("Zoom:"); 433 mZoom = new Combo(zoomComposite, SWT.READ_ONLY); 434 for (int z : ZOOMS) { 435 mZoom.add(String.format("%1$d%%", z)); //$NON-NLS-1$ 436 } 437 438 mZoom.select(0); 439 mZoom.addSelectionListener(new SelectionAdapter() { 440 @Override 441 public void widgetSelected(SelectionEvent e) { 442 setLegendText(mZoom.getSelectionIndex()); 443 Client client = getCurrentClient(); 444 if (client != null) { 445 renderHeapData(client.getClientData(), 1, true); 446 mTop.pack(); 447 } 448 } 449 }); 450 451 createLegend(rightSection); 452 } 453 mHilbertBase.pack(); 454 455 mLayout = new Composite[] { mStatisticsBase, mLinearBase, mHilbertBase }; 456 mDisplayMode.select(0); 457 mDisplayMode.addSelectionListener(new SelectionAdapter() { 458 @Override 459 public void widgetSelected(SelectionEvent e) { 460 int index = mDisplayMode.getSelectionIndex(); 461 Client client = getCurrentClient(); 462 463 if (client != null) { 464 if (index == 0) { 465 fillDetailedTable(client, true /* forceRedraw */); 466 } else { 467 renderHeapData(client.getClientData(), index-1, true /* forceRedraw */); 468 } 469 } 470 471 mDisplayStack.topControl = mLayout[index]; 472 //mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT, 473 // SWT.DEFAULT)); 474 mDisplayBase.layout(); 475 //mScrolledComposite.redraw(); 476 } 477 }); 478 479 //mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT, 480 // SWT.DEFAULT)); 481 mDisplayBase.layout(); 482 //mScrolledComposite.redraw(); 483 484 return mTop; 485 } 486 487 /** 488 * Sets the focus to the proper control inside the panel. 489 */ 490 @Override setFocus()491 public void setFocus() { 492 mHeapSummary.setFocus(); 493 } 494 495 createSummaryTable(Composite base)496 private Table createSummaryTable(Composite base) { 497 Table tab = new Table(base, SWT.SINGLE | SWT.FULL_SELECTION); 498 tab.setHeaderVisible(true); 499 tab.setLinesVisible(true); 500 501 TableColumn col; 502 503 col = new TableColumn(tab, SWT.RIGHT); 504 col.setText("ID"); 505 col.pack(); 506 507 col = new TableColumn(tab, SWT.RIGHT); 508 col.setText("000.000WW"); //$NON-NLS-1$ 509 col.pack(); 510 col.setText("Heap Size"); 511 512 col = new TableColumn(tab, SWT.RIGHT); 513 col.setText("000.000WW"); //$NON-NLS-1$ 514 col.pack(); 515 col.setText("Allocated"); 516 517 col = new TableColumn(tab, SWT.RIGHT); 518 col.setText("000.000WW"); //$NON-NLS-1$ 519 col.pack(); 520 col.setText("Free"); 521 522 col = new TableColumn(tab, SWT.RIGHT); 523 col.setText("000.00%"); //$NON-NLS-1$ 524 col.pack(); 525 col.setText("% Used"); 526 527 col = new TableColumn(tab, SWT.RIGHT); 528 col.setText("000,000,000"); //$NON-NLS-1$ 529 col.pack(); 530 col.setText("# Objects"); 531 532 // make sure there is always one empty item so that one table row is always displayed. 533 TableItem item = new TableItem(tab, SWT.NONE); 534 item.setText(""); 535 536 return tab; 537 } 538 createDetailedTable(Composite base)539 private Table createDetailedTable(Composite base) { 540 IPreferenceStore store = DdmUiPreferences.getStore(); 541 542 Table tab = new Table(base, SWT.SINGLE | SWT.FULL_SELECTION); 543 tab.setHeaderVisible(true); 544 tab.setLinesVisible(true); 545 546 TableHelper.createTableColumn(tab, "Type", SWT.LEFT, 547 "4-byte array (object[], int[], float[])", //$NON-NLS-1$ 548 PREFS_STATS_COL_TYPE, store); 549 550 TableHelper.createTableColumn(tab, "Count", SWT.RIGHT, 551 "00,000", //$NON-NLS-1$ 552 PREFS_STATS_COL_COUNT, store); 553 554 TableHelper.createTableColumn(tab, "Total Size", SWT.RIGHT, 555 "000.000 WW", //$NON-NLS-1$ 556 PREFS_STATS_COL_SIZE, store); 557 558 TableHelper.createTableColumn(tab, "Smallest", SWT.RIGHT, 559 "000.000 WW", //$NON-NLS-1$ 560 PREFS_STATS_COL_SMALLEST, store); 561 562 TableHelper.createTableColumn(tab, "Largest", SWT.RIGHT, 563 "000.000 WW", //$NON-NLS-1$ 564 PREFS_STATS_COL_LARGEST, store); 565 566 TableHelper.createTableColumn(tab, "Median", SWT.RIGHT, 567 "000.000 WW", //$NON-NLS-1$ 568 PREFS_STATS_COL_MEDIAN, store); 569 570 TableHelper.createTableColumn(tab, "Average", SWT.RIGHT, 571 "000.000 WW", //$NON-NLS-1$ 572 PREFS_STATS_COL_AVERAGE, store); 573 574 tab.addSelectionListener(new SelectionAdapter() { 575 @Override 576 public void widgetSelected(SelectionEvent e) { 577 578 Client client = getCurrentClient(); 579 if (client != null) { 580 int index = mStatisticsTable.getSelectionIndex(); 581 TableItem item = mStatisticsTable.getItem(index); 582 583 if (item != null) { 584 Map<Integer, ArrayList<HeapSegmentElement>> heapMap = 585 client.getClientData().getVmHeapData().getProcessedHeapMap(); 586 587 ArrayList<HeapSegmentElement> list = heapMap.get(item.getData()); 588 if (list != null) { 589 showChart(list); 590 } 591 } 592 } 593 594 } 595 }); 596 597 return tab; 598 } 599 600 /** 601 * Creates the chart below the statistics table 602 */ createChart()603 private void createChart() { 604 mAllocCountDataSet = new DefaultCategoryDataset(); 605 mChart = ChartFactory.createBarChart(null, "Size", "Count", mAllocCountDataSet, 606 PlotOrientation.VERTICAL, false, true, false); 607 608 // get the font to make a proper title. We need to convert the swt font, 609 // into an awt font. 610 Font f = mStatisticsBase.getFont(); 611 FontData[] fData = f.getFontData(); 612 613 // event though on Mac OS there could be more than one fontData, we'll only use 614 // the first one. 615 FontData firstFontData = fData[0]; 616 617 java.awt.Font awtFont = SWTUtils.toAwtFont(mStatisticsBase.getDisplay(), 618 firstFontData, true /* ensureSameSize */); 619 620 mChart.setTitle(new TextTitle("Allocation count per size", awtFont)); 621 622 Plot plot = mChart.getPlot(); 623 if (plot instanceof CategoryPlot) { 624 // get the plot 625 CategoryPlot categoryPlot = (CategoryPlot)plot; 626 627 // set the domain axis to draw labels that are displayed even with many values. 628 CategoryAxis domainAxis = categoryPlot.getDomainAxis(); 629 domainAxis.setCategoryLabelPositions(CategoryLabelPositions.DOWN_90); 630 631 CategoryItemRenderer renderer = categoryPlot.getRenderer(); 632 renderer.setBaseToolTipGenerator(new CategoryToolTipGenerator() { 633 @Override 634 public String generateToolTip(CategoryDataset dataset, int row, int column) { 635 // get the key for the size of the allocation 636 ByteLong columnKey = (ByteLong)dataset.getColumnKey(column); 637 String rowKey = (String)dataset.getRowKey(row); 638 Number value = dataset.getValue(rowKey, columnKey); 639 640 return String.format("%1$d %2$s of %3$d bytes", value.intValue(), rowKey, 641 columnKey.getValue()); 642 } 643 }); 644 } 645 mChartComposite = new ChartComposite(mStatisticsBase, SWT.BORDER, mChart, 646 ChartComposite.DEFAULT_WIDTH, 647 ChartComposite.DEFAULT_HEIGHT, 648 ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH, 649 ChartComposite.DEFAULT_MINIMUM_DRAW_HEIGHT, 650 3000, // max draw width. We don't want it to zoom, so we put a big number 651 3000, // max draw height. We don't want it to zoom, so we put a big number 652 true, // off-screen buffer 653 true, // properties 654 true, // save 655 true, // print 656 false, // zoom 657 true); // tooltips 658 659 mChartComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); 660 } 661 prettyByteCount(long bytes)662 private static String prettyByteCount(long bytes) { 663 double fracBytes = bytes; 664 String units = " B"; 665 if (fracBytes < 1024) { 666 return sByteFormatter.format(fracBytes) + units; 667 } else { 668 fracBytes /= 1024; 669 units = " KB"; 670 } 671 if (fracBytes >= 1024) { 672 fracBytes /= 1024; 673 units = " MB"; 674 } 675 if (fracBytes >= 1024) { 676 fracBytes /= 1024; 677 units = " GB"; 678 } 679 680 return sLargeByteFormatter.format(fracBytes) + units; 681 } 682 approximateByteCount(long bytes)683 private static String approximateByteCount(long bytes) { 684 double fracBytes = bytes; 685 String units = ""; 686 if (fracBytes >= 1024) { 687 fracBytes /= 1024; 688 units = "K"; 689 } 690 if (fracBytes >= 1024) { 691 fracBytes /= 1024; 692 units = "M"; 693 } 694 if (fracBytes >= 1024) { 695 fracBytes /= 1024; 696 units = "G"; 697 } 698 699 return sByteFormatter.format(fracBytes) + units; 700 } 701 addCommasToNumber(long num)702 private static String addCommasToNumber(long num) { 703 return sCountFormatter.format(num); 704 } 705 fractionalPercent(long num, long denom)706 private static String fractionalPercent(long num, long denom) { 707 double val = (double)num / (double)denom; 708 val *= 100; 709 710 NumberFormat nf = NumberFormat.getInstance(); 711 nf.setMinimumFractionDigits(2); 712 nf.setMaximumFractionDigits(2); 713 return nf.format(val) + "%"; 714 } 715 fillSummaryTable(ClientData cd)716 private void fillSummaryTable(ClientData cd) { 717 if (mHeapSummary.isDisposed()) { 718 return; 719 } 720 721 mHeapSummary.setRedraw(false); 722 mHeapSummary.removeAll(); 723 724 int numRows = 0; 725 if (cd != null) { 726 synchronized (cd) { 727 Iterator<Integer> iter = cd.getVmHeapIds(); 728 729 while (iter.hasNext()) { 730 numRows++; 731 Integer id = iter.next(); 732 Map<String, Long> heapInfo = cd.getVmHeapInfo(id); 733 if (heapInfo == null) { 734 continue; 735 } 736 long sizeInBytes = heapInfo.get(ClientData.HEAP_SIZE_BYTES); 737 long bytesAllocated = heapInfo.get(ClientData.HEAP_BYTES_ALLOCATED); 738 long objectsAllocated = heapInfo.get(ClientData.HEAP_OBJECTS_ALLOCATED); 739 740 TableItem item = new TableItem(mHeapSummary, SWT.NONE); 741 item.setText(0, id.toString()); 742 743 item.setText(1, prettyByteCount(sizeInBytes)); 744 item.setText(2, prettyByteCount(bytesAllocated)); 745 item.setText(3, prettyByteCount(sizeInBytes - bytesAllocated)); 746 item.setText(4, fractionalPercent(bytesAllocated, sizeInBytes)); 747 item.setText(5, addCommasToNumber(objectsAllocated)); 748 } 749 } 750 } 751 752 if (numRows == 0) { 753 // make sure there is always one empty item so that one table row is always displayed. 754 TableItem item = new TableItem(mHeapSummary, SWT.NONE); 755 item.setText(""); 756 } 757 758 mHeapSummary.pack(); 759 mHeapSummary.setRedraw(true); 760 } 761 fillDetailedTable(Client client, boolean forceRedraw)762 private void fillDetailedTable(Client client, boolean forceRedraw) { 763 // first check if the client is invalid or heap updates are not enabled. 764 if (client == null || client.isHeapUpdateEnabled() == false) { 765 mStatisticsTable.removeAll(); 766 showChart(null); 767 return; 768 } 769 770 ClientData cd = client.getClientData(); 771 772 Map<Integer, ArrayList<HeapSegmentElement>> heapMap; 773 774 // Atomically get and clear the heap data. 775 synchronized (cd) { 776 if (serializeHeapData(cd.getVmHeapData()) == false && forceRedraw == false) { 777 // no change, we return. 778 return; 779 } 780 781 heapMap = cd.getVmHeapData().getProcessedHeapMap(); 782 } 783 784 // we have new data, lets display it. 785 786 // First, get the current selection, and its key. 787 int index = mStatisticsTable.getSelectionIndex(); 788 Integer selectedKey = null; 789 if (index != -1) { 790 selectedKey = (Integer)mStatisticsTable.getItem(index).getData(); 791 } 792 793 // disable redraws and remove all from the table. 794 mStatisticsTable.setRedraw(false); 795 mStatisticsTable.removeAll(); 796 797 if (heapMap != null) { 798 int selectedIndex = -1; 799 ArrayList<HeapSegmentElement> selectedList = null; 800 801 // get the keys 802 Set<Integer> keys = heapMap.keySet(); 803 int iter = 0; // use a manual iter int because Set<?> doesn't have an index 804 // based accessor. 805 for (Integer key : keys) { 806 ArrayList<HeapSegmentElement> list = heapMap.get(key); 807 808 // check if this is the key that is supposed to be selected 809 if (key.equals(selectedKey)) { 810 selectedIndex = iter; 811 selectedList = list; 812 } 813 iter++; 814 815 TableItem item = new TableItem(mStatisticsTable, SWT.NONE); 816 item.setData(key); 817 818 // get the type 819 item.setText(0, mMapLegend[key]); 820 821 // set the count, smallest, largest 822 int count = list.size(); 823 item.setText(1, addCommasToNumber(count)); 824 825 if (count > 0) { 826 item.setText(3, prettyByteCount(list.get(0).getLength())); 827 item.setText(4, prettyByteCount(list.get(count-1).getLength())); 828 829 int median = count / 2; 830 HeapSegmentElement element = list.get(median); 831 long size = element.getLength(); 832 item.setText(5, prettyByteCount(size)); 833 834 long totalSize = 0; 835 for (int i = 0 ; i < count; i++) { 836 element = list.get(i); 837 838 size = element.getLength(); 839 totalSize += size; 840 } 841 842 // set the average and total 843 item.setText(2, prettyByteCount(totalSize)); 844 item.setText(6, prettyByteCount(totalSize / count)); 845 } 846 } 847 848 mStatisticsTable.setRedraw(true); 849 850 if (selectedIndex != -1) { 851 mStatisticsTable.setSelection(selectedIndex); 852 showChart(selectedList); 853 } else { 854 showChart(null); 855 } 856 } else { 857 mStatisticsTable.setRedraw(true); 858 } 859 } 860 861 private static class ByteLong implements Comparable<ByteLong> { 862 private long mValue; 863 ByteLong(long value)864 private ByteLong(long value) { 865 mValue = value; 866 } 867 getValue()868 public long getValue() { 869 return mValue; 870 } 871 872 @Override toString()873 public String toString() { 874 return approximateByteCount(mValue); 875 } 876 877 @Override compareTo(ByteLong other)878 public int compareTo(ByteLong other) { 879 if (mValue != other.mValue) { 880 return mValue < other.mValue ? -1 : 1; 881 } 882 return 0; 883 } 884 885 } 886 887 /** 888 * Fills the chart with the content of the list of {@link HeapSegmentElement}. 889 */ showChart(ArrayList<HeapSegmentElement> list)890 private void showChart(ArrayList<HeapSegmentElement> list) { 891 mAllocCountDataSet.clear(); 892 893 if (list != null) { 894 String rowKey = "Alloc Count"; 895 896 long currentSize = -1; 897 int currentCount = 0; 898 for (HeapSegmentElement element : list) { 899 if (element.getLength() != currentSize) { 900 if (currentSize != -1) { 901 ByteLong columnKey = new ByteLong(currentSize); 902 mAllocCountDataSet.addValue(currentCount, rowKey, columnKey); 903 } 904 905 currentSize = element.getLength(); 906 currentCount = 1; 907 } else { 908 currentCount++; 909 } 910 } 911 912 // add the last item 913 if (currentSize != -1) { 914 ByteLong columnKey = new ByteLong(currentSize); 915 mAllocCountDataSet.addValue(currentCount, rowKey, columnKey); 916 } 917 } 918 } 919 920 /* 921 * Add a color legend to the specified table. 922 */ createLegend(Composite parent)923 private void createLegend(Composite parent) { 924 mLegend = new Group(parent, SWT.NONE); 925 mLegend.setText(getLegendText(0)); 926 927 mLegend.setLayout(new GridLayout(2, false)); 928 929 RGB[] colors = mMapPalette.colors; 930 931 for (int i = 0; i < NUM_PALETTE_ENTRIES; i++) { 932 Image tmpImage = createColorRect(parent.getDisplay(), colors[i]); 933 934 Label l = new Label(mLegend, SWT.NONE); 935 l.setImage(tmpImage); 936 937 l = new Label(mLegend, SWT.NONE); 938 l.setText(mMapLegend[i]); 939 } 940 } 941 getLegendText(int level)942 private String getLegendText(int level) { 943 int bytes = 8 * (100 / ZOOMS[level]); 944 945 return String.format("Key (1 pixel = %1$d bytes)", bytes); 946 } 947 setLegendText(int level)948 private void setLegendText(int level) { 949 mLegend.setText(getLegendText(level)); 950 951 } 952 953 /* 954 * Create a nice rectangle in the specified color. 955 */ createColorRect(Display display, RGB color)956 private Image createColorRect(Display display, RGB color) { 957 int width = 32; 958 int height = 16; 959 960 Image img = new Image(display, width, height); 961 GC gc = new GC(img); 962 gc.setBackground(new Color(display, color)); 963 gc.fillRectangle(0, 0, width, height); 964 gc.dispose(); 965 return img; 966 } 967 968 969 /* 970 * Are updates enabled? 971 */ setUpdateStatus(int status)972 private void setUpdateStatus(int status) { 973 switch (status) { 974 case NOT_SELECTED: 975 mUpdateStatus.setText("Select a client to see heap updates"); 976 break; 977 case NOT_ENABLED: 978 mUpdateStatus.setText("Heap updates are " + 979 "NOT ENABLED for this client"); 980 break; 981 case ENABLED: 982 mUpdateStatus.setText("Heap updates will happen after " + 983 "every GC for this client"); 984 break; 985 default: 986 throw new RuntimeException(); 987 } 988 989 mUpdateStatus.pack(); 990 } 991 992 993 /** 994 * Return the closest power of two greater than or equal to value. 995 * 996 * @param value the return value will be >= value 997 * @return a power of two >= value. If value > 2^31, 2^31 is returned. 998 */ 999 //xxx use Integer.highestOneBit() or numberOfLeadingZeros(). nextPow2(int value)1000 private int nextPow2(int value) { 1001 for (int i = 31; i >= 0; --i) { 1002 if ((value & (1<<i)) != 0) { 1003 if (i < 31) { 1004 return 1<<(i + 1); 1005 } else { 1006 return 1<<31; 1007 } 1008 } 1009 } 1010 return 0; 1011 } 1012 zOrderData(ImageData id, byte pixData[])1013 private int zOrderData(ImageData id, byte pixData[]) { 1014 int maxX = 0; 1015 for (int i = 0; i < pixData.length; i++) { 1016 /* Tread the pixData index as a z-order curve index and 1017 * decompose into Cartesian coordinates. 1018 */ 1019 int x = (i & 1) | 1020 ((i >>> 2) & 1) << 1 | 1021 ((i >>> 4) & 1) << 2 | 1022 ((i >>> 6) & 1) << 3 | 1023 ((i >>> 8) & 1) << 4 | 1024 ((i >>> 10) & 1) << 5 | 1025 ((i >>> 12) & 1) << 6 | 1026 ((i >>> 14) & 1) << 7 | 1027 ((i >>> 16) & 1) << 8 | 1028 ((i >>> 18) & 1) << 9 | 1029 ((i >>> 20) & 1) << 10 | 1030 ((i >>> 22) & 1) << 11 | 1031 ((i >>> 24) & 1) << 12 | 1032 ((i >>> 26) & 1) << 13 | 1033 ((i >>> 28) & 1) << 14 | 1034 ((i >>> 30) & 1) << 15; 1035 int y = ((i >>> 1) & 1) << 0 | 1036 ((i >>> 3) & 1) << 1 | 1037 ((i >>> 5) & 1) << 2 | 1038 ((i >>> 7) & 1) << 3 | 1039 ((i >>> 9) & 1) << 4 | 1040 ((i >>> 11) & 1) << 5 | 1041 ((i >>> 13) & 1) << 6 | 1042 ((i >>> 15) & 1) << 7 | 1043 ((i >>> 17) & 1) << 8 | 1044 ((i >>> 19) & 1) << 9 | 1045 ((i >>> 21) & 1) << 10 | 1046 ((i >>> 23) & 1) << 11 | 1047 ((i >>> 25) & 1) << 12 | 1048 ((i >>> 27) & 1) << 13 | 1049 ((i >>> 29) & 1) << 14 | 1050 ((i >>> 31) & 1) << 15; 1051 try { 1052 id.setPixel(x, y, pixData[i]); 1053 if (x > maxX) { 1054 maxX = x; 1055 } 1056 } catch (IllegalArgumentException ex) { 1057 System.out.println("bad pixels: i " + i + 1058 ", w " + id.width + 1059 ", h " + id.height + 1060 ", x " + x + 1061 ", y " + y); 1062 throw ex; 1063 } 1064 } 1065 return maxX; 1066 } 1067 1068 private final static int HILBERT_DIR_N = 0; 1069 private final static int HILBERT_DIR_S = 1; 1070 private final static int HILBERT_DIR_E = 2; 1071 private final static int HILBERT_DIR_W = 3; 1072 hilbertWalk(ImageData id, InputStream pixData, int order, int x, int y, int dir)1073 private void hilbertWalk(ImageData id, InputStream pixData, 1074 int order, int x, int y, int dir) 1075 throws IOException { 1076 if (x >= id.width || y >= id.height) { 1077 return; 1078 } else if (order == 0) { 1079 try { 1080 int p = pixData.read(); 1081 if (p >= 0) { 1082 // flip along x=y axis; assume width == height 1083 id.setPixel(y, x, p); 1084 1085 /* Skanky; use an otherwise-unused ImageData field 1086 * to keep track of the max x,y used. Note that x and y are inverted. 1087 */ 1088 if (y > id.x) { 1089 id.x = y; 1090 } 1091 if (x > id.y) { 1092 id.y = x; 1093 } 1094 } 1095 //xxx just give up; don't bother walking the rest of the image 1096 } catch (IllegalArgumentException ex) { 1097 System.out.println("bad pixels: order " + order + 1098 ", dir " + dir + 1099 ", w " + id.width + 1100 ", h " + id.height + 1101 ", x " + x + 1102 ", y " + y); 1103 throw ex; 1104 } 1105 } else { 1106 order--; 1107 int delta = 1 << order; 1108 int nextX = x + delta; 1109 int nextY = y + delta; 1110 1111 switch (dir) { 1112 case HILBERT_DIR_E: 1113 hilbertWalk(id, pixData, order, x, y, HILBERT_DIR_N); 1114 hilbertWalk(id, pixData, order, x, nextY, HILBERT_DIR_E); 1115 hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_E); 1116 hilbertWalk(id, pixData, order, nextX, y, HILBERT_DIR_S); 1117 break; 1118 case HILBERT_DIR_N: 1119 hilbertWalk(id, pixData, order, x, y, HILBERT_DIR_E); 1120 hilbertWalk(id, pixData, order, nextX, y, HILBERT_DIR_N); 1121 hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_N); 1122 hilbertWalk(id, pixData, order, x, nextY, HILBERT_DIR_W); 1123 break; 1124 case HILBERT_DIR_S: 1125 hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_W); 1126 hilbertWalk(id, pixData, order, x, nextY, HILBERT_DIR_S); 1127 hilbertWalk(id, pixData, order, x, y, HILBERT_DIR_S); 1128 hilbertWalk(id, pixData, order, nextX, y, HILBERT_DIR_E); 1129 break; 1130 case HILBERT_DIR_W: 1131 hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_S); 1132 hilbertWalk(id, pixData, order, nextX, y, HILBERT_DIR_W); 1133 hilbertWalk(id, pixData, order, x, y, HILBERT_DIR_W); 1134 hilbertWalk(id, pixData, order, x, nextY, HILBERT_DIR_N); 1135 break; 1136 default: 1137 throw new RuntimeException("Unexpected Hilbert direction " + 1138 dir); 1139 } 1140 } 1141 } 1142 hilbertOrderData(ImageData id, byte pixData[])1143 private Point hilbertOrderData(ImageData id, byte pixData[]) { 1144 1145 int order = 0; 1146 for (int n = 1; n < id.width; n *= 2) { 1147 order++; 1148 } 1149 /* Skanky; use an otherwise-unused ImageData field 1150 * to keep track of maxX. 1151 */ 1152 Point p = new Point(0,0); 1153 int oldIdX = id.x; 1154 int oldIdY = id.y; 1155 id.x = id.y = 0; 1156 try { 1157 hilbertWalk(id, new ByteArrayInputStream(pixData), 1158 order, 0, 0, HILBERT_DIR_E); 1159 p.x = id.x; 1160 p.y = id.y; 1161 } catch (IOException ex) { 1162 System.err.println("Exception during hilbertWalk()"); 1163 p.x = id.height; 1164 p.y = id.width; 1165 } 1166 id.x = oldIdX; 1167 id.y = oldIdY; 1168 return p; 1169 } 1170 createHilbertHeapImage(byte pixData[])1171 private ImageData createHilbertHeapImage(byte pixData[]) { 1172 int w, h; 1173 1174 // Pick an image size that the largest of heaps will fit into. 1175 w = (int)Math.sqrt(((16 * 1024 * 1024)/8)); 1176 1177 // Space-filling curves require a power-of-2 width. 1178 w = nextPow2(w); 1179 h = w; 1180 1181 // Create the heap image. 1182 ImageData id = new ImageData(w, h, 8, mMapPalette); 1183 1184 // Copy the data into the image 1185 //int maxX = zOrderData(id, pixData); 1186 Point maxP = hilbertOrderData(id, pixData); 1187 1188 // update the max size to make it a round number once the zoom is applied 1189 int factor = 100 / ZOOMS[mZoom.getSelectionIndex()]; 1190 if (factor != 1) { 1191 int tmp = maxP.x % factor; 1192 if (tmp != 0) { 1193 maxP.x += factor - tmp; 1194 } 1195 1196 tmp = maxP.y % factor; 1197 if (tmp != 0) { 1198 maxP.y += factor - tmp; 1199 } 1200 } 1201 1202 if (maxP.y < id.height) { 1203 // Crop the image down to the interesting part. 1204 id = new ImageData(id.width, maxP.y, id.depth, id.palette, 1205 id.scanlinePad, id.data); 1206 } 1207 1208 if (maxP.x < id.width) { 1209 // crop the image again. A bit trickier this time. 1210 ImageData croppedId = new ImageData(maxP.x, id.height, id.depth, id.palette); 1211 1212 int[] buffer = new int[maxP.x]; 1213 for (int l = 0 ; l < id.height; l++) { 1214 id.getPixels(0, l, maxP.x, buffer, 0); 1215 croppedId.setPixels(0, l, maxP.x, buffer, 0); 1216 } 1217 1218 id = croppedId; 1219 } 1220 1221 // apply the zoom 1222 if (factor != 1) { 1223 id = id.scaledTo(id.width / factor, id.height / factor); 1224 } 1225 1226 return id; 1227 } 1228 1229 /** 1230 * Convert the raw heap data to an image. We know we're running in 1231 * the UI thread, so we can issue graphics commands directly. 1232 * 1233 * http://help.eclipse.org/help31/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/GC.html 1234 * 1235 * @param cd The client data 1236 * @param mode The display mode. 0 = linear, 1 = hilbert. 1237 * @param forceRedraw 1238 */ renderHeapData(ClientData cd, int mode, boolean forceRedraw)1239 private void renderHeapData(ClientData cd, int mode, boolean forceRedraw) { 1240 Image image; 1241 1242 byte[] pixData; 1243 1244 // Atomically get and clear the heap data. 1245 synchronized (cd) { 1246 if (serializeHeapData(cd.getVmHeapData()) == false && forceRedraw == false) { 1247 // no change, we return. 1248 return; 1249 } 1250 1251 pixData = getSerializedData(); 1252 } 1253 1254 if (pixData != null) { 1255 ImageData id; 1256 if (mode == 1) { 1257 id = createHilbertHeapImage(pixData); 1258 } else { 1259 id = createLinearHeapImage(pixData, 200, mMapPalette); 1260 } 1261 1262 image = new Image(mDisplay, id); 1263 } else { 1264 // Render a placeholder image. 1265 int width, height; 1266 if (mode == 1) { 1267 width = height = PLACEHOLDER_HILBERT_SIZE; 1268 } else { 1269 width = PLACEHOLDER_LINEAR_H_SIZE; 1270 height = PLACEHOLDER_LINEAR_V_SIZE; 1271 } 1272 image = new Image(mDisplay, width, height); 1273 GC gc = new GC(image); 1274 gc.setForeground(mDisplay.getSystemColor(SWT.COLOR_RED)); 1275 gc.drawLine(0, 0, width-1, height-1); 1276 gc.dispose(); 1277 gc = null; 1278 } 1279 1280 // set the new image 1281 1282 if (mode == 1) { 1283 if (mHilbertImage != null) { 1284 mHilbertImage.dispose(); 1285 } 1286 1287 mHilbertImage = image; 1288 mHilbertHeapImage.setImage(mHilbertImage); 1289 mHilbertHeapImage.pack(true); 1290 mHilbertBase.layout(); 1291 mHilbertBase.pack(true); 1292 } else { 1293 if (mLinearImage != null) { 1294 mLinearImage.dispose(); 1295 } 1296 1297 mLinearImage = image; 1298 mLinearHeapImage.setImage(mLinearImage); 1299 mLinearHeapImage.pack(true); 1300 mLinearBase.layout(); 1301 mLinearBase.pack(true); 1302 } 1303 } 1304 1305 @Override setTableFocusListener()1306 protected void setTableFocusListener() { 1307 addTableToFocusListener(mHeapSummary); 1308 } 1309 } 1310 1311