JVM内存图解
一、JDK、JRE、JVM三者之间的关系
JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。
Java运行环境(Java Runtime Environment,简称JRE)是一个软件,由太阳微系统所研发,JRE可以让计算机系统运行Java应用程序(Java Application)。
JRE的内部包含一个Java虚拟机(Java Virtual Machine,JVM)以及一些标准的类别函数库(Class Library)。
JVM :Java虚拟机(Java Virtual Machine),就是二级制字节码运行环境,负责装载字节码到其内部,解释编译器为对应平台是上的机器指令执行。
特点:一次编译到处运行,自动内存管理,自动垃圾回收功能。
二、JVM加载class文件的原理
1、Java程序从源代码到运行的过程:
2、JVM加载class文件原理:
1>装载:查找和导入class文件:
2>连接:
① 检查:检查载入的class文件数据的正确性;
② 准备:为类的静态变量分配存储空间;
③ 解析:将符号引用转换成直接引用(这一步是可选的)
3>初始化:初始化静态变量,静态代码块。
三、java运行时内存
JVM内存空间分为五部分:方法区、堆、Java虚拟机栈、本地方法栈、程序计数器元空间,JDK1.8之后方法区变成了元空间。
线程共享的是:堆、方法区、直接内存
线程私有的是:程序计数器、虚拟机栈、本地方法栈
3.1、堆
堆是Java虚拟机所管理的内存中最大的一块,用来存放对象实例,几乎所有的对象实例以及数组对象都在这里分配内存。
Java堆是垃圾回收的主要区域,因此也被称为GC堆(Garbage Collected Heap),Java堆分为新生代跟老年代,再细致划分可以分为:Eden空间、From Survivor、To Survivor空间等,进一步划分的好处是为了更好的回收或者更快的分配内存。
JDK7之前,堆被分为三部分:新生代、老年代、永生代
JDK8之后,方法区的永久代被移除了,取而代之的是元空间,元空间使用的是直接内存。
3.1.1 堆内存分布
jvm 将堆内存分为 年轻代和年老代。年轻代和年老代的比例为1:2 。年老代用来存储存活时间比较长或者大对象。年轻代用来存储错过时间比较短的对象。年轻代中又分为Eden 区和两个Survivor 区,比例为8:1:1。
3.1.2 对象分配过程:
1、首先会将对象放入年轻代的 Eden 区,如果Eden 区能发下就放入。
2、如果 Eden 区放不下了,就会触发 YangGC。会对Eden 区和使用的 Survivor 区进行垃圾回收,存活的对象保存到另一个空闲的Survivor 区。然后将新对象放入 Eden 区。
3、当Survivor 区有对象经历 15 次 yangGC 后还存活,就迁移到年老代。15次是默认的,可以调整。
4、如果新增对象大小超过Eden 区 一半时,会直接加入年老区。如果年老区 能放下就放入。
5、如果年老区放不下,就会触发OldGC。
6、如果OldGC 后,还是放不下对象,就会触发FullGC
7、如果FullGC 还放不下,就会报OOM 异常。
3.2、方法区
方法区用于存储已经被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码数据。方法区也被称为永久代。
为什么将永久代替换为元空间:
堆内存中最常见的错误是OutOfMemoryError错误,永久代有一个JVM本身设置的固定大小上线,无法进行调整;元空间使用的是直接内存,受本机内存限制,出现溢出的几率比永久代小。元空间存放的是类的元数据,加载类的数量就不受MaxPermSize控制了,由系统实际可用的空间来控制,这样能加载的类就更多了。
3.3、虚拟机栈
Java虚拟机栈是描述JAVA方法执行的内存模型,Java虚拟机栈会为每一个即将执行的方法创建一个叫做“栈帧”的区域,该区域用来存储该方法运行时需要的一些信息,包括:局部变量表、操作数栈、动态链接、方法返回地址等。比如我们方法执行过程中需要创建变量时,就会将局部变量插入到局部变量表中,局部变量的运算、传递等在操作数栈中进行,当方法执行结束后,这个方法对应的栈帧将出栈,并释放内存空间。栈中会发生的两种异常,StackOverFlowError和OutOfMemoryError,StackOverFlowError表示当前线程申请的栈超过了事先定好的栈的最大深度,但内存空间可能还有很多。 而OutOfMemoryError是指当线程申请栈时发现栈已经满了,而且内存也全都用光了。
3.4、本地方法栈
本地方法栈结构上和Java虚拟机栈一样,只不过Java虚拟机栈是运行Java方法的区域,而本地方法栈是运行本地方法的内存模型。运行本地方法时也会创建栈帧,同样栈帧里也有局部变量表、操作数栈、动态链接和方法返回地址等,在本地方法执行结束后栈帧也会出栈并释放内存资源,也会发生OutOfMemoryError。
3.5、程序计数器
程序计数器是一个比较小的内存空间,用来记录当前线程正在执行的那一条字节码指令的地址。如果当前线程正在执行的是本地方法,那么此时程序计数器为空。
程序计数器有两个作用:
1、字节码解释器通过改变程序计数器来一次读取指令,从而实现代码的流程控制,比如我们常见的顺序、循环、选择、异常处理等。
2、在多线程的情况下,程序计数器用来记录当前线程执行的位置,当线程切换回来的时候仍然可以知道该线程上次执行到了哪里。
而且程序计数器是唯一一个不会出现OutOfMeroryError的内存区域。