基本概念:
Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
简要言之,jmm是jvm的一种规范,定义了jvm的内存模型。它屏蔽了各种硬件和操作系统的访问差异,不像c那样直接访问硬件内存,相对安全很多,它的主要目的是解决由于**多线程通过共享内存**进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。可以保证并发编程场景中的原子性、可见性和有序性。
JVM原理:
基本概念:
Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
简要言之,jmm是jvm的一种规范,定义了jvm的内存模型。它屏蔽了各种硬件和操作系统的访问差异,不像c那样直接访问硬件内存,相对安全很多,它的主要目的是解决由于**多线程通过共享内存**进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。可以保证并发编程场景中的原子性、可见性和有序性。
JVM(Java Virtual Machine)是Java程序的运行环境,它是Java平台的核心组件之一。JVM的主要任务是将Java程序解释成可执行的机器码,并提供内存管理、安全性、异常处理等运行时环境支持。
JVM包括以下三个主要组件:
- Class Loader(类加载器):负责将Java字节码文件加载到JVM中,并生成对应的Class对象,以便JVM执行字节码文件中的指令。
- Execution Engine(执行引擎):负责执行Class对象中的字节码指令,包括解释执行和编译执行两种方式。
- Memory Management System(内存管理系统):负责管理JVM中的内存,包括堆内存和栈内存的分配、回收等操作。
JVM的执行过程如下:
- Class Loader将Java字节码文件加载到JVM中,并生成对应的Class对象。
- Execution Engine将Class对象中的字节码指令解释成可执行的机器码,或者将其编译成本地代码并执行。
- Memory Management System负责管理JVM中的内存,包括堆内存和栈内存的分配、回收等操作。
- JVM提供一系列的运行时环境支持,包括异常处理、线程管理、安全性等。
-
程序计数器–程序计数器是一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。
- 我们知道对于一个处理器(如果是多核cpu那就是一核),在一个确定的时刻都只会执行一条线程中的指令,一条线程中有多个指令,为了线程切换可以恢复到正确执行位置,每个线程都需有独立的一个程序计数器,不同线程之间的程序计数器互不影响,独立存储。
-
虚拟机栈
- 同计数器也为线程私有,生命周期与相同,就是我们平时说的栈,栈描述的是Java方法执行的内存模型。
- 每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。【栈先进后出,下图栈1先进最后出来】
-
本地方法栈
- 本地方法栈是与虚拟机栈发挥的作用十分相似,区别是虚拟机栈执行的是Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++,我们打开jdk安装目录可以看到也有很多用c编写的文件,可能就是native方法所调用的c代码。
-
堆
- 对于大多数应用来说,堆是java虚拟机管理内存最大的一块内存区域,因为堆存放的对象是线程共享的,所以多线程的时候也需要同步机制。因此需要重点了解下。
- 注意:它是所有线程共享的,它的目的是存放对象实例。同时它也是GC所管理的主要区域,因此常被称为GC堆,又由于现在收集器常使用分代算法,Java堆中还可以细分为新生代和老年代,再细致点还有Eden(伊甸园)空间之类的不做深究。
-
方法区
- 方法区同堆一样,是所有线程共享的内存区域,为了区分堆,又被称为非堆。
- 用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。
GC算法
标记清除算法
- 标记-- 标记阶段:标记的过程其实就是前面介绍的可达性分析算法的过程,遍历所有的GC Roots对象,对从GC Roots对象可达的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象;
- 清除-- 清除阶段:清除的过程是对堆内存进行遍历,如果发现某个对象没有被标记为可达对象(通过读取对象header信息),则将其回收。
- 引发的问题:
- 1、效率问题。标记和清除两个阶段的效率都不高,因为这两个阶段都需要遍历内存中的对象,很多时候内存中的对象实例数量是非常庞大的,这无疑很耗费时间,而且GC时需要停止应用程序,这会导致非常差的用户体验。
- 2、空间问题。标记清除之后会产生大量不连续的内存碎片(从上图可以看出),内存空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。
复制算法
- 复制算法-- 为了解决效率问题,复制算法出现了。复制算法的原理是:将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉;
- 缺点:
- 浪费一般内存空间
- 存活对象太高造成的复制时间就越长
- 缺点:
标记整理算法
- 不是直接对可回收对象进行回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边线以外的内存。
- 标记整理算法缺点
- 在执行过程中,需要标记存活对象且需要整理所有存活对象的引用地址,因为在效率上要低于复制算法
几个算法的效率:
- 效率:复制算法 > 标记/整理算法 > 标记/清除算法(标记/清除算法有内存碎片问题,给大对象分配内存时可能会触发新一轮垃圾回收)
- 内存整齐率:复制算法 = 标记/整理算法 > 标记/清除算法
- 内存利用率:标记/整理算法 = 标记/清除算法 > 复制算法
分代收集算法:
前几种算法的组合,将内存依据对象存活周期的不同将内存划分为几块,一般把内存分为新生代,老年代,永久代。其中新生代是朝生夕灭,存活时间较短;老年代:经过多次的MinorGC而存活下来,存活周期的较长;
因此分代收集算法的原理是采用复制算法来收集新生代,采用标记整理算法或者标记清除算法来进行回收老年代。
JVM堆内存划分:
JVM堆内存是Java程序中最大的一块内存区域,主要用于存储创建的Java对象。堆内存又分为新生代和老年代两部分,其中新生代又分为Eden区、Survivor区1和Survivor区2三部分。下面是JVM堆内存的详细划分:
-
Eden区:Eden区是新生代中最大的一块区域,用于存储新创建的Java对象。当Eden区满时,就会触发Minor GC(年轻代垃圾回收),将Eden区中的垃圾对象清理掉,并将存活的对象复制到Survivor区
-
Survivor区1和Survivor区2:Survivor区1和Survivor区2是新生代中的两个较小的区域,用于存储经过一次Minor GC后仍然存活的Java对象。当Survivor区1满时,就会将其中的存活对象复制到Survivor区2,并清空Survivor区1。当Survivor区2也满时,就会将其中的存活对象和Eden区中的存活对象一起复制到老年代中。
-
老年代:老年代用于存储长时间存活的Java对象,它的大小通常比新生代大得多。当老年代满时,就会触发Full GC(全局垃圾回收),将整个堆中的垃圾对象进行清理。Full GC的开销比Minor GC大得多,因此尽量减少Full GC的发生是很重要的。
-
Minor GC的执行流程如下:
- 首先,JVM会将新创建的Java对象放入新生代的Eden区中。
- 当Eden区满时,就会触发Minor GC。Minor GC会检查Eden区中的Java对象,将其中的垃圾对象进行清理,并将存活的对象复制到Survivor区1中。
- 当Survivor区1满时,就会将其中的存活对象复制到Survivor区2中,并清空Survivor区1。
- 当Survivor区2也满时,就会将其中的存活对象和Eden区中的存活对象一起复制到老年代中。
-
Major GC过程:
- 首先,JVM会扫描整个堆内存,包括新生代和老年代,标记出所有存活的Java对象。
- 然后,JVM会清理所有未被标记的Java对象,包括不再被引用的对象和死亡的线程等。
- 最后,JVM会对堆内存进行整理,以便更好地利用可用内存。
-
Full GC过程:
- JVM会扫描整个堆内存,包括新生代和老年代,标记出所有存活的Java对象
- 清理所有未被标记的Java对象,包括不再被引用的对象和死亡的线程
- JVM会对堆内存进行整理,以便更好地利用可用内存
-
Full GC触发的条件
- 堆内存空间不足:当堆内存空间不足时,JVM会尝试回收所有垃圾对象,以便释放更多的空间。如果进行Minor GC和Major GC后,堆内存仍然不足,就会触发Full GC。
- 老年代空间不足:当老年代中的对象占用的空间超过了老年代的总空间时,就会触发Full GC。这种情况通常发生在应用程序中存在大量的长生命周期的对象,且这些对象无法被回收。
- 调用System.gc()方法:当调用System.gc()方法时,JVM会尝试进行一次Full GC,以回收所有垃圾对象。但是,调用System.gc()方法并不能保证一定会触发Full GC,因为JVM可以自行决定何时进行垃圾回收。
- Perm区空间不足:Perm区是用于存储类信息和常量池的区域,当Perm区空间不足时,就会触发Full GC。这种情况通常发生在应用程序中存在大量的动态生成的类或者使用了大量的反射机制。
-
参考文章:
https://www.jianshu.com/p/76959115d486
https://www.cnblogs.com/fangfuhai/p/7203468.html