First, the JVM memory model
According to the JVM specification, the JVM memory is divided into five parts: virtual machine stack, heap, method area, program counter, local method stack.
1. Virtual machine stack: Each thread has a private stack that is created as the thread is created. In the stack is a kind of "stack frame" thing, each method will create a stack frame, the stack frame contains local variable table (basic data type and object reference), operand stack, method exit and other information. The size of the stack can be fixed or dynamically expanded. When the stack call depth is greater than the scope allowed by the JVM, it throws a stackoverflowerror error, but this depth range is not a constant value, and we can test the result with this program:
Stack Overflow test Source:
1234567891011121314151617181920 |
package com.paddx.test.memory;
public class StackErrorMock {
private static int index =
1
;
public void call(){
index++;
call();
}
public static void main(String[] args) {
StackErrorMock mock =
new StackErrorMock();
try {
mock.call();
}
catch (Throwable e){
System.out.println(
"Stack deep : "
+index);
e.printStackTrace();
}
}
}
|
Code Snippet 1
Run three times, you can see the depth of each stack is not the same, the output is as follows .
As for the value of the red box is how to come out, you need to go deep into the JVM source code can be explored, here does not elaborate.
Virtual machine Stack In addition to the above error, there is another error, that is, when the application is not space, will throw outofmemoryerror. Here's a small detail to note that catch captures are throwable, not Exception. Because Stackoverflowerror and outofmemoryerror do not belong to Exception subclasses.
2. Local method Stack:
This part is mainly related to the Native method used by the virtual machine, and in general, the Java application does not need to care about this part of the content.
3. PC Register:
PC registers, also called program counters. The JVM supports multiple threads running at the same time, each with its own program counter. If the JVM is currently executing a method, the address of the current execution instruction is saved in the register, and if the native method is executed, the PC register is empty.
4. Heap
Heap memory is a part of the JVM that is shared by all threads and is created when the virtual machine is started. All objects and arrays are allocated on the heap. This part of the space can be reclaimed by GC. OutOfMemoryError is thrown when there is no space to apply. Below we simply simulate a heap memory overflow scenario:
12345678910111213141516171819202122 |
package com.paddx.test.memory;
import java.util.ArrayList;
import java.util.List;
public class HeapOomMock {
public static void main(String[] args) {
List<
byte
[]> list =
new ArrayList<
byte
[]>();
int i =
0
;
boolean flag =
true
;
while (flag){
try {
i++;
list.add(
new byte
[
1024 *
1024
]);
//每次增加一个1M大小的数组对象
}
catch (Throwable e){
e.printStackTrace();
flag =
false
;
System.out.println(
"count="
+i);
//记录运行的次数
}
}
}
}
|
Code Snippet 2
Run the above code and the output is as follows:
Note that here I specify the size of the heap memory is 16M, so this place shows the count=14 (this number is not fixed), as to why it is 14 or other numbers, need to be judged according to the GC log, specific reasons will be explained in the next article.
5. Method Area:
The method area is also shared by all threads. It is mainly used for storing class information, Chang, method data, method code and so on. The method area is logically part of the heap, but is often called non-heap in order to differentiate it from the heap. The problem with the method area memory overflow is discussed in detail later in this article.
Second, PermGen (permanent generation)
Most Java programmers should have seen the "Java.lang.OutOfMemoryError:PermGen space" exception. Here the "PermGen space" actually refers to the method area. But the method area and "PermGen space" also have the essential difference. The former is a specification of the JVM, and the latter is an implementation of the JVM specification, and only the HotSpot has "PermGen space", while for other types of virtual machines, such as JRockit (Oracle), J9 (IBM), there is no "PermGen space". Because the method area primarily stores information about classes, it is relatively easy to generate a persistent memory overflow for dynamically generated classes. The most typical scenario is that the JSP page is much more prone to permanent memory overflow. We now simulate the memory overflow of "PermGen space" by dynamically generating classes:
1234 |
package com.paddx.test.memory; public class Test { } |
Code Snippet 3
12345678910111213141516171819202122232425 |
package com.paddx.test.memory;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
public class PermGenOomMock{
public static void main(String[] args) {
URL url =
null
;
List<ClassLoader> classLoaderList =
new ArrayList<ClassLoader>();
try {
url =
new File(
"/tmp"
).toURI().toURL();
URL[] urls = {url};
while (
true
){
ClassLoader loader =
new URLClassLoader(urls);
classLoaderList.add(loader);
loader.loadClass(
"com.paddx.test.memory.Test"
);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
|
Code Snippet 4
The results of the operation are as follows:
The JDK version used in this example is 1.7, and the size of the specified PermGen area is 8M. By loading the test class each time a different URLClassLoader object is generated, a different class object is generated so that we can see the familiar "Java.lang.OutOfMemoryError:PermGen space" exception. JDK 1.7 is used here because, in JDK 1.8, the HotSpot has no "PermGen space" and is replaced by something called metaspace (meta space). Let's take a look at the difference between Metaspace and PermGen space.
Three, metaspace (yuan space)
In fact, the removal of the permanent generation of work started from JDK1.7. In JDK1.7, portions of data stored in a permanent generation have been transferred to the Java heap or to the Native heap. However, the permanent generation still exists in JDK1.7 and is not completely removed, for example, the symbolic reference (Symbols) is transferred to the native heap; the literal (interned strings) is transferred to the Java heap; static variable of Class (class Statics) Transferred to the Java heap. We can compare the differences between JDK 1.6 and JDK 1.7 and JDK 1.8 in a program, with string constants as an example:
12345678910111213141516 |
package com.paddx.test.memory;
import java.util.ArrayList;
import java.util.List;
public class StringOomMock {
static String base =
"string"
;
public static void main(String[] args) {
List<String> list =
new ArrayList<String>();
for (
int i=
0
;i< Integer.MAX_VALUE;i++){
String str = base + base;
base = str;
list.add(str.intern());
}
}
}
|
This program continuously generates new strings at 2 exponential level, which makes it possible to consume memory more quickly. We run through JDK 1.6, JDK 1.7, and JDK 1.8, respectively:
Results of JDK 1.6 operation:
Results of JDK 1.7 operation:
Results of JDK 1.8 operation:
As you can see from the above results, there is a memory overflow of "PermGen Space" under JDK 1.6, and in JDK 1.7 and JDK 1.8, there is a heap memory overflow, and permsize and MaxPermGen in JDK 1.8 are not valid. Therefore, it is possible to roughly verify that JDK 1.7 and 1.8 transfer string constants from a permanent generation to a heap, and that there is no longer a permanent generation in JDK 1.8. Now let's see what the meta-space really is.
The nature of meta-space is similar to permanent generation, which is the implementation of the method area in the JVM specification. However, the biggest difference between meta-space and permanent generation is that the meta-space is not in the virtual machine, but rather uses local memory. Therefore, by default, the size of the meta-space is limited only by local memory, but you can specify the size of the meta-space by using the following parameters:
-xx:metaspacesize, the initial space size, which will trigger a garbage collection to type unload, and the GC will adjust the value: If a large amount of space is freed, the value is appropriately lowered, and if very little space is released, then when no more than maxmetaspacesize , raise the value appropriately.
-xx:maxmetaspacesize, the maximum space, the default is unlimited.
In addition to the above two specified size options, there are two GC-related properties:
-xx:minmetaspacefreeratio, after GC, the minimum amount of metaspace remaining space is reduced to the garbage collection caused by allocated space
-xx:maxmetaspacefreeratio, after GC, the maximum amount of metaspace remaining space is reduced to the garbage collection caused by free space
Now we rerun code Snippet 4 under JDK 8, but this time no longer specifies PermSize and MaxPermSize. Instead, specify the size of the metaspacesize and maxmetaspacesize. The output results are as follows:
From the output, we can see that this time there is no permanent overflow, but there is a meta-space overflow.
Iv. Summary
Through the above analysis, you should understand the JVM's memory division, also clear the JDK 8 in the conversion of the permanent generation of meta-space. But everyone should have a question, why do you want to do this conversion? So, finally, we summarize the following several reasons:
1, the string exists in the permanent generation, prone to performance problems and memory overflow.
2, classes and methods of information, such as difficult to determine its size, so the size of the perpetual generation is difficult to specify, too small prone to permanent overflow, too large will easily lead to old age overflow.
3, the permanent generation will bring unnecessary complexity to the GC, and the recovery efficiency is low.
4. Oracle may merge hotspot with JRockit.
JAVA8 memory Model-Permanent generation (PermGen) and meta-space (Metaspace)