fix: #158: ClassCastException in ComponentReportQuery.addZeroLengthArrays#165
Open
fix: #158: ClassCastException in ComponentReportQuery.addZeroLengthArrays#165
Conversation
…rays
The addZeroLengthArrays method was reading the wrong column indices from
the arrays_grouped_by_size refined table, causing a ClassCastException
("Long cannot be cast to Integer") whenever the retained size of
zero-length arrays of a single type exceeded 5% of the component's
total retained size.
Root cause
----------
Both addZeroLengthArrays and the similar addEmptyCollections method
read column values from a Quantize-backed RefinedTable. The collections
query (collections_grouped_by_size) groups by a single key - the
collection size - so its column layout is:
col 0: collection size (Integer, key)
col 1: count (Integer, Quantize.COUNT)
col 2: shallow heap (Bytes)
col 3: retained size (Long, derived)
The array query (arrays_grouped_by_size) was changed in commit 185b069
(Bug 573258, April 2021) to group by TWO keys - array length AND
single-instance size - in order to distinguish, for example, byte[0]
from int[0] which have the same length but different heap footprints.
That shifted every non-key column right by one:
col 0: array length (Integer, key)
col 1: single-instance size (Long, key) <- new key column
col 2: count (Integer, Quantize.COUNT)
col 3: shallow heap (Bytes)
col 4: retained size (Long, derived)
addZeroLengthArrays was introduced in the same commit and was written
with the two-key layout for the retained-size read (col 4, correct),
but used the single-key layout for the count (col 1, wrong) and the
formatted retained-size string (col 3, wrong - that is shallow heap).
Why it went unnoticed for four years
-------------------------------------
The buggy casts are guarded by two conditions that are rarely true
simultaneously:
1. The most-frequently-seen array length for a given class is 0
(i.e. zero-length instances dominate that class by shallow heap).
2. The approximate retained size of those zero-length instances
exceeds totalSize / 20 (5% of the component's retained heap).
Standard heap dumps from typical applications almost never satisfy
condition 2. It requires a pathological allocation pattern where
zero-length arrays of a single type collectively retain a large
fraction of the heap.
Fix
---
- col 1 -> col 2 for numberOfObjects (correct Quantize.COUNT column)
- col 3 -> col 4 for retainedSize string (correct derived-data column,
also ensures the RetainedSizeFormat "~" approximation prefix is
applied rather than showing the shallow heap value)
Add testArraysBySizeColumnTypes to QueriesTest to pin the column
contract of arrays_grouped_by_size. The test runs on the existing
byte[] population of the standard test snapshot and will fail
immediately if the key-column count of ArraysBySizeQuery changes
without a corresponding update to ComponentReportQuery.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Should fix #158.
The addZeroLengthArrays method was reading the wrong column indices from the arrays_grouped_by_size refined table, causing a ClassCastException ("Long cannot be cast to Integer") whenever the retained size of zero-length arrays of a single type exceeded 5% of the component's total retained size.
Root cause
Both addZeroLengthArrays and the similar addEmptyCollections method read column values from a Quantize-backed RefinedTable. The collections query (collections_grouped_by_size) groups by a single key - the collection size - so its column layout is:
col 0: collection size (Integer, key)
col 1: count (Integer, Quantize.COUNT)
col 2: shallow heap (Bytes)
col 3: retained size (Long, derived)
The array query (arrays_grouped_by_size) was changed in commit 185b069 (Bug 573258, April 2021) to group by TWO keys - array length AND single-instance size - in order to distinguish, for example, byte[0] from int[0] which have the same length but different heap footprints. That shifted every non-key column right by one:
col 0: array length (Integer, key)
col 1: single-instance size (Long, key) <- new key column
col 2: count (Integer, Quantize.COUNT)
col 3: shallow heap (Bytes)
col 4: retained size (Long, derived)
addZeroLengthArrays was introduced in the same commit and was written with the two-key layout for the retained-size read (col 4, correct), but used the single-key layout for the count (col 1, wrong) and the formatted retained-size string (col 3, wrong - that is shallow heap).
Why it went unnoticed
The buggy casts are guarded by two conditions that are rarely true simultaneously:
Standard heap dumps from typical applications almost never satisfy condition 2. It requires a pathological allocation pattern where zero-length arrays of a single type collectively retain a large fraction of the heap.
Fix
Add testArraysBySizeColumnTypes to QueriesTest to pin the column contract of arrays_grouped_by_size. The test runs on the existing byte[] population of the standard test snapshot and will fail immediately if the key-column count of ArraysBySizeQuery changes without a corresponding update to ComponentReportQuery.