2-1JVM内存分析
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
一、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 中“几乎”所有的对象都会在堆中分配但随着 JITJust-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");
}
}