Original: Taste of Little Sister (wechat official ID: XjjDog), welcome to share, please reserve the source.

If you see this picture, please don’t share it. We’ll limit this to a small discussion, because this is a powerful image that took me 10 years to draw!

Knowing this diagram will give you a better understanding of how the JVM’s memory is divided than the more superficial aspects of the virtual stack, program counters, and so on.

So what does this picture do? When doing a memory sweep, we need to know which part is the problem. If you don’t get it right, it’s harder to cut in, and it takes a lot of energy.

A 4GB machine, typically allocated to a JVM using Xmx, is definitely not too much. Like 3.5GB or something. This is greedy and can easily cause the JVM to die unexpectedly. Why is that?

This is easier to understand because on an operating system, not only is your JVM application running, but there are other daemons running, such as various log collection tools, monitoring tools, security tools, and so on. They don’t take up a lot of memory, but they add up to a lot. There is a trade-off between the JVM memory and the remaining memory of the operating system, and these small amounts of memory occupy the JVM’s space and are prone to problems.

The JVM is our subject, so put it where the protagonist is. In this way, the entire memory can be divided into JVM memory, operating system physical memory, and SWAP.

When the JVM and other programs fill up physical memory, and then SWAP memory, when it comes time to allocate memory, the operating system realizes: “Screw it, there is no memory available.”

At this point, Linux will start oom-killer and kill the process that consumes the most memory, which is most likely your cute JVM baby.

The OOM is for the operating system, not the JVM. So you’ll find: your Java process is dead, but nothing is left. So quietly went.

This information, which can only be found through the dmesg command, belongs to the operating system.

So, let’s take a look at the main picture and explain what all of these parts do.

We still split the memory into the above three parts, but the JVM’s process memory is more finely divided.

First, for the MEMORY of the JVM, there is in-heap memory and out-of-heap memory.

In-heap memory is where we deal with most of the time, because most of our Java objects are allocated on the heap. Once there is an overflow problem, use a series of fierce operations such as JMAP + MAT, you can find the problem quickly and easily.

This is a skill that any Java expert can master.

The key is the part of the heap out of memory, which is very painful. Because it’s all here, and it’s easy to get mixed up.

It can be seen that for this part of the memory problem, even the most authoritative JVM teacher Zhou’s book, still has related errors.

The unsafe code is actually the wrong result, as in unsafe, not direct memory.

Let’s take inventory of what’s in there.

First, metaspace

The meta space was added after JDK8 to replace the original permanent generation. In other words, the method region of the original PERm region is also here. As its name suggests, permanent generations refer to data that change little and are stable. Like those class files that we load when the JVM starts up; And dynamically generated proxy classes at run time.

Compare pit is, the size of meta space, by default, there is no upper limit. In extreme cases, the operating system’s remaining memory will always be squeezed.

Second, CodeCache

Many articles have very little to say about this part, but this is a very important non-heap area. Because JIT is a very important feature of the JVM, CodeCahe stores the binary code generated by the just-in-time compiler. Of course, the JNI code is also located here.

This space is in different platform, size is not the same, but generally enough. In this case, the JVM will not overflow, this area will not overflow, but will degenerate into interpreted execution mode, speed and JIT are not comparable, slower is also possible.

Local memory

In fact, when we talk to each other about off-heap memory, most of it is here, and most of the problems are here. It’s more nuanced.

(1) Network memory

Network connections also take up a lot of memory. This connection is very interesting, and you can think of it as memory occupied by the operating system kernel, or memory occupied by the JVM process.

If your system has very high concurrency, this part of the memory footprint is also high. Because the connection generally corresponds to the data buffer of the network card, as well as the cost of file handles.

(2) Thread memory

Similarly, if you build a lot of threads, the JVM takes up a very small portion of the heap memory of the Thread objects themselves, and most of it lives on the operating system as a lightweight process.

This is also an area of memory that adds up, but generally doesn’t cause problems.

(3) JNI memory

JNI memory refers to the specific memory malloc of the CodeCache code.

The Java ZIP library, for example, is not built in the JVM heap, but in an out-of-heap buffer pool.

(4) Direct memory

Direct memory refers to the memory in which operations are performed using Java’s direct memory API. This portion of memory can be controlled by the JVM, as the ByteBuffer class does.

Unsafe is the underlying word for ByteBuffer, but unsafe is not governed by direct memory, and these are not one thing.

Using the unsafe program directly in the book above does not cause the JVM to spill memory directly, but rather the operating system.


So how do we see this memory?

Linux has a command, lsof, to see all handle information associated with the JVM process, as a general reference.

As a further step, the specific memory distribution can be observed using the Pmap function. But fear not, there is a lot of shared memory.

For details on this process, please refer to a previous article on out-of-heap memory troubleshooting.

If you understand these memory partitions in the figure, it is easy to understand why the NMT tool cannot show JNI memory statistics.

Next, let’s summarize these memory regions, and what parameters control them.

  • The heap-Xmx -Xms
  • dimension-XX:MaxMetaspaceSize -XX:MetaspaceSize
  • The stack-Xss
  • Direct memory-XX:MaxDirectMemorySize
  • JIT compiled code is stored-XX:ReservedCodeCacheSize
  • Other out-of-heap memory out of control! The penson.

As you can see, the footprint of memory outside the heap is actually quite large. If you are too greedy, the whole memory is easy to play with.

In general, it is reasonable to use 2/3 of the operating system’s heap space. This is a rule of thumb. For example, if you allocate 6GB of memory to the JVM, you should not allocate more than 4GB.

Also, the swap partition we talked about above is usually turned off in high-concurrency applications. Because it causes frequent page swapping, it can cause serious lag during GC.

But look at the problem dialectically. For low-frequency, very dependent on memory size, SWAP should not only open, but also open larger.

Xjjdog is a public account that doesn’t allow programmers to get sidetracked. Focus on infrastructure and Linux. Ten years architecture, ten billion daily flow, and you discuss the world of high concurrency, give you a different taste. My personal wechat xjjdog0, welcome to add friends, further communication.