JVM 内存模型
运行时数据区域
程序计数器(Program Conunter Regisiter)
程序计数器是一个比较小的内存空间,可以看作是当前线程执行的字节码行号指示器。本质就是记录字节码执行顺序。 在《Java 虚拟机规范》中没有任何 OutOfMemoryError 情况的区域。
虚拟机栈(JVM Stack)
虚拟机栈,存放的是线程运行时内部的局部变量,也可以理解为线程栈。
每个方法被执行的时候, 虚拟机会创建一个栈帧(Stack Frame)用于存放局部变量表(local variable),操作数栈(operand stack),动态连接,方法出口等信息。
栈帧(Stack Frame)随着方法的调用而创建,随着方法的结束而销毁(不论是正常结束还抛出异常)。
字节码指令分析(描述 JVM Stack 操作过程)
publicintadd(){inta =1;intb =2;intc = b - a;returnc;}0iconst_1//将 a 压入局部变量表栈顶1istore_1//对 a 进行赋值 1 2iconst_2//将 b 压入局部变量表栈顶3istore_2//对 b 进行赋值 24iload_2//读取 b 到操作数栈5iload_1//读取 a 到操作数栈6isub//执行 b - a7istore_3//将 int 类型的值存入局部变量表 3 8iload_3//读取 c 到操作数栈9ireturn//返回复制代码
局部变量表(local variable)
局部变量表存放了各种编译期 Java 虚拟机基本数据库类型(boolean、byte、char、short、int、float、long、dubble)和对象引用(reference 类型,即对象的起始位置指针或者对象句柄), 对象的真实数据通常存放在堆空间。
局部变量表中的存储空间通过变量槽(slot) 来表示,其中 64 位长度的 long 和 double 占 2 个变量槽。
操作数栈(operand stack)
每个栈帧都包含一个操作数栈的先进先出(FIFO)栈,栈帧中操作数栈的深度由编译期决定,并且通过方法的 code 属性保存以及提供给栈帧使用。
动态链接
每个栈帧都包含一个指向当前方法所在类型的运行时常量池的引用。以便对当前方法的代码实现动态链接。
在 class 文件中,一个方法如果要调用其它方法, 或者访问局部成员变量,则需要将符号引用(synbolic reference)来表示,动态链接的作用就是将这些符号引用转换为对实际方法的直接引用。
方法出口
方法正常完成,当前栈帧恢复调用者的责任,包括恢复调用者的局部变量表,操作数栈,以及正确的程序计数器递增。跳过刚才执行的方法调用指令等,低哦啊用着的代码被调用的方法正返回值压入调用者操作数栈后,会继续正常执行。
方法一场完成,某些指令导致了 JVM 虚拟机抛出异常,或者用户显示的通过thorw关键字跑出一场,同时在改该方法中没有捕获异常。如果方法异常调用完成,那不一定有方法返回值返回给调用者。
本地方法栈(Native Method Stack)
为本地方法所分配的内存空间,就是为native关键字修饰的方法提供服务的。
本地方法主要是 Java 来调用 C/C++ 函数库的调用方法。
方法区(Method Area)
主要存放数据有:常量,静态变量,类信息。
方法区存放的是静态变量的内存地址, 方法区里面有一个元空间, 在JDK1.8 之前叫永久代。
堆(Heap)
JVM 管理的最大的一块内存空间。与堆相关的一个重要概念是垃圾收集器。几乎所有的垃圾收集器都是采用分代收集算法,所以对内存空间也是基于这一点进行相应的划分:新生代和老年代,新生代分为Eden 空间、From Survivor 空间、To Survivor 空间。
对象创建的过程中首先会存在 Eden 区,然后经过 minor gc 过后进入 survivor ,进过 15 次 survivor 转移过后,进入老年代。
如果内存都不够用了就触发 full gc, 再次触发 GC 过后无法分配申请内存,JVM 就会抛出 OOM。
分析对象是否存在引用,是否被回收采用的是 GC ROOT 可达性分析。
直接内存(Direact Memory)
直接内存,不是 JVM 来管理,是通过操作系统来管理的, 与 Java NIO 密切相关。 Java 通过DirectByteBuffer来操作直接内存。
JVM 内存参数设置
内存参数配置
Spring-Boot 程序的 JVM 内存参数设置格式(Tomcat 启动直接在 bin 目录下的 Catalina.sh 文件设置)
java -Xms2048m -Xmx2048m -Xmn1024 -Xss512k -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -jar xxx-xxx.jar复制代码
关于元空间JVM 有两个:-XX:MetaspaceSize=N 和 -XX:MaxMetaspaceSize=N,对于 64 位 JVM 来说, 元空间默认是 21MB,默认的元空间的最大值是无限。
-XX:MaxMetaspaceSize: 设置元空间最大值,默认是 -1, 即不限制,或者说是受限制于本地内存大小。
-XX:MetaspaceSize:指定元空间的初始大小,以字节为单位,默认是 21M,达到该值过后就会触发 full gc 进行类型卸载,同时收集器会对该值进行调整;如果释放了大量的空间就适当降低该值;如果释放了很少的空间,那么就在不超过 -XX:MaxMetaspaceSize (如果设置)的情况下,适当提高该值。
由于调整元空间大小需要 full gc , 这是一个非常昂贵的操作,如果在启动过程中发生大量 full gc, 通常都是由于永久代或者元空间发生了大小调整,基于这种情况,一般建议在 JVM 参数将 MaxMetaspaceSize 和 MetaspaceSize 设置成一样的值,并设置得比初始值要大,对于 8G 的物理内存来说我们通常都会将这两个值设置为 256M。
堆空间内存溢出
importjava.util.ArrayList;importjava.util.List;publicclassHeapOverFlowTest{byte[] a =newbyte[1024*1024*2];// 2mbpublicstaticvoidmain(String[] args){ List list =newArrayList<>();while(true) { list.add(newHeapOverFlowTest()); } }}// 输出结果Exception in thread"main"java.lang.OutOfMemoryError: Java heap spaceat cn.edu.cqvie.jvm.HeapOverFlowTest.(HeapOverFlowTest.java:8)at cn.edu.cqvie.jvm.HeapOverFlowTest.main(HeapOverFlowTest.java:13)复制代码
虚拟机栈内存溢出
publicclassStackOverFlowTest{// JVM 设置// -Xss128k, -Xss默认1Mstaticintcount =0;staticvoidredo(){ count++; redo(); }publicstaticvoidmain(String[] args){try{ redo(); System.out.println(count); }catch(Throwable t) { t.printStackTrace(); } }}// 输出结果: 栈溢出java.lang.StackOverflowErrorat cn.edu.cqvie.jvm.StackOverFlowTest.redo(StackOverFlowTest.java:11)at cn.edu.cqvie.jvm.StackOverFlowTest.redo(StackOverFlowTest.java:11)at cn.edu.cqvie.jvm.StackOverFlowTest.redo(StackOverFlowTest.java:11)....复制代码
总结:
-Xss 设置越小 count 值越小,说明一个线程栈里能够分配的栈帧就越小,但是对于 JVM 整体来说能够开启的线程数就会更多。
方法区内存溢出
需要注意的是 1.8 内模型中,将运行时常量池数据放入堆中,所以我们限制方法区的大小对运行时常量池的限制毫无意义。最终也只会抛出java.lang.OutOfMemoryError: Java heap space异常。
下面通过GCLib 模拟方法区溢出模拟的一个例子。
/**
* -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
*/publicclassMyTest4{publicstaticvoidmain(String[] args){for(; ; ) { Enhancer enhancer =newEnhancer(); enhancer.setSuperclass(MyTest4.class); enhancer.setUseCache(false); enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invoke(obj, args1)); System.out.println("hello world"); enhancer.create(); } }}//输出结果Caused by: java.lang.OutOfMemoryError: Metaspaceat java.lang.ClassLoader.defineClass1(Native Method)at java.lang.ClassLoader.defineClass(ClassLoader.java:756) ......复制代码
JVM 监控工具
VisualVM
VisualVM 提供在 Java 虚拟机 (Java Virutal Machine, JVM) 上运行的 Java 应用程序的详细信息。在 VisualVM 的图形用户界面中,可以方便、快捷地查看多个 Java 应用程序的相关信息。
作者:大漠北
链接:https://juejin.cn/post/6925664904833662983
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
网友评论