深入理解java内存:深入理解Java虚拟机之图解Java内存区域与内存溢出异常
深入理解java内存:深入理解Java虚拟机之图解Java内存区域与内存溢出异常采用不同的JDK及垃圾回收收集器均可能会产生不同的结果,以下实战均以JDK8,ParallelGC垃圾收集器为例运行代码# 查看默认垃圾收集器VM参数 -XX: PrintCommandLineFlags -versionJava堆溢出只要不断创建对象实例,同时又避免垃圾收集器回收,这样达到最大堆容量限制后便能产生OOM异常public class Hello { /** * -Xms:最小堆内存20M -Xmx:最大堆内存20M 两者设置一样避免自动扩展 * VM参数:-Xms20M -Xmx20M -XX: HeapDumpOnOutOfMemoryError */ public static void main(String[] args) { List<Hello> hellos = new ArrayLi
本地方法栈- 与Java虚拟机栈类似,只不过服务对象不一样,本地方法栈为虚拟机使用到的本地方法服务,Java虚拟机栈为虚拟机执行Java方法(字节码)服务
- 对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中 最大 的一块。Java堆是被所有 线程共享 的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存
- 当堆内存没有足够空间给对象实例分配内存并且堆内存无法扩展时都会抛出OOM异常
- 方法区与Java堆类似,也是各个线程共享的区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
- 通常用别名“非堆”来与Java堆做区分
- 当方法区没有足够空间满足内存分配要求时,也会抛出OOM异常
- 运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量与符号引用
- 受方法区内存限制,当常量池无法再申请到内存时会抛出OOM异常
- 直接内存并不是运行时数据区的一部分,但它受总内存限制,也可能会出现OOM异常
在类加载检查通过后,接下来虚拟机将为新生对象分配内存,而内存分配方式主要有两种:
- 指针碰撞
- 空闲列表
在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
- 对象头存储对象自身运行时数据(Mark Word),如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等类型指针(对象指向其类型元数据的指针)
- 实例数据对象真正存储的有效信息,即代码中的各类型字段内容
- 对齐填充由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,即任何对象大小都是8字节的整数倍,故实例数据部分没有对齐的话需要对齐填充来充当占位符补全
Java程序会通过栈上的reference(一个指向对象的引用)数据来操作堆上的具体对象,具体的访问方式由虚拟机实现。
主流访问方式主要有两种:
- 句柄
- 直接指针
采用不同的JDK及垃圾回收收集器均可能会产生不同的结果,以下实战均以JDK8,ParallelGC垃圾收集器为例运行代码
# 查看默认垃圾收集器VM参数
-XX: PrintCommandLineFlags -version
只要不断创建对象实例,同时又避免垃圾收集器回收,这样达到最大堆容量限制后便能产生OOM异常
public class Hello {
/**
* -Xms:最小堆内存20M -Xmx:最大堆内存20M 两者设置一样避免自动扩展
* VM参数:-Xms20M -Xmx20M -XX: HeapDumpOnOutOfMemoryError
*/
public static void main(String[] args) {
List<Hello> hellos = new ArrayList<>();
while (true) {
hellos.add(new Hello());
}
}
}
《Java虚拟机规范》明确允许Java虚拟机实现自行选择是否支持栈的动态扩展,而HotSpot虚拟机的选择是不支持扩展,所以除非在创建线程申请内存时就因无法获得足够内存而出现OutOfMemoryError异常,否则在线程运行时是不会因为扩展而导致内存溢出的,只会因为栈容量无法容纳新的栈帧而导致StackOverflowError异常
- 使用-Xss参数减少栈容量
public class Hello {
/**
* VM参数:-Xss128k
*/
private int stackLength = 1;
public void stackLeak() {
stackLength ;
// 递归调用方法,不断入栈
stackLeak();
}
public static void main(String[] args) throws Throwable {
Hello oom = new Hello();
try {
// 调用方法,入栈
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" oom.stackLength);
throw e;
}
}
}
- 定义了大量的本地变量,增大此方法帧中本地变量表的长度(即调整栈帧大小)
public class Hello {
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;
}
}
}