一、java类的生命周期
1.加载(把class文件的数据加载到jvm内存的元空间)
2.连接
验证 验证语法是否正确
准备 给静态变量做内存分配和默认值分配
识别 解析常量池
3.初始化
静态变量赋初始值
静态代码块执行
4.使用(被jvm使用)
5.卸载(如果在程序中没有再使用到这个类,这个类会被从jvm内存中清除)
二、程序执行过程
在谈 JVM 内存区域划分之前,我们先来看一下 Java 程序的具体执行过程
Java 源代码文件经过编译器编译后生成字节码文件,然后交给 JVM 的类加载器,加载完毕后,交给执行引擎执行。在整个执行的过程中,JVM 会用一块空间来存储程序执行期间需要用到的数据,这块空间一般被称为运行时数据区,也就是常说的 JVM 内存。
所以,当我们在谈 JVM 内存区域划分的时候,其实谈的就是这块空间——运行时数据区。
三、运行时数据区
运行时数据区可以分为以下几个部分
01、程序计数器
程序计数器(Program Counter Register)所占的内存空间不大,很小一块,可以看作是当前线程所执行的字节码指令的行号指示器
02、Java 虚拟机栈
Java 虚拟机栈中是一个个栈帧,每个栈帧对应一个被调用的方法。当线程执行一个方法时,会创建一个对应的栈帧,并将栈帧压入栈中。当方法执行完毕后,将栈帧从栈中移除。栈遵循的是后进先出的原则,所以线程当前执行的方法对应的栈帧必定在 Java 虚拟机栈的顶部。
1)局部变量表
顾名思义,就是用来存储方法中的局部变量的,包括方法的参数。对于基本数据类型的变量,直接存储变量的值;对于引用类型的变量,存储的是对象的引用。局部变量表的大小在编译期间就确定了,程序执行期间,它的大小是不会改变的。
2)操作数栈
表达式的计算是在操作数栈中完成的。当一个方法刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是入栈/出栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
3)指向运行时常量池的引用
当前方法所属的类的运行时常量池的引用,引用其他的常量类或者使用字符串常量池中的字符串。
4)方法返回地址
方法执行完(不论是正常执行还是发生了异常)后需要返回到方法被调用的位置,程序才能继续执行,方法返回地址保存一些用来帮助恢复上层方法的执行状态的信息。
5)动态链接
每个栈帧都包含了一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
03、本地方法栈
本地方法栈与 Java 虚拟机栈类似,区别是本地方法栈执行的是本地方法,也就是带有 native 关键字修饰的方法。
04、堆
堆是所有线程共享的一块内存区域,在 Java 虚拟机启动的时候创建,用来存储对象(数组也是一种对象)。
以前,Java 中“几乎”所有的对象都会在堆中分配,但随着 JIT(Just-In-Time)编译器的发展和逃逸技术的逐渐成熟,所有的对象都分配到堆上渐渐变得不那么“绝对”了。从 JDK 7 开始,Java 虚拟机已经默认开启逃逸分析了,意味着如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。
05、元空间
JDK 8 的时候,原有的方法区(更准确的说应该是永久代)被彻底移除,取而代之的是元空间。
和堆一样,是线程共享的区域,它用来存储已经被 Java 虚拟机加载的类信息、常量、静态变量等。
JDK 7 的时候,字符串常量池从方法区中拿出来放到了堆中,运行时常量池中的其他东西还在方法区中。
JDK 8 的时候,移除了永久代,也就是说方法区不存在了,取而代之的是元空间。也就意味着字符串常量池在堆中,运行时常量池跑到了元空间。
四.堆的划分
堆内存如何划分,如何回收这些内容对象,有哪些回收算法?
说明:
有GC垃圾收集器回收这些对象;
老年代一般采用标记清除算法;新生代一般复制算法
五.实战:内存溢出的定位与分析
内存溢出在实际的生产环境中经常会遇到,比如,不断的将数据写入到一个集合中,出现了死循环,读取超大的文件等等,都可能会造成内存溢出。如果出现了内存溢出,首先我们需要定位到发生内存溢出的环节,并且进行分析,是正常还是非正常情况,如果是正常的需求,就应该考虑加大内存的设置,如果是非正常需求,那么就要对代码进行修改,修复这个bug。首先,我们得先学会如何定位问题,然后再进行分析。如何定位问题呢,我们需要借助于jmap与MAT工具进行定位分析。
接下来,我们模拟内存溢出的场景。
5.1 内存溢出场景
编写代码,向List集合中添加100万个字符串,每个字符串由1000个UUID组成。如果程序能够正常执行,最后打印ok。
import java.util.*;
public class TestJvmOutOfMemory {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
for (int i = 0; i < 10000000; i++) {
String str = "";
for (int j = 0; j < 1000; j++) {
str += UUID.randomUUID().toString();
}
list.add(str);
}
System.out.println("ok");
}
}