preface
Interaction as we know, the computer CPU and memory is the most frequent, memory is our cache area, and the interaction of the user disk and CPU and CPU running faster and faster, with a disk far couldn’t keep up with reading and writing speed of the CPU, to design the memory, users buffer IO lead to CPU waiting for cost, but with the development of the CPU, The memory read/write speed was also far behind the CPU read/write speed, so to solve this problem, the CPU manufacturer added a cache on each CPU to alleviate this problem, so the CPU and memory interaction now looks like this.
Again, by Moore’s law, we know that mononuclear
CPU frequency can not be unlimited growth, to a lot of new energy, need multiple processors to work together, Intel president Barrett knelt down event marks the arrival of multi-core era.
Cache based storage interaction solves the contradiction between processor and memory, but also introduces a new problem: cache consistency. In a multiprocessor system, each processor has its own cache, and they share the same block of memory.
Cache inconsistencies can occur when multiple processors operate on the same memory area. In order to solve this problem, it is necessary for each processor to follow some protocols when running, and these protocols need to ensure data consistency during running. Such protocols include MSI, MESI, MOSI, Synapse, Firely, DragonProtocol, etc. As shown in the figure below
The access operations defined in the Java virtual machine memory model are basically the same as those handled by physical computers!
All threads share main memory of the JVM, and each thread has its own working memory separately. When the thread interacts with the memory area, data is copied from main memory to the working memory, and then processed by the thread (opcode + operand).
Reader benefits: Java core knowledge learning notes.
As mentioned earlier, the JVM’s logical memory model is as follows:
Let’s take a look at what each one does!
1. Program counter
The Program Counter Register is a small memory space that acts as a line number indicator of the bytecode being executed by the current thread. In the concept of virtual machine model (just the conceptual model, all kinds of virtual machine may be through some of the more efficient way to achieve), bytecode interpreter to work is by changing the counter value to select a need to be performed under the bytecode instruction, branches, loops, jumps, exception handling, thread to restore the basic function such as all need to rely on the counter.
Because multithreading in the Java VIRTUAL machine is implemented by the way threads alternate and allocate processor execution time, at any given moment, one processor (or kernel for multi-core processors) will execute instructions in only one thread. Therefore, in order to restore the thread to the correct execution position after switching, each thread needs to have an independent program counter. Counters between threads do not affect each other and are stored independently. We call this kind of memory area “thread private” memory.
If the thread is executing a Java method, this counter records the address of the virtual machine bytecode instruction being executed. If the Natvie method is being executed, this counter value is null (Undefined). This memory region is the only one where the Java Virtual Machine specification does not specify any OutOfMemoryError cases.
2. Java Virtual machine stack
Like program counters, the Java Virtual Machine Stack is thread-private and has the same lifetime as a thread. The virtual machine Stack describes the memory model of Java method execution: each method execution creates a Stack Frame (Stack Frame ①) to store information such as local variation tables, operation stacks, dynamic links, method exits, etc. Each method is called until the execution is complete, corresponding to the process of a stack frame in the virtual machine stack from the stack to the stack.
Java memory is often divided into Heap and Stack memory, which is a crude division, and the division of Java memory regions is actually much more complex than that. The popularity of this partition only shows that most programmers care most about these two areas of memory that are most closely related to the allocation of object memory. The “stack” refers to the virtual machine stack, or the local variable table part of the virtual machine stack.
The local variable table stores various basic data types known at compile time (Boolean, byte, CHAR, short, int, float, long, double), object references (reference type, which is not equivalent to the object itself, depending on the implementation of different virtual machines. It may be a reference pointer to the object’s starting address, a handle to the object or some other location associated with the object, and the returnAddress type (which points to the address of a bytecode instruction).
64-bit long and double data occupy two local variable slots, and the rest occupy only one. The memory space required for the local variable table is allocated at compile time. When entering a method, how much local variable space the method needs to allocate in the frame is completely determined, and the size of the local variable table does not change during the method run.
In the Java Virtual Machine specification, two exceptions are specified for this area: a StackOverflowError is thrown if the stack depth of a thread request is greater than the depth allowed by the virtual machine; If the virtual stack can scale dynamically (and most Java virtual machines currently do, although the Java Virtual Machine specification also allows fixed-length virtual stacks), an OutOfMemoryError will be thrown when sufficient memory cannot be allocated while scaling.
3. Local method stack
The Native Method stack is similar to the virtual machine stack, except that the virtual machine stack performs Java methods (i.e. bytecodes) for the virtual machine, whereas the Native Method stack services the Native methods used by the virtual machine. The virtual machine specification does not mandate the language, usage, or data structure of methods in the local method stack, so specific virtual machines are free to implement it. There are even virtual machines (such as the Sun HotSpot VIRTUAL machine) that simply merge the local method stack with the virtual machine stack.
Like the virtual stack, the local method stack area throws StackOverflowError and OutOfMemoryError exceptions.
4, the Java heap
For most applications, the Java Heap is the largest chunk of memory managed by the Java virtual machine. The Java heap is an area of memory that is shared by all threads and is created when the virtual machine is started. The sole purpose of this memory area is to hold object instances, and almost all object instances are allocated memory here. This is described in the Java Virtual Machine specification as follows: All object instances and arrays are allocated on the heap, but with the development of JIT compilers and escape analysis techniques, allocation on the stack and scalar replacement optimization techniques can lead to subtle changes that make it less “absolute” that all objects are allocated on the heap.
The Java Heap is the main area managed by the garbage collector, so it is sometimes called a “garbage collector’s cut Heap.” From the point of view of memory collection, the Java heap can be subdivided into: new generation and old generation; More detailed are Eden space, From Survivor space, To Survivor space, etc. From the perspective of memory allocation, the Java heap shared by threads can be divided into Thread LocalAllocation buffers (tlabs) that are private to multiple threads. However, no matter how to partition, it has nothing to do with the storage content, no matter which area, the storage is still the object instance, the purpose of further partition is to better reclaim memory, or faster allocation of memory. In this chapter, we only discuss the role of memory regions; the details of allocation and reclamation of each of these regions in the Java heap will be the subject of the next chapter.
According to the Java Virtual Machine specification, the Java heap can be in a physically discontinuous memory space, as long as it is logically contiguous, like our disk space. When implemented, it can be either fixed size or extensible, but most current virtual machines are implemented as extensible (controlled by -xmx and -xMS). OutOfMemoryError is thrown if there is no memory in the heap to complete the instance allocation and the heap can no longer be extended.
4. Method area
The Method Area, like the Java heap, is an Area of memory shared by threads that stores information about classes loaded by the virtual machine, constants, static variables, code compiled by the just-in-time compiler, and so on. Although the Java Virtual Machine specification describes the method area as a logical part of the Heap, it has an alias called non-heap, which is supposed to distinguish it from the Java Heap.
For developers who are used to developing and deploying applications on the HotSpot VIRTUAL machine, many prefer to refer to the method area as “Permanent Generation”, which is essentially not equivalent, simply because the HotSpot Virtual machine design team chose to extend GC Generation collection to the method area. Or to implement method sections using persistent generation. For other virtual machines (BEA JRockit, IBM J9, etc.) there is no concept of a permanent generation. Even the HotSpot VIRTUAL machine itself, according to the official roadmap, is now planned to abandon the permanent generation and “move” to Native Memory to implement the method area.
The Java Virtual Machine specification is very relaxed about this area, in addition to the fact that, like the Java heap, it does not require contiguous memory and has the option of either fixed size or scalability, and optionally does not implement garbage collection. Garbage collection is relatively rare in this area, but it is not as “permanent” as the name of the permanent generation that data enters the method area. The target of memory reclamation in this area is mainly for constant pool reclamation and type unloading. Generally speaking, the “performance” of the reclamation in this area is not satisfactory, especially for type unloading, but the reclamation in this part of the area is indeed necessary. Several serious bugs on Sun’s BUG list have been memory leaks caused by earlier versions of the HotSpot VIRTUAL machine not fully reclaiming this area.
According to the Java Virtual Machine specification, OutOfMemoryError is thrown when the method area cannot meet memory allocation requirements.
5. Runtime constant pool
The Runtime Constant Pool is part of the method area. The Constant PoolTable is used to store the literal and symbolic references generated at compile time. This part of the Constant table is stored in the runtime Constant pool of the method area after the Class is loaded.
The Java virtual machine has strict rules on the format of each part of a Class file (including, of course, the constant pool), and each byte must be used to store what data must conform to the specification before it is accepted, loaded, and executed by the virtual machine. The Java Virtual Machine specification does not specify any details about runtime constant pools, however, and virtual machines implemented by different vendors can implement this memory area as they see fit. However, in general, in addition to storing symbolic references described in Class files, translated direct references are also stored in the runtime constant pool.
The Java language does not require constants to be generated only at compile time. That is, the contents of the constant pool in the Class file can be entered into the method area runtime constant pool. New constants may also be added to the pool during runtime. One feature that developers use most often is the Intern () method of the String class.
Since the runtime constant pool is part of the method area, it is naturally limited by the method area memory, and an OutOfMemoryError is thrown when the constant pool can no longer claim memory
6. Direct memory
Direct Memory is not part of the virtual machine’s run-time data area, nor is it defined in the Java Virtual Machine specification, but it is frequently used and can cause OutofMemoryErrors, so we’ll cover it here.
The NIO (New Input/Output) class was introduced in JDK 1.4, introducing a Channel and Buffer based I/O method that can allocate off-heap memory directly using Native libraries. This is then referenced by a DirectByteBuffer object stored in the Java heap. This can significantly improve performance in some scenarios because it avoids copying data back and forth between the Java heap and Native heap.
Obviously, the allocation of native direct memory is not limited by the Size of the Java heap, but since it is memory, it is certainly limited by the size of total native memory (including RAM and SWAP or paging files) and processor addressing space. When configuring VM parameters, the server administrator usually sets parameters such as -xmx based on the actual memory. However, the direct memory is often ignored. As a result, the total number of memory regions is greater than the physical memory limit (including the physical and OS limits), resulting in an OutOfMemoryError during dynamic expansion.
We’ve seen the logical memory model, but how do we access it when we create an object? How does object access work in the Java language? Object access is ubiquitous in the Java language and is the most common program behavior, but even the simplest access involves the relationship between the Java stack, the Java heap, and the method area, the three most important areas of memory, as shown in this code:
Object obj = new Object();
Assuming that this code appears in the method body, the semantics of the “Object obj” part will be reflected in the Java stack’s local variation table as a reference type of data. The semantics of the “new Object()” part will be reflected in the Java heap, forming a structured memory that holds all the Instance Data values of type Object(the Data of each Instance field in the Object). Depending on the type and Object Memory Layout implemented by the VIRTUAL machine, the size of the Memory block is not fixed. In addition, the Java heap must contain the address information where the object type data (such as object type, parent class, implemented interface, method, and so on) can be found, which is stored in the method area.
In the Java Virtual Machine specification, the reference type only specifies a reference to an object, and does not define which way the reference should be located or the specific location of the object in the Java heap. Therefore, different virtual machines implement different object access methods. There are two main access methods: Use handles and direct Pointers.
If the handle access method is used, a block of memory will be allocated to the Java heap as the handle pool. Reference stores the handle address of the object, and the handle contains the specific address information of the instance data and type data of the object, as shown in the figure below.
If direct pointer access is used, the layout of the Java heap object must consider how to place the information related to the access type data, and the object address is stored directly in reference, as shown in the following figure
The two methods of accessing objects have their own advantages. The biggest advantage of using the handle access method is that reference stores a stable handle address and only changes the instance data pointer in the handle when the object is moved (which is a very common behavior during garbage collection). Reference itself does not need to be modified.
The biggest benefit of using direct pointer access is that it is faster. It saves the time cost of a pointer location, and since objects are accessed very frequently in Java, this overhead can add up to a very significant execution cost. In the case of Sun HotSpot, the main virtual machine discussed in this book, it uses the second approach for object access, but it is also common across the software development spectrum for languages and frameworks to use handles for access.
Let’s look at a few examples
1. Java heap overflow
In the following procedure, we limit the size of the Java heap to 20MB, which is not scalable (the minimum -xms parameter of the heap is set to the same as the maximum -xmx parameter to avoid automatic heap expansion). Through the parameter – XX: + HeapDumpOnOutOfMemoryError allows a virtual machine when abnormal memory Dump out the current memory heap Dump snapshot for later analysis.
Parameter Settings are as follows
package com.yhj.jvm.memory.heap;
import java.util.ArrayList;
import java.util.List;
/ * * *@Described: Heap overflow test *@VM args:-verbose:gc -Xms20M -Xmx20M -XX:+PrintGCDetails
* @author YHJ create at 2011-11-12 07:52:22 PM *@FileNmae com.yhj.jvm.memory.heap.HeapOutOfMemory.java
*/
public class HeapOutOfMemory {
/ * * *@param args
* @Author YHJ create at 2011-11-12 07:52:18 */
public static void main(String[] args) {
List<TestCase> cases = new ArrayList<TestCase>();
while(true){
cases.add(newTestCase()); }}}/ * * *@Described: Test cases *@author YHJ create at 2011-11-12 07:55:50 *@FileNmae com.yhj.jvm.memory.heap.HeapOutOfMemory.java
*/
class TestCase{}Copy the code
OutOfMemoryError of Java heap memory is the most common out-of-memory exception in practical applications. Java heap memory leak, “Java. Lang. OutOfMemoryError exception stack information will follow further prompt” Java heapspace “.
To resolve exceptions in this region, a common approach is to first analyze heap dump snapshots through a memory image analysis tool (such as EclipseMemory Analyzer), focusing on determining whether objects in memory are necessary. In this case, we need to know whether there is a Memory Leak or Memory Overflow. Figure 2-5 shows a heap dump snapshot file opened using Eclipse Memory Analyzer.
If it is a memory leak, you can further look at the leak object’s chain of references to GC Roots through the tool. You can then find out how the leak objects are associated with GC Roots and cause the garbage collector to fail to reclaim them automatically. With the information of the type of the leaking object and the GC Roots reference chain, the location of the leaking code can be more accurately located.
If there are no leaks, in other words, objects in memory that really must be alive, you should check the heap parameters of the virtual machine (-XMX and -xMS) against the physical memory of the machine to see if they can be increased, and code to see if some objects are too long in life and hold state. Try to reduce the memory consumption of the program’s runtime.
That’s a quick overview of how to deal with Java heap memory problems, and I’ll do some additional analysis of the knowledge, tools, and experience needed to deal with these problems in the next few shares.
2. Java stack overflow
package com.yhj.jvm.memory.stack;
/ * * *@Described: Insufficient exploration of stack hierarchy *@VM args:-Xss128k
* @author YHJ Create at 2011-11-12 11:4pm *@FileNmae com.yhj.jvm.memory.stack.StackOverFlow.java
*/
public class StackOverFlow {
private int i ;
public void plus() {
i++;
plus();
}
/ * * *@param args
* @Author YHJ Create at 2011-11-12 11:4pm */
public static void main(String[] args) {
StackOverFlow stackOverFlow = new StackOverFlow();
try {
stackOverFlow.plus();
} catch (Exception e) {
System.out.println("Exception:stack length:"+stackOverFlow.i);
e.printStackTrace();
} catch (Error e) {
System.out.println("Error:stack length:"+stackOverFlow.i); e.printStackTrace(); }}}Copy the code
Constant pool overflow (constant pool information will be described in a later JVM class file structure)
package com.yhj.jvm.memory.constant;
import java.util.ArrayList;
import java.util.List;
/ * * *@DescribedConstant pool memory overflow exploration *@VM args : -XX:PermSize=10M -XX:MaxPermSize=10M
* @author YHJ Create at 2011-10-30 04:28:30 *@FileNmae com.yhj.jvm.memory.constant.ConstantOutOfMemory.java
*/
public class ConstantOutOfMemory {
/ * * *@param args
* @throws Exception
* @Author YHJ create at 2011-10-30 PM 04:28:25 */
public static void main(String[] args) throws Exception {
try {
List<String> strings = new ArrayList<String> (); int i =0;
while(true){
strings.add(String.valueOf(i++).intern()); }}catch (Exception e) {
e.printStackTrace();
throwe; }}}Copy the code
4, method to overflow
package com.yhj.jvm.memory.methodArea;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/ * * *@Described: Method area overflow testing * using technology CBlib *@VM args : -XX:PermSize=10M -XX:MaxPermSize=10M
* @author YHJ Create at 2011-11-12 08:47:55 PM *@FileNmae com.yhj.jvm.memory.methodArea.MethodAreaOutOfMemory.java
*/
public class MethodAreaOutOfMemory {
/ * * *@param args
* @Author YHJ Create at 2011-11-12 08:47:51 */
public static void main(String[] args) {
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TestCase.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
returnarg3.invokeSuper(arg0, arg2); }}); enhancer.create(); }}}/ * * *@Described: Test cases *@author YHJ Create at 2011-11-12 08:53:09 PM *@FileNmae com.yhj.jvm.memory.methodArea.MethodAreaOutOfMemory.java
*/
class TestCase{}Copy the code
5. Direct memory overflow
package com.yhj.jvm.memory.directoryMemory;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
/ * * *@Described: Direct memory overflow test *@VM args: -Xmx20M -XX:MaxDirectMemorySize=10M
* @author YHJ Create at 2011-11-12 09:06:10 PM *@FileNmae com.yhj.jvm.memory.directoryMemory.DirectoryMemoryOutOfmemory.java
*/
public class DirectoryMemoryOutOfmemory {
private static final int ONE_MB = 1024*1024;
private static int count = 1;
/ * * *@param args
* @Author YHJ Create at 2011-11-12 09:05:54 PM */
public static void main(String[] args) {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
while (true) { unsafe.allocateMemory(ONE_MB); count++; }}catch (Exception e) {
System.out.println("Exception:instance created "+count);
e.printStackTrace();
} catch (Error e) {
System.out.println("Error:instance created "+count); e.printStackTrace(); }}}Copy the code