前言
2.1 概述
2.2 运行时数据区域
2.2.1 程序计数器:
- 程序计数器是线程私有的,主要目的是存放字节码执行行号,为线程完成上下文切换后可以回到正确的位置。
2.2.2 Java虚拟机栈
通俗理解
线程的内部模型,在执行Java方法的时候创建栈帧存储方法的相关信息。
2.2.3 本地方法栈(Native Method Stacks)
通俗理解
线程的内部模型,在执行本地方法的时候创建栈帧存储方法的相关信息。
2.2.4 Java堆(Heap)
-Xms20m
-Xmx20m
通俗理解
堆存储实例对象,所有线程共享空间。
2.2.5 方法区(Method Area)
通俗理解
方法区存放 静态变量、常量、类型信息等内容。 java8 开始存放在元空间里面。基本不会进行垃圾回收。
2.2.6 运行时常量池(Runtime Constant Pool)
方法区的一部分,
2.2.7 直接内存(Direct Memory)
java操作Unsafe类来直接分配堆外内存。
堆外内存主要是由 NIO 来开辟。
2.3 HotSpot虚拟机对象探秘
2.3.1 对象的创建
通俗理解
当遇到new关键字,首先类的加载过程,然后是内存空间分配,这里涉及到两种方法,指针碰撞和空闲列表,
而采取哪种方法跟是否规整有关系,是否规整又跟垃圾收集相关知识有关,内存空间的分配涉及到线程安全,可以采用 CAS失败重试或本地线程分配缓冲,内存分配完毕后进行初始化,然后进行对象头的信息设置,执行init方法,这样对象就创建完毕了。
2.3.2 对象的内存布局
路径:openjdk\hotspot\src\share\vm\oops\markOop.cpp
对象头 Header
实例数据 instance Data
对齐填充 padding ,对象必须是8字节的整数倍,如果没有对齐则需要填充。
oops: ordinary object pointers 普通对象指针
2.3.3 对象的访问定位
2.4 实战:OutOfMemoryError异常
2.4.1 Java堆溢出
-Xms20m
-Xmx20m
-XX:+HeapDumpOnOutOfMemoryError
-------------------------
public class C2D1 {
public static void main(String[] args) {
List<C2D1> list = new ArrayList<>();
while (true) {
list.add(new C2D1());
}
}
}
-------
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid2196.hprof ...
Heap dump file created [28427774 bytes in 0.076 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at com.example.common.jvm.c2.C2D1.main(C2D1.java:18)
转储文件 java_pid2196.hprof 如图所示
通俗理解
内存泄漏是出现了非必须存活的对象,而内存溢出是所有必须存活的对象占用过多的内存,应该检查堆的相关参数,尽量减少运行期的内存消耗。
2.4.2 虚拟机栈和本地方法栈溢出
-Xss128k
-------
public class C2D2 {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
C2D2 oom = new C2D2();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
--------
stack length:989
Exception in thread "main" java.lang.StackOverflowError
at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:12)
at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:13)
at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:13)
at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:13)
at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:13)
at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:13)
public class C2D3 {
private static int stackLength = 0;
public static void test() {
long unused1, unused2, unused3, unused4, unused5, unused6, unused7, unused8, unused9, unused10, unused11, unused12, unused13, unused14, unused15, unused16, unused17, unused18, unused19, unused20, unused21, unused22, unused23, unused24, unused25, unused26, unused27, unused28, unused29, unused30, unused31, unused32, unused33, unused34, unused35, unused36, unused37, unused38, unused39, unused40, unused41, unused42, unused43, unused44, unused45, unused46, unused47, unused48, unused49, unused50, unused51, unused52, unused53, unused54, unused55, unused56, unused57, unused58, unused59, unused60, unused61, unused62, unused63, unused64, unused65, unused66, unused67, unused68, unused69, unused70, unused71, unused72, unused73, unused74, unused75, unused76, unused77, unused78, unused79, unused80, unused81, unused82, unused83, unused84, unused85, unused86, unused87, unused88, unused89, unused90, unused91, unused92, unused93, unused94, unused95, unused96, unused97, unused98, unused99, unused100;
stackLength++;
test();
unused1 = unused2 = unused3 = unused4 = unused5 = unused6 = unused7 = unused8 = unused9 = unused10 = unused11 = unused12 = unused13 = unused14 = unused15 = unused16 = unused17 = unused18 = unused19 = unused20 = unused21 = unused22 = unused23 = unused24 = unused25 =
unused26 = unused27 = unused28 = unused29 = unused30 = unused31 = unused32 = unused33 = unused34 = unused35 = unused36 = unused37 = unused38 = unused39 = unused40 = unused41 = unused42 = unused43 = unused44 = unused45 = unused46 = unused47 = unused48 = unused49 = unused50 = unused51 = unused52 = unused53 = unused54 = unused55 = unused56 = unused57 = unused58 = unused59 = unused60 = unused61 = unused62 = unused63 = unused64 = unused65 = unused66 = unused67 = unused68 = unused69 = unused70 = unused71 = unused72 = unused73 = unused74 = unused75 = unused76 = unused77 = unused78 = unused79 = unused80 = unused81 = unused82 = unused83 = unused84 = unused85 = unused86 = unused87 = unused88 = unused89 = unused90 = unused91 = unused92 = unused93 = unused94 = unused95 = unused96 = unused97 = unused98 = unused99 = unused100 = 0;
}
public static void main(String[] args) {
try {
test();
} catch (Error e) {
System.out.println("stack length:" + stackLength);
throw e;
}
}
}
------
stack length:7672
Exception in thread "main" java.lang.StackOverflowError
at com.example.common.jvm.c2.C2D3.test(C2D3.java:13)
at com.example.common.jvm.c2.C2D3.test(C2D3.java:13)
at com.example.common.jvm.c2.C2D3.test(C2D3.java:13)
-Xss2M
--------
public class C2D4 {
private void dontStop() {
while (true) {
}
}
public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) throws Throwable {
C2D4 c2D4 = new C2D4();
c2D4.stackLeakByThread();
}
}
2.4.3 方法区和运行时常量池溢出
jdk6 通过限制永久代大小来限制常量池的容量。
-XX:PermSize=6M
-XX:MaxPermSize=6M
jdk8版本
-Xmx5m
-------
public class C2D5 {
public static void main(String[] args) {
// 使用Set保持着常量池引用,避免Full GC回收常量池行为
Set<String> set = new HashSet<>();
short i = 0;
while (true) {
set.add(String.valueOf(i++).intern());
}
}
}
--------
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.HashMap.newNode(HashMap.java:1750)
at java.util.HashMap.putVal(HashMap.java:631)
at java.util.HashMap.put(HashMap.java:612)
at java.util.HashSet.add(HashSet.java:220)
at com.example.common.jvm.c2.C2D5.main(C2D5.java:18)
public class C2D6 {
public static void main(String[] args) {
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
}
}
在JDK 8中的运行结果
public class C2D7 {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1));
enhancer.create();
}
}
static class OOMObject {
}
}
查询元空间相关VM选项
java -XX:+PrintFlagsFinal -version | findstr Metaspace
结果
2.4.4 本机直接内存溢出
-Xmx20M
-XX:MaxDirectMemorySize=10M
-----
public class C2D8 {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
------
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at com.example.common.jvm.c2.C2D8.main(C2D8.java:22)
查询直接内存VM参数
java -XX:+PrintFlagsFinal -version | findstr Direct
MaxDirectMemorySize