When a buffer is freed does it automatically free the contents of the buffer?
A "buffer", such as the VkCommandPoolCreateInfo.Buffer is a Java object holding the address of an "off-heap" memory area in the process'es address space.
That off-heap memory is _not_ managed by the JVM.
Instances of VkCommandPoolCreateInfo are also just simple wrappers for holding the address of a native memory region which fits a native VkCommandPoolCreateInfo struct instance.
The reason why a VkCommandPoolCreateInfo.Buffer offers put/get methods to handle VkCommandPoolCreateInfo is because of convenience. Because the VkCommandPoolCreateInfo.Buffer knows how large a native VkCommandPoolCreateInfo struct is, its put/get methods can memcpy the native memory pointed to by the VkCommandPoolCreateInfo instances at the correct position in the VkCommandPoolCreateInfo.Buffer's memory (for put methods).
What is important to realize here is that VkCommandPoolCreateInfo instances only exist whenever you want to handle a native VkCommandPoolCreateInfo struct in a typed way in Java via a VkCommandPoolCreateInfo Java wrapper object. That wrapper object gets created on-demand and initialized with the address offset of the requested VkCommandPoolCreateInfo instance into the memory allocated by the VkCommandPoolCreateInfo.Buffer.
For instance, does freeing a VkCommandPoolCreateInfo.Buffer free the VkCommandPoolCreateInfo instances inside it?
If you manually allocated a VkCommandPoolCreateInfo instance via the malloc methods, then: no.
If you obtained a VkCommandPoolCreateInfo Java instance by using VkCommandPoolCreateInfo.Buffer.get() then: yes.
But we must separate two things here:
a) the Java VkCommandPoolCreateInfo instances which are _not_ the content or parts of the VkCommandPoolCreateInfo.Buffer but merely wrapper to hold a native memory address pointing into the buffer's memory region;
b) the native memory allocated explicitly by certain LWJGL methods, such as the various malloc() methods.
That means: The on-heap memory of the Java wrapper object managed by the JVM is different from the native memory region it should represent in LWJGL for the native structs in Vulkan or GLFW, etc.
Again, the important thing here is to ask: Who allocated the native memory first via one of the malloc() methods (i.e. who is responsible for the memory and responsible for deallocating it again)?
Generally, it is the object you created with one of the malloc() methods. And when you created a Java object via one of the malloc() methods, it means that this Java object now holds the address of a newly allocated memory area, which you must also deallocate via free().
I always thought java objects were actually heap allocated, and the name is always tantamount to a pointer somewhere in the heap. I recall somewhere that native types like int and float can be stack allocated in java automatically, but subclasses of Object are always heap allocated.
How the JVM manages Java objects is completely orthogonal to the discussion about how to handle native memory. The JVM may decide to allocate a Java object on the heap or may not allocate it at all if during Escape Analysis it found that the object cannot escape the "scope of optimization." But again, how the JVM manages memory for Java objects has nothing to do with the management of native memory, which is the topic here.
Does that mean the following is correct or incorrect?
In this example, you MUST manually call free() on the VkCommandPoolCreateInfo instances 'a' and 'b', because you also manually allocated them via malloc().