Describe the bug
I identified a resource management issue in org.jitsi.utils.TimeUtils. The class uses static ThreadLocal fields (trailingMilliFormat, nanosPerMilliFormat) to cache DecimalFormat instances. These ThreadLocals are initialized but never cleaned up (no remove() call).
Source Code Location:
// org/jitsi/utils/TimeUtils.java (Lines 166-180)
private static final ThreadLocal trailingMilliFormat =
ThreadLocal.withInitial(() -> new DecimalFormat("000"));
private static final ThreadLocal nanosPerMilliFormat =
ThreadLocal.withInitial(() -> { ... });
Impact
-
Blocker for Hot-Swapping: Jitsi components often run in modular environments (OSGi). Static ThreadLocal variables on long-lived worker threads hold strong references to the TimeUtils class and its ClassLoader. This prevents the bundle/module from being unloaded or updated at runtime, causing "Zombie Bundles" or Metaspace leaks upon reload.
-
State Consistency: DecimalFormat is mutable. If an exception occurs during a formatting operation (leaving internal state inconsistent), the polluted instance persists on the thread and may affect subsequent calls.
Expected behavior
Utility classes should avoid static ThreadLocal caches for lightweight objects like DecimalFormat unless strictly necessary and accompanied by a cleanup lifecycle. On modern JVMs, the cost of creating DecimalFormat is negligible compared to the architectural risk of memory leaks.
Suggested Fix
Remove the ThreadLocal cache and instantiate DecimalFormat as a local variable within formatTimeAsFullMillis.
Proposed Change:
public static String formatTimeAsFullMillis(long secs, int nanos) {
// ... logic ...
// Use local instance instead of ThreadLocal.get()
DecimalFormat trailingMilliFormat = new DecimalFormat("000");
builder.append(trailingMilliFormat.format(millis));
// ...
}
Context
File: org.jitsi.utils.TimeUtils
Concern: Memory Leaks / Module Lifecycle Management
Describe the bug
I identified a resource management issue in org.jitsi.utils.TimeUtils. The class uses static ThreadLocal fields (trailingMilliFormat, nanosPerMilliFormat) to cache DecimalFormat instances. These ThreadLocals are initialized but never cleaned up (no remove() call).
Source Code Location:
// org/jitsi/utils/TimeUtils.java (Lines 166-180)
private static final ThreadLocal trailingMilliFormat =
ThreadLocal.withInitial(() -> new DecimalFormat("000"));
private static final ThreadLocal nanosPerMilliFormat =
ThreadLocal.withInitial(() -> { ... });
Impact
Blocker for Hot-Swapping: Jitsi components often run in modular environments (OSGi). Static ThreadLocal variables on long-lived worker threads hold strong references to the TimeUtils class and its ClassLoader. This prevents the bundle/module from being unloaded or updated at runtime, causing "Zombie Bundles" or Metaspace leaks upon reload.
State Consistency: DecimalFormat is mutable. If an exception occurs during a formatting operation (leaving internal state inconsistent), the polluted instance persists on the thread and may affect subsequent calls.
Expected behavior
Utility classes should avoid static ThreadLocal caches for lightweight objects like DecimalFormat unless strictly necessary and accompanied by a cleanup lifecycle. On modern JVMs, the cost of creating DecimalFormat is negligible compared to the architectural risk of memory leaks.
Suggested Fix
Remove the ThreadLocal cache and instantiate DecimalFormat as a local variable within formatTimeAsFullMillis.
Proposed Change:
public static String formatTimeAsFullMillis(long secs, int nanos) {
// ... logic ...
}
Context
File: org.jitsi.utils.TimeUtils
Concern: Memory Leaks / Module Lifecycle Management