Java equivalents of malloc(), new, free() and delete (ctd)
The C malloc() function is used to allocate an area of memory from a heap of memory
available to the program. Does Java have or need an equivalent of malloc and free
for allocating and releasing arbitrary blocks of memory?
The Java equivalent of malloc()?
Java memory management works differently to a language such as C. In C,
the programmer has much more responsibility for allocating and de-allocating memory when required.
In Java, we allocate objects using new and have them automatically released
by the garbage collector when they are no longer referenced. So does that mean that we need a Java equivalent
to malloc at all?
In C/C++, malloc() gives us a pointer to an arbitrary block of memory:
whether that memory is then used to store a struct, buffer the contents of a file etc is up to the programmer.
So in a sense, asking about the Java equivalent is a bit like asking
what is the Java equivalent of pointers: there's strictly no such thing,
but there are functional equivalents when we need to achieve the same purpose such as
buffering the contents of a file.
Java ewill never give us access to "raw" memory in the sense of a memory address or pointer.
But Java does provide the following rough equivalents to an area of memory allocated via malloc():
- if you just want a block of bytes, e.g. in order to buffer input form a file
or stream, then a common solution is to use a byte array;
- the Java NIO (New I/O) package provides various buffer classes
that allow you to manipulate an array or area of memory more flexibly: with methods to get/set
a given primitive at a particular offset in the buffer, for example.
So, for example, the equivalent of the following C code:
unsigned char *memarea = (char *) malloc(200);
*(memarea + 10) = 200;
*((int *) (memarea + 16) = 123456;
ch = memarea[4];
would be something along the following lines in Java, using a
ByteBuffer object:
ByteBuffer bb = ByteBuffer.allocate(200);
bb.put(0, (byte) 200);
bb.putInt(16, 123456);
int ch = bb.get(4) & 0xff;
Notice a subtlety of buffer access in Java is that we must deal with sign conversions ourselves.
To read an unsigned byte from the ByteBuffer, we must store it in an int (or some primitive
big enough to handle values between 128 and 255— basically, anything but a byte!),
and must explicitly "AND off" the bits holding the sign.
Performance
Note that accesses to ByteBuffers usually compile directly to MOV instructions at the machine code
level. In other words, accessing a ByteBuffer really is usually as efficient as
accessing a malloced area of memory from C/C++1.
Direct byte buffers
In the above example, the byte buffer would actually be "backed" by an ordinary Java byte array.
It is even possible to call bb.array() to retrieve the backing array. An alternative
that is perhaps closer to malloc() is
to create what is called a direct ByteBuffer in Java: a ByteBuffer
that is backed by a "pure block of memory" rather than a Java byte array2.
To do so requires us simply to change the initial
call:
ByteBuffer bb = ByteBuffer.allocateDirect(200);
Note that from Java, we still don't see or have control over the actual memory address
of the buffer. Direct byte buffers have the advantage of
accessibility via native code: although the address is of the buffer
isn't available or controllable in Java, it is possible for your Java program to interface
with native code (via the Java Native Interface). From the native side,
you can:
- query the address of a direct buffer;
- allocate a direct buffer around a determined address range.
This very last point is quite significant, as it effectively allows things like device drivers to
be written in Java (OK, in "Java plus a couple of lines of C" at any rate...).
1. The same is often true of accessing object fields: the JIT compiler can compile accesses
to object fields into MOV instructions that "know" the offset of the field in question.
2. You can actually create buffers backed by other types of primitive array,
such as an IntBuffer backed by an int array.