最近在学习 JVM(Java Virtual Machine)的内存区域相关知识,为巩固所学知识整理做个记录,个人学习总结的知识体系架构图如下:
Java 程序编译后生成的 Java 字节码( .class 文件或者 .jar 文件)需要通过 JVM 翻译才能被操作系统识别(0/1机器码),目前可实现跨平台甚至跨语言(如 kotlin)。
Java SE 体系架构中 JDK 提供工具,JRE 提供基础类库, JVM 则只负责翻译,其关系如下图:
JVM 运行过程是本次学习记录的重点,其运行流程图如下:
本次主要分析记录“运行时数据区”模块的工作流程,后续会有专门的文章记录“类加载器 classLoader” 模块的内部流程。
Java 虚拟机在执行 Java 程序过程中会将所管理的内存划分为若干个不同的数据区域:程序计数器、虚拟机栈、本地方法栈、方法区和堆。
其中,左侧灰色背景的 方法区 和 堆 属于线程共享区域;
右侧白色背景的 虚拟机栈、本地方法栈和程序计数器 属于线程私有区域。
因为线程共享区域相对较简单,故先总结这块的知识,后续专门分析线程私有区域知识。
静态变量+常量+类信息+运行时常量池存在方法区中
数组和几乎所有的对象实例存在 Java 堆内存中,堆是 gc 主要的回收区,一个 JVM 实例只存在一个堆内存,堆内存的大小是可以调节的,可分为三个部分:
A 新生区:伊甸区(Eden space)和2个幸存者区(Survivor pace)
重点:
如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:
B 养老区
养老区用于保存从新生区筛选出来的 JAVA 对象,一般池对象都在这个区域活跃。
C 永久区
永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。
重点:
如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。原因有二:
4.1 程序计数器:指向当前线程正在执行的字节码指令的地址
4.2 虚拟机栈(Java栈):Java栈是Java方法执行的内存模型,后进先出
每个线程私有的区域,它的生命周期与线程相同,一个线程对应一个java栈,每执行一个方法就会往栈中压入一个元素,这个元素叫“栈帧”,而栈帧中包括了方法中的局部变量、用于存放中间状态值的操作栈,动态连接及完成出口(返回地址)。
如果java栈空间不足了,程序会抛出StackOverflowError异常,所以递归最容易产生这样的问题,若递归如果深度很深,就会执行大量的方法,方法越多java栈的占用空间越大。
4.3 本地方法栈:保存的是native方法的信息
6.1 流程
申请内存–》类加载(class进入方法区)–》常量 静态变量进方法区 --》虚拟机栈入栈桢 --》栈帧的方法执行。
6.2 内存可视化工具HSDB:监控JVM内存运行情况
执行以下命令后会弹出HSDB窗口:
E:\jdk\lib>java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
然后再另开窗口在对应执行的类的目录后执行 jps 命令则会显示当前正在运行的进程,将进程对应ID输入HSDB中则可查看其中运行的线程,点击 Stack Memory 则可看到对应虚拟机栈以及各个栈帧: F:\androidproject\PracticeDemo\app\src\main\java\com\example\practicedemo\jvm\ex1>jps
1 功能
3 空间大小
栈的内存要远远小于堆内存,栈的深度是有限制的,可能发生StackOverFlowError问题
1 堆溢出
JVM在启动的时候会自动设置JVM Heap的值, 可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。Heap的大小是Young Generation 和Tenured Generaion 之和。
在JVM中如果98%的时间是用于GC,且可用的Heap size 不足2%的时候将抛出此异常信息。
分清 内存泄露还是 内存容量不足。泄露则看对象如何被 GC Root 引用。不足则通过 调大 -Xms,-Xmx参数。
解决方法 :
手动设置JVM Heap(堆)的大小。
2 栈溢出:程序所要求的栈深度过大导致。
解决方法 :
1:修改程序。2:通过 -Xss: 来设置每个线程的Stack大小即可。
3 方法区溢出:Class对象未被释放,Class对象占用信息过多,有过多的Class对象
解决方法 :
通过-XX:PermSize和-XX:MaxPermSize设置永久代大小即可。
4 本机直接溢出
出现这种情况的原因基本下面2点:
1 编译优化技术:方法内联
2 栈的优化技术:栈帧之间数据共享