eclipse-jdt / eclipse-jdt/eclipse.jdt.core
`JavaModelCache.getInfo()` is overwhelmed by increased model element count
- Dominant language
- Java
- Stars
- 237
- Forks
- 195
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 47
Description
During analysis of two heapdumps shared [here](https://github.com/eclipse-pde/eclipse.pde/pull/2253#issuecomment-4054961583) the following issue was discovered as a hotspot that can benefit from optimization:
### Performance Data
| Metric | WITH transitive | WITHOUT transitive | Ratio |
|--------|----------------:|-------------------:|------:|
| Self time (µs) | 10,508,208 | 2,089,167 | **5.0×** |
| HashMap.getNode() (µs) | 46,845,862 | 19,922,989 | **2.4×** |
| HashMap.resize() (µs) | 22,741,011 | 7,826,247 | **2.9×** |
| HashMap.put() (µs) | 9,616,898 | 706,988 | **13.6×** |
### Description
`JavaModelCache.getInfo()` dispatches to different caches (`rootCache`, `pkgCache`, `openableCache`, `childrenCache`) based on element type. The caches use either `ElementCache` (an LRU cache) or `HashMap`. The dramatic increase in `HashMap.resize()` time (2.9×) and `HashMap.put()` time (13.6×) suggests the hash maps are growing well beyond their initial capacity with transitive dependencies, causing frequent resizing and poor hash distribution.
Default cache sizes:
```java
public static final int DEFAULT_ROOT_SIZE = 2_000;
public static final int DEFAULT_PKG_SIZE = 20_000;
public static final int DEFAULT_OPENABLE_SIZE = 200_000;
```
With transitive dependencies, the number of package fragment roots and packages may exceed these defaults, causing LRU evictions (thrashing) or excessive HashMap growth.
### Suggested Fix
1. **Increase default cache sizes** or make them scale proportionally to the workspace's actual classpath size.
2. **Pre-size HashMaps**: The `childrenCache` and `projectCache` are `HashMap` instances that start at default capacity. Pre-sizing them based on the expected number of elements would avoid costly `resize()` operations.
3. **Review `hashCode()` implementations** for `PackageFragment`, `PackageFragmentRoot`, and other elements used as keys. The 2.4× slowdown in `HashMap.getNode()` may indicate hash collisions causing linear probing within buckets.
4. **Consider `HashMap.put()` 13.6× regression**: This dramatic slowdown suggests that the WITH configuration is repeatedly putting new entries (not updates), overwhelming the maps. Investigate whether entries are being created but never re-used due to object identity vs. equality issues.
Contributor guide
Assessment
This issue has not been assessed yet.