Low level programming with Java

Java hides a surprisingly capable “low-level subset” for doing bit-twiddly, memory-adjacent work while still staying safe(-ish). Here’s a compact map with the key tools, what they’re for, and tiny examples.

1) Bitwise core: operators & integer promotions

  • Types: byte (8, signed), short (16, signed), char (16, unsigned), int (32, signed), long (64, signed).

  • Operators: & | ^ ~ << >> >>>

    • >> = arithmetic right shift (sign-extends)

    • >>> = logical right shift (zero-fills)

  • Small types (byte/short/char) promote to int before ops—mask when you need exact widths.

int packRGBA(int r, int g, int b, int a) { return ((r & 0xFF) << 24) | ((g & 0xFF) << 16) | ((b & 0xFF) << 8) | (a & 0xFF); } int r = (rgba >>> 24) & 0xFF;

2) Unsigned helpers (on signed primitives)

  • Convert/compare/divide as if unsigned:

    • Byte.toUnsignedInt(byte b), Short.toUnsignedInt(short s)

    • Integer.toUnsignedLong(int x), Integer.compareUnsigned, Integer.divideUnsigned, Integer.remainderUnsigned

    • Same for Long (compare/divide/remainder as unsigned)

int u8 = Byte.toUnsignedInt(b); long u32 = Integer.toUnsignedLong(i);

3) Integer/Long bit utilities (fast, often intrinsic)

Integer and Long expose high-quality bit hacks:

  • bitCount(x) — popcount

  • numberOfLeadingZeros(x) / numberOfTrailingZeros(x)

  • rotateLeft(x, n) / rotateRight(x, n)

  • reverse(x) — bit reverse

  • reverseBytes(x) — endianness swap

  • highestOneBit(x) / lowestOneBit(x)

int idx = 31 - Integer.numberOfLeadingZeros(x); // floor(log2(x)) for x>0 int parity = Integer.bitCount(x) & 1; int rot = Integer.rotateLeft(mask, 5);

4) Float/Double bit-level access (IEEE-754)

Exact reinterprets without changing payloads (incl. signaling NaNs):

  • Float.floatToRawIntBits(float f)

  • Float.intBitsToFloat(int bits)

  • Double.doubleToRawLongBits(double d)

  • Double.longBitsToDouble(long bits)

Other IEEE helpers:

  • Float/Double.isFinite, isNaN, isInfinite

  • nextUp/nextDown, ulp, getExponent, scalb, copySign

  • Math.fma, StrictMath (strict IEEE semantics), strictfp (class/method modifier)

int bits = Float.floatToRawIntBits(f); int sign = (bits >>> 31) & 1; int exp = (bits >>> 23) & 0xFF; int frac = bits & 0x7FFFFF; float fromBits = Float.intBitsToFloat((sign<<31) | ((exp&0xFF)<<23) | frac); // next representable float above x: float next = Math.nextUp(x);

5) Byte order and packing

  • ByteBuffer + order(ByteOrder.LITTLE_ENDIAN) for explicit endianness.

  • Integer.reverseBytes / Long.reverseBytes for quick swaps.

ByteBuffer bb = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); bb.putInt(0xCAFEBABE).putFloat(1.0f); bb.flip(); int i = bb.getInt(); float f = bb.getFloat();

6) Raw-ish memory without Unsafe: NIO & FFM

NIO Direct/Memory-mapped buffers

  • FileChannel.map(...)MappedByteBuffer (off-heap, OS-backed).

  • ByteBuffer.allocateDirect(n) for off-heap scratch.

  • Great for struct-like layouts with manual indexing.

try (var ch = FileChannel.open(path, READ, WRITE, CREATE)) { MappedByteBuffer m = ch.map(MapMode.READ_WRITE, 0, 1L<<20).order(ByteOrder.LITTLE_ENDIAN); m.putFloat(0, 3.14f); float pi = m.getFloat(0); }

Foreign Function & Memory (Project Panama)

  • MemorySegment, MemoryLayout, VarHandle-based layout accessors (no Unsafe, bounds-checked, off-heap).

  • Lets you define C-style structs and access fields at addresses. (Availability depends on your JDK version; on recent JDKs it’s standard, earlier it was preview/incubator.)

7) Concurrency-level primitives (low-level flavor)

