Topic: jvm
-
Which observability signals should a JVM or .NET service emit by default, and at what overhead?
Open question: both runtimes ship built-in telemetry (Flight Recorder and GC logging on the JVM; EventPipe counters and dotnet-trace on .NET) and both have OpenTelemetry auto-instrumentation, but there is little shared evidence on which of these should be always-on in production, what they cost, and which ones actually shortened incidents.
-
Writing a unit test in JUnit 5 and xUnit.net: annotations, lifecycle and parameterised cases side by side
JUnit Jupiter marks tests with @Test, runs @BeforeEach and @AfterEach around each one on a fresh instance by default, and drives data-driven cases with @ParameterizedTest plus a source annotation; xUnit.net uses [Fact], the constructor and IDisposable for per-test setup on a fresh instance, [Theory] with [InlineData] for cases, and fixtures for shared expensive context. Writing tests with the same shape in both keeps a polyglot team's conventions aligned.
-
Sizing a JVM inside a container: heap percentage, non-heap memory and CPU count
The JVM reads cgroup limits by default (UseContainerSupport) and sizes the heap as a percentage of the container's memory, not the host's; heap is only part of the footprint, so set MaxRAMPercentage to leave room for metaspace, thread stacks, direct buffers and code cache, check the CPU count the JVM sees, and verify with -Xlog:os+container and native memory tracking before trusting a limit.
-
Maven versus Gradle: what a newcomer needs to build someone else's JVM project
Maven describes a project declaratively in pom.xml and runs fixed lifecycle phases (validate, compile, test, package, verify, install, deploy) with plugin goals bound to them; Gradle runs a graph of tasks configured by build.gradle(.kts) scripts and plugins. Both resolve transitive dependencies but with different conflict rules (Maven: nearest definition; Gradle: highest version), and both ship a wrapper script that pins the build tool version.
-
Java records, sealed types and pattern matching for switch
Records (JDK 16) are transparent, immutable data carriers with a generated canonical constructor, accessors, equals, hashCode and toString; sealed types (JDK 17) restrict which classes may extend or implement them so that a pattern switch (JDK 21) over the hierarchy can be checked for exhaustiveness. Together they give Java a lightweight sum type.
-
JVM garbage collection: the collectors, the defaults and the few flags worth setting
HotSpot ships Serial, Parallel, G1 and ZGC; it picks G1 on machines it considers server-class (two or more processors and at least 1792 MB) and Serial otherwise, with a default maximum heap of a quarter of memory. For most services the flags worth setting are the maximum heap, GC logging, and only after reading logs a collector or a pause-time goal.
-
Java virtual threads in outline: what changes and what does not
JEP 444 (JDK 21) adds virtual threads: cheap threads scheduled by the JDK onto a small pool of carrier platform threads, which unmount while blocked on most JDK I/O so that thread-per-request code scales without an asynchronous style. They are not faster, must never be pooled, and until JEP 491 (JDK 24) blocking inside synchronized pinned the carrier.
-
Java streams versus loops: when a pipeline is clearer and when it is not
A stream is a lazy pipeline of intermediate operations closed by one terminal operation; it reads well for filter, map and collect over a collection, but the package documentation discourages side effects in the lambdas, a stream cannot be reused, checked exceptions do not fit, and a loop is clearer for early exit with state, index-based work and mutation.
Machine-readable: JSON