深入理解JVM内存结构(二)
- JVM 向操作系统申请内存:
JVM 第一步就是通过配置参数或者默认配置参数向操作系统申请内存空间,根据内存大小找到具体的内存分配表,然后把内存段的起始地址和终止地址分配给 JVM,接下来 JVM 就进行内部分配。 - JVM 获得内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小
-Xms30m -Xmx30m -Xss1m -XX:MaxMetaspaceSize=30m - 类加载(类加载的细节后续章节会讲):这里主要是把 class 放入方法区、还有 class 中的静态变量和常量也要放入方法区
- 执行方法及创建对象:启动 main 线程,执行 main 方法,开始执行第一行代码。此时堆内存中会创建一个 student 对象,对象引用 student 就存放在栈中。后续代码中遇到 new 关键字,会再创建一个 student 对象,对象引用 student 就存放在栈中。
JVM 在操作系统上启动,申请内存,先进行运行时数据区的初始化,然后把类加载到方法区,最后执行方法。
方法的执行和退出过程在内存上的体现上就是虚拟机栈中栈帧的入栈和出栈。
同时在方法的执行过程中创建的对象一般情况下都是放在堆中,最后堆中的对象也是需要进行垃圾回收清理的。
堆空间分代划分
堆被划分为新生代和老年代(Tenured),新生代又被进一步划分为 Eden 和 Survivor 区,最后 Survivor 由 From Survivor 和 To Survivor 组成
GC 概念
垃圾回收,在 JVM 中是自动化的垃圾回收机制。 System.gc(),主动发起垃圾回收,项目中切记不要使用。
深入辨析堆和栈
功能
以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char 等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放;
而堆内存用来存储 Java 中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中;
线程独享还是共享
栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。
堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
空间大小
栈的内存要远远小于堆内存
内存溢出
查看GC信息可以使用jvm自带的一些可视化工具,我用的是JHSDB.
1.栈溢出
参数:-Xss1m
HotSpot 版本中栈的大小是固定的,是不支持拓展的。
java.lang.StackOverflowError 一般的方法调用是很难出现的,如果出现了可能会是无限递归。
2.堆溢出
内存溢出:申请内存空间,超出最大堆内存空间。
如果是内存溢出,则通过 调大 -Xms,-Xmx 参数。
如果不是内存泄漏,就是说内存中的对象却是都是必须存活的,那么久应该检查 JVM 的堆参数设置,与机器的内存对比,看是否还有可以调整的空间,再从代码上检查是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运行时的内存消耗。内存泄漏是指分配出去的内存回收不回来,会导致oom。
package ex2.oom;
/**
* @author
* VM Args:-Xms30m -Xmx30m -XX:+PrintGCDetails
* 堆内存溢出(直接溢出)
*/
public class HeapOom {
public static void main(String[] args)
{
String[] strings = new String[35*1000*1000]; //35m的数组(堆)
}
}
**3.方法区溢出**
(1 ) 运行时常量池溢出
(2 )方法区中保存的 Class 对象没有被及时回收掉或者 Class 信息占