Atomics & fences

  • java.util.concurrent.atomic.* (AtomicInteger, AtomicLong, AtomicReferenceArray, etc.)

  • Field updaters: AtomicIntegerFieldUpdater for CAS on volatile fields.

  • VarHandle (since Java 9): fine-grained memory semantics: getOpaque, getAcquire, setRelease, compareAndSet, getAndAdd, etc.

class Counter { volatile int x; private static final VarHandle X; static { try { X = MethodHandles.lookup().findVarHandle(Counter.class, "x", int.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } } void inc() { X.getAndAdd(this, 1); } }

8) Fast array/memory moves & diffs

  • System.arraycopy(src, si, dst, di, len) — usually a native bulk copy.

  • Arrays.mismatch(a, b) — first differing index (-1 if equal).

  • Arrays.hashCode(...) on primitives is vectorized on some JVMs.

System.arraycopy(src, 0, dst, 0, n); int k = Arrays.mismatch(a, b); // k == -1 if identical

9) Big bitsets/bignums

  • BitSet — compact bit arrays with logical ops (and, or, xor, andNot, nextSetBit).

  • BigInteger — arbitrary precision with low-level operations, plus testBit, setBit, clearBit, flipBit, bitLength, bitCount, shiftLeft/Right.

BitSet bs = new BitSet(); bs.set(3); bs.set(10); int next = bs.nextSetBit(4); // 10

10) Checked overflow & exact math

  • Math.addExact/subtractExact/multiplyExact, incrementExact/negateExact

  • Math.floorDiv/floorMod (mathematically consistent with negatives)

int safe = Math.addExact(a, b); // throws ArithmeticException on overflow

11) CRCs and light hashing over bytes

  • java.util.zip.CRC32, Adler32 for streaming checksums (simple, fast)

  • MessageDigest for stronger hashes (SHA-family) when needed.

Checksum crc = new CRC32(); crc.update(buf, 0, buf.length); long value = crc.getValue();

12) Performance/intrinsics odds & ends

  • Many of the above map to HotSpot intrinsics (very fast), e.g. bitCount, rotates, arraycopy, fma.

  • @Contended (JEP 142) can reduce false-sharing on fields (needs -XX:-RestrictContended).

  • final, volatile, escape-analysis friendly patterns help the JIT.

13) Practical float/int reinterpretation patterns

Extract IEEE-754 fields

static int floatSign(float f) { return Float.floatToRawIntBits(f) >>> 31; } static int floatExponent(float f) { return (Float.floatToRawIntBits(f) >>> 23) & 0xFF; } static int floatMantissa(float f) { return Float.floatToRawIntBits(f) & 0x7FFFFF; }

Create a quiet NaN with custom payload

static float qnan(int payload23) { int bits = 0x7FC00000 | (payload23 & 0x003FFFFF); return Float.intBitsToFloat(bits); }

Encode/decode a 24-bit unsigned integer in 3 bytes

static void putU24(byte[] a, int off, int v) { a[off] = (byte) (v & 0xFF); a[off+1] = (byte) ((v>>8) & 0xFF); a[off+2] = (byte) ((v>>16) & 0xFF); } static int getU24(byte[] a, int off) { return (Byte.toUnsignedInt(a[off])) | (Byte.toUnsignedInt(a[off+1]) << 8) | (Byte.toUnsignedInt(a[off+2]) << 16); }

14) Things that feel low-level but are better avoided

  • sun.misc.Unsafe (non-portable, access restricted). Prefer VarHandle, ByteBuffer, or MemorySegment.

  • Hand-rolled pointer arithmetic: Java doesn’t expose raw pointers; emulate with indices and bounds checks (or FFM API for off-heap with safety).


Quick “what to use when” cheat sheet

  • Bit hacks/popcount/rotates: Integer/Long bit utilities

  • Unsigned math: Byte/Short/Integer/Long unsigned methods

  • Endian swaps: reverseBytes, ByteBuffer.order

  • Float–int reinterprets: floatToRawIntBits / intBitsToFloat (and Double variants)

  • Struct-like I/O: ByteBuffer (heap/direct) or MappedByteBuffer

  • Off-heap structured access / native interop: MemorySegment (+ layouts/VarHandles)

  • Atomic CAS/fences: VarHandle or atomic.*

  • Bulk copy/diff: System.arraycopy, Arrays.mismatch

  • Bit arrays: BitSet; big math: BigInteger

Comments

Popular posts from this blog

Neon Bulb Oscillators

23 Circuits you can Build in an Hour - Free Book

Q Multiplier Circuits