OpenHFT Java Thread Affinity for CPU-Pinned JVM Workloads
Bind a java thread to a given core
At a glance
- What is it?
- OpenHFT Java Thread Affinity binds Java threads to selected CPUs and supports placement relative to sockets and cores. It offers its fullest controls on Linux, but requires isolation, a reserved CPU mask, and JNA.
- Who is it for?
- Application teams that control Linux CPU isolation and can reserve non-overlapping cores should evaluate Java Thread Affinity for latency-sensitive JVM workloads. Teams on macOS or Solaris, or deployments that expect portable affinity control, should not adopt it for that purpose.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 28 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What AffinityControl Solves for JVM Latency Work
OpenHFT Java Thread Affinity addresses a narrow scheduling problem: keeping a chosen Java thread on a particular logical CPU. It is aimed at developers building JVM applications where thread placement matters, especially low-latency services and workloads that have already reserved dedicated processors. The repository describes the project as a way to "bind a thread to a given core" and says that doing so can improve performance. That is a conditional claim, not a guarantee, and the documentation specifically says the library works best on Linux.
The library is more than a single call to an operating-system affinity function. Its Java API can acquire either a logical CPU or a whole core, release the assignment after use, inspect the current thread, and place one lock relative to another according to socket and core relationships. This makes it relevant when several worker threads must be distributed without sending every new lock to an arbitrary processor.
It is the wrong abstraction for code that only needs larger thread pools, ordinary work-stealing behavior, or a general Java executor. Pinning narrows scheduler freedom and introduces operating-system and deployment responsibilities. Applications should adopt it only when controlled placement is part of the latency design. The README does not claim that affinity improves every workload, and it gives no benchmark results from which an application team could infer a performance gain.
Affinity.java and the Platform-Specific Native Layer
The documented architecture starts with platform detection in `Affinity.java`. That class selects an implementation for the running operating system, so callers do not have to select a Linux, Windows, macOS, or Solaris backend themselves. The resulting feature set is deliberately uneven. Linux provides the complete control surface through JNA: it can read and set thread affinity, query the current CPU, and obtain process and thread IDs.
Windows uses a kernel API for thread affinity. It can expose process and thread IDs, but `getCpu()` returns `-1`. macOS exposes process and thread IDs without changing affinity, and its CPU result is also `-1`. Solaris follows a similar reduced implementation, returning process and thread IDs without affinity or CPU-query support. A cross-platform application can therefore share the library dependency without receiving equivalent scheduling behavior on every supported system.
Native access depends on Java Native Access, or JNA. The documented dependency is JNA `4.4.0`, which in turn requires GLIBC `2.14` or newer for native invocation. On an older operating system, the README says to fork the repository and override the `<version>` values for `jna` and `jna-platform` in its `pom` file. That is a maintenance task for the adopting organization, not a documented drop-in compatibility guarantee. The architecture is clear, but portability means importing the API, not assuming every API operation works everywhere.
CPU Discovery, Isolation, and Reserved Bitmasks
Before assigning locks, the library must decide which processors are eligible. According to the README, it reads `/proc/cpuinfo` when available to determine the CPU layout. If that file is absent, it assumes that all CPUs belong to one socket. On Linux, it identifies processors unavailable to general use from the process affinity present at startup and begins assigning those processors first.
The intended deployment pattern uses `isolcpus`. Once specified on the Linux kernel command line, those CPUs do not participate in normal load balancing and do not receive tasks unless explicitly assigned. The README supplies boot configuration for both GRUB and systemd-boot. These commands alter kernel boot configuration, affect the whole host, and require a reboot. They are not application-only settings.
Allocation also needs process-level coordination. If several processes use the library, each needs a distinct CPU set, or both processes can receive the same assignments. The `affinity.reserved` system property supplies a hexadecimal bit mask without the `0x` prefix. Bit `0` maps to CPU `0`, bit `1` to CPU `1`, and additional bits select additional CPUs. For example, `2` selects CPU `1`, `6` selects CPUs `1` and `2`, while `10` selects CPUs `1` and `3`.
The documented commands are:
sudo apt-get install libjna-javaThis installs the Ubuntu JNA package. The CentOS equivalent is:
sudo yum install jnaFor Windows, the README gives:
choco install jnaIt also permits adding `jna.jar` and `jna-platform.jar` from the JNA project to the classpath. The README links the published Maven artifact as `net.openhft:affinity`, but it does not provide a complete Maven or Gradle dependency snippet, so this review does not invent one.
AffinityLock Acquisition for a First CPU-Bound Task
The first real use begins with an `AffinityLock`, not a conventional synchronized lock. The Java 6 form acquires a lock and releases it in a `finally` block:
AffinityLock al = AffinityLock.acquireLock();
try {
// do some work locked to a CPU.
} finally {
al.release();
}For Java 7 or Java 8, the same lifetime can be expressed with try-with-resources:
try (AffinityLock al = AffinityLock.acquireLock()) {
// do some work while locked to a CPU.
}The scope matters because the calling thread remains associated with the selected CPU until the lock is released or its scope closes. The comment inside the block is where latency-sensitive work would run. The README does not document behavior for a particular callback or executor integration at this point, so the documented pattern is direct lexical scope.
If hyper-threading is enabled and the application wants one logical CPU without its sibling handling work on the same core, the documented alternative reserves a core:
try (AffinityLock al = AffinityLock.acquireCore()) {
// do some work while locked to a CPU.
}The README states that this uses one CPU and leaves its twin CPU unused. That choice can simplify placement, but it also deliberately leaves capacity unused. A developer should verify the host's processor topology before choosing `acquireCore()` rather than `acquireLock()`. The documentation does not provide capacity calculations or a way to compare their contention behavior.
AffinityStrategies for Socket and Core Placement
Related locks can be placed according to topology through the `AffinityStrategies` enum. `ANY` accepts any available CPU. `SAME_CORE` requests a CPU on the same core. `SAME_SOCKET` requests a different core on the same socket, while `DIFFERENT_CORE` allows another core and may cross sockets. `DIFFERENT_SOCKET` requests a processor on another socket.
The README shows a parent lock acquired in the main thread and a child thread acquiring a related lock:
try (final AffinityLock al = AffinityLock.acquireLock()) {
System.out.println("Main locked");
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try (AffinityLock al2 = al.acquireLock(AffinityStrategies.SAME_SOCKET,
AffinityStrategies.ANY)) {
System.out.println("Thread-0 locked");
}
}
});
t.start();
}This call expresses a preference rather than an absolute reservation. The documentation says the library prefers a free CPU on the same socket as the first lock, then picks any free CPU if that preference cannot be met. The second argument, `ANY`, is the fallback. The source does not specify what happens when no eligible CPU remains, how contention is timed out, or whether calling threads waiting for the same placement are serialized. Those are concrete operational details to confirm in the test sources or code before relying on a placement strategy under saturation.
For diagnostics, the README also documents retrieval of the current Java thread ID through `AffinitySupport.getThreadId()`. CPU reporting is materially weaker outside Linux: Windows and macOS return `-1` from `getCpu()`, while Solaris has no CPU-query support. Logging the thread ID can confirm thread identity, but it does not provide portable evidence of the selected CPU.
Maintenance, Releases, and the Custom Licence
The repository is not archived, and its last push was on 2026-09-25. That status does not by itself guarantee compatibility with a particular JVM or kernel, but it establishes that the source was changed recently. Tagged releases include `Java-Thread-Affinity-2026.2` and `Java-Thread-Affinity-2026.1`, both dated 2026-01-28, and `Java-Thread-Affinity-3.26.9`, dated 2025-07-22.
The README points adopters to `RELEASE_NOTES.adoc` for detailed release notes and upgrade impact since 2020. Upgrade planning should start there because affinity behavior, platform support, or dependency requirements can affect deployment. The repository includes separate `affinity` and `affinity-test` modules at the top level, plus a Maven `pom.xml`.
The repository is labelled with a custom licence that GitHub cannot classify, and the licence text is stored in `LICENSE.adoc`. That matters for organizations screening dependencies. This review does not interpret or provide legal advice about redistribution, commercial use, or modification. Teams must read that file and confirm that its terms fit their intended use before adopting the library.
There is also a clear upgrade burden when the host cannot use JNA `4.4.0` because of an older GLIBC. The documented workaround is to fork the repository and replace JNA versions in `pom.xml`. Such a fork creates an internal maintenance responsibility for dependency changes and later project updates.
Editorial conclusion
Application teams that control Linux CPU isolation and can reserve non-overlapping cores should evaluate Java Thread Affinity for latency-sensitive JVM workloads. Teams on macOS or Solaris, or deployments that expect portable affinity control, should not adopt it for that purpose. Verify JNA compatibility, the operating system implementation, boot-time isolation, and distinct `-Daffinity.reserved` masks for each JVM before introducing thread pinning.
Frequently asked questions
Which operating systems support setting Java thread affinity with Java Thread Affinity?
Linux provides full affinity control, and Windows manages affinity through its kernel API. macOS does not modify affinity and reports the CPU ID as `-1`; Solaris provides process and thread IDs without affinity or CPU-query support.
What must be configured before Java Thread Affinity can assign isolated CPUs?
The README says CPUs must first be isolated through the Linux kernel command line. It provides `isolcpus=1,3` examples for GRUB and systemd-boot, both of which require a reboot.
How can multiple JVM processes avoid selecting the same reserved CPUs?
Start each process with a different `-Daffinity.reserved` hexadecimal bit mask. Without separate masks, the README warns that multiple processes can be assigned the same CPUs.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/openhft-java-thread-affinity)Was this useful?