JVM内存结构
JDK、JVM、Jre之间的关系
- JVM 只负责把.class文件翻译成操作系统可识别的语句。
- Jre 提供Java程序的运行环境
- JDK 提供工具
JVM的三大区域
- Java类加载器ClassLoader
- 运行时数据区(JVM内存)
- 执行引擎
注意: JVM是边解释边执行的。
运行时数据区
定义:Jre在执行Java程序的过程中会把他所管理的内存划分成若干个不同的数据区域。包括: - 程序计数器 - 虚拟机栈 - 本地方法栈 用来保存native方法的信息 - Java堆 - 方法区(运行时常量池) - 直接内存(也叫堆外内存,用于nio)
线程私有
- 虚拟机栈(A函数调用B函数,B函数会先入栈,此时B函数位于栈顶,被执行。执行完B出栈,使A回到栈顶,A继续执)
- 本地方法栈
- 程序计数器
线程共享
- 方法区
- 堆
程序计数器
程序计数器是一个很小的内存区域,指向当前线程正在执行的指令对应的字节码地址(指针)。
为什么需要程序计数器?
CPU时间片轮转回来的时候,通过程序计数器才直到程序执行到了哪里,以及从哪继续执行。
JVM的内存区域中,程序计数器是唯一不会OOM的储存区。
虚拟机栈
栈:FILA,后进先出。
虚拟机栈用来存储当前线程运行方法所需的数据、指令、返回地址等。
栈帧
- 局部变量表:用来存储局部变量(结构体和引用)
- 操作数栈:存放函数的执行。
- 动态连接: 用于多态
- 完成出口(返回地址):
|
|
当以上函数进入虚拟机栈时,就成为了一个栈帧,他内部做了两件事:
- 首先函数中有个创建变量i的操作,入栈到操作数栈;
- 将操作数栈的栈顶操作生成的数据(i)存入局部变量表下标为[1]的位置(局部变量表下标[0]位置是this,代表实例自己)。
Java的解释执行是基于操作数栈,所以移植性和兼容性好,而C语言是基于寄存器(硬件)运算,所以速度快。
虚拟机栈的大小 -Xss
本地方法栈
用来保存native方法的信息。比如hashCode()方法等加了native关键字的方法。
本地方法栈中存放的其实是C函数的链接,由于不是java方法,所以程序计数器不会计数。
方法区
用来存放:
- 类信息
- 常量
- 静态变量
- 即时编译后的代码
Java堆
用来存放:
- 对象
- 数组
参数:
- Xmx 堆区内存可被分配的最大上限
- Xms 堆内存初始内存分配的大小
问题,为何要分成方法区和堆
堆位于新生代和老年代,回收频繁;而方法区位于永生代(1.8叫元空间,使用的是物理内存,提升了容量),回收不频繁。这是一种动静分离的思想,有利于回收的高效。
怎么可视化查看内存
- AS的profile
- JDK9的HSDB
内存溢出
常见的内存溢出有以下三种:
- java.lang.OutOfMemoryError: Java heap space ——>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
- java.lang.OutOfMemoryError: PermGen space ——>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。
- java.lang.StackOverflowError ——> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
遗留问题
1.JVM 有哪些内存区域?(JVM 的内存布局是什么?) 2.StackOverFlow与OOM的区别?分别发生在什么时候,JVM栈中存储的是什么?堆中存储是什么?
今天状态不好,学的云里雾里,时间不够快下班了,也懒得总结了,留到后面再说吧。