jvm整体结构及内存模型
栈
在jvm中什么是栈?
其实栈是一种先进后出的数据结构,jvm的虚拟机栈(线程栈)就是用的这种数据结构。
个人理解为 一个线程先进入的方法,肯定是最后出栈的,最后执行的方法肯定是最先出栈的,一个简单的查询接口请求,先进入Controller 方法(controller方法栈帧),后进入Dao方法(Dao方法栈帧) ,最先结束(最先出栈)的肯定是Dao方法,最后结束的肯定是Controller方法。
JVM栈中每出现一个线程就会分配出一个线程栈(线程分配的数量大小取决于栈的内存空间)
栈帧
栈帧简单理解就是每个线程运行轨迹上所用到的方法,如上面举例的controller方法栈帧 ,Dao方法栈帧
栈帧内部又有以下几个部分:
- 局部变量:局部变量是Java虚拟机栈帧中的重要组成部分,用于存储方法中的局部变量和参数。其中局部变量如果是对象,那么这些局部变量存储的是这个对象实例在堆中的内存地址
- 操作数栈:操作数栈是一个重要的数据结构,它在JVM执行字节码指令时起到关键作用。操作数栈主要用于存储中间计算结果,以及作为参数传递的临时存储空间。用途如下
- 计算存储:当执行算术、逻辑或比较操作时,操作数栈用于存储计算过程中的中间结果。例如,在做加法或乘法操作时,操作数会被压入栈中,然后计算结果也会被压入栈中。
- 方法参数传递:在调用方法时,方法的参数会首先被压入操作数栈,然后方法的字节码指令可以使用这些参数进行操作。方法的返回值也会被压入操作数栈。
- 动态链接:在程序运行过程中把符号引用(引用的方法名)转换为符号对应的具体方法代码在内存中的直接地址
- 方法出口:方法执行完回到上一个方法的下一步
程序计数器
是一个特殊的寄存器,用于存储下一条指令的内存地址。程序计数器在CPU中,通常与指令指针(Instruction Pointer,IP)或类似的机制相关联。
在执行程序时,CPU从内存中读取指令并放入指令寄存器中,然后程序计数器通常会增加以指向下一条指令。这样,CPU可以不断地从内存中读取和执行指令,从而执行程序。
在多线程环境中,程序计数器的作用更加重要,因为它帮助CPU快速地在不同线程之间切换。当线程被切换时,程序计数器会被更新以指向当前线程的正确执行位置
程序计数器在每个线程栈中都有一而且是各自独有不共享
字节码执行引擎
字节码执行引擎是Java虚拟机(JVM)中的一个关键组件,它负责执行由Java编译器编译成字节码的程序。字节码执行引擎的作用是将字节码转换为特定平台上的本地代码并执行它们。
字节码执行引擎每执行一行代码都会动态的改变线程栈中程序计数器的值
堆
Java虚拟机(JVM)中的堆是一个动态数据结构,用于存储Java对象实例。它是JVM所管理的内存区域中最大的一块,主要用于存储对象实例。堆是由所有线程共享的,因此任何一个线程创建的对象都会存储在堆中,可以被所有线程访问。
在堆中,对象被存储为一系列的“槽”(slot),每个槽对应一个对象实例。这些槽在堆中是连续的,并且每个槽都有一个与之关联的类型信息。当一个对象被创建时,JVM会在堆中找到一个空闲的槽,将对象实例存储在该槽中,并更新堆的大小和对象实例的信息。
堆是垃圾收集器(Garbage Collector,GC)管理的主要区域。垃圾收集器会自动回收不再使用的对象,释放堆中占用的空间。这意味着当一个对象不再被引用时,垃圾收集器可以将其从堆中删除,回收其占用的空间。
理解Java堆的关键在于理解它是如何存储和管理对象实例的,以及它是如何与垃圾收集器交互的。这有助于更好地理解Java内存管理和性能调优方面的问题。
年轻代
Eden区:用于存放新创建的对象。当一个对象被创建时,它首先会被分配到Eden区
survivor区:用于存放生命周期未超过一个垃圾回收周期的对象
当Minor GC发生时,Eden区和S0区中的存活对象会被移动到另一个S1区或老年代(Old Generation)中。具体来说,首先会将Eden区和S0区中的存活对象复制到S1区,然后将Eden区和S0区清空,并将S1区的存活对象的年龄加1。如果对象的年龄达到一定阈值(由JVM参数决定),则该对象将被移动到老年代。设置两个Survivor区的最大好处是解决内存碎片化问题。因为有两个Survivor区,所以每次Minor GC后,会有一个Survivor区是空的,另一个Survivor区则是无碎片的。这种机制确保了整个过程中始终有一个Survivor区可用,减少了内存碎片化对应用程序性能的影响。注意这里的S0和S1是可以相互轮流使用的
老年代:静态变量引用的对象,对象池,缓存对象,spring容器对象
方法区
Java虚拟机(JVM)中的方法区是一个存储已加载的类信息、常量、静态变量以及即时编译器编译后的代码等数据的区域。它是JVM内存管理中的一个重要部分,也是每个线程共享的内存区域之一。
方法区在Java虚拟机中是必不可少的一块内存区域,它的大小会影响到系统可以支持的类数量,如使用大量的第三方库时,这个区域就会被占用掉很大的空间。
当Java应用程序启动时,JVM会加载应用程序中使用的类和库到方法区中。这些类和库的信息存储在方法区中,包括类的元数据、常量池、静态变量等。JVM使用方法区来支持类的加载、链接和卸载等操作。
方法区的存储结构包括:
- 类的元数据(Class Metadata):用于描述类的信息,如类的名称、父类的名称、实现的接口、字段和方法等信息。
- 常量池(Constant Pool):用于存储常量,如字符串常量、数字常量等。常量池是类的一个特殊区域,它包含了该类中使用的所有常量。
- 静态变量(Static Variables):存储在方法区中的变量,它们属于类本身,而不是类的实例。这些变量在类加载时被初始化,并且在整个应用程序生命周期中保持不变。
- 即时编译后的代码:JVM使用即时编译器将字节码转换为本地机器代码,这些代码会被存储在方法区中,以提高执行效率。
需要注意的是,方法区的大小也是有限制的,如果超过了这个限制,JVM会抛出OutOfMemoryError异常。为了避免这种情况,程序员需要注意控制加载的类和库的数量,以及合理地使用静态变量和常量等资源
方法区中如果有静态变量,那么方法区中静态变量放的堆中的对象内存地址
本地方法栈
Java虚拟机(JVM)中的本地方法栈(Native Method Stack)是用于支持native方法的执行的数据结构。在Java中,可以使用native关键字声明一个方法为native方法,这样该方法将使用本地代码实现,而不是由JVM解释或JIT编译。
当一个native方法被调用时,JVM会在本地方法栈上为其创建一个新的栈帧,该栈帧包含了该方法的执行上下文。与Java方法的栈帧不同,本地方法栈帧中不包含操作数栈和动态链接等信息,因为这些信息对于本地方法来说不是必需的。
本地方法栈的作用是提供了一个与本地代码交互的机制,使得Java程序可以调用操作系统或其他本地库的函数。
Minor GC
在Java虚拟机(JVM)中,Minor GC主要针对的是年轻代(Young Generation)中的Eden区进行垃圾回收。当Eden区空间不足以继续分配新对象时,JVM就会触发一次Minor GC,以清理不再使用的对象,回收内存空间。
以下是一些触发Minor GC的情况:
-
Eden区空间不足:当Eden区中的可用空间不足以分配新对象时,JVM会触发Minor GC来清理不再使用的对象。这是最常见的触发Minor GC的情况。
-
大对象直接进入老年代:如果一个大对象在Eden区创建时,发现Eden区剩余空间不足,且没有足够的连续空间来分配这个大对象,那么这个大对象就会直接在老年代中分配。这种情况下,虽然不会立即触发Minor GC,但可能会导致后续Eden区空间更快地耗尽,从而触发Minor GC。
-
动态年龄判定:JVM中有一个参数叫
-XX:MaxTenuringThreshold
,它用来设置对象从年轻代晋升到老年代的最大年龄。然而,在实际运行中,JVM并不总是要求对象的年龄必须达到这个值才能晋升到老年代。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor区空间的一半,那么年龄大于或等于该年龄的对象就可以直接进入老年代,而无须等到MaxTenuringThreshold中要求的年龄。这种情况下,也可能会间接导致Minor GC的发生。
Full gc
Full GC(全局垃圾收集)的发生通常由Java虚拟机(JVM)自动管理,以回收不再使用的对象所占用的内存。
以下是一些可能导致Full GC的情况:
- 内存不足:当Java堆内存不足时,JVM会触发Full GC以释放不再使用的对象所占用的内存。如果释放的内存不足以满足当前内存需求,JVM可能会抛出OutOfMemoryError异常。
- 长时间存活的对象:如果存在一些长时间存活的对象占用了大量内存,并且没有其他对象引用它们,JVM可能会在Full GC中清理这些对象。
- JVM参数设置:通过设置JVM参数,如-XX:+UseConcMarkSweepGC或-XX:+UseParallelGC等,可以影响Full GC的触发时机和执行方式。
需要注意的是,频繁的Full GC会对应用程序的性能产生负面影响,因为垃圾收集过程中会暂停应用程序的运行(STW)。因此,优化垃圾收集器、调整堆内存大小和配置适当的JVM参数是提高应用程序性能的重要步骤。
什么是STW?
STW(Stop-The-World)是指在Java虚拟机(JVM)执行垃圾回收(GC)时,将应用程序的所有线程暂停,直到垃圾回收完成。在STW期间,应用程序的执行被暂停,导致应用程序停顿或延迟。STW是不可避免的,因为垃圾回收算法需要将整个堆内存冻结,以便扫描和回收不再使用的对象。STW是垃圾回收器在后台自动发起和自动完成的,通常在用户不可见的情况下将用户正常的工作线程全部停掉。STW是GC事件发生过程中应用程序停顿的一种状态,停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉。为了减少应用程序停顿的时间,优化垃圾回收算法和调优JVM参数是必要的。
为什么STW是必须的?
Java的STW(Stop-The-World)是必须的,因为在定位堆中的对象时JVM会记录下对所有对象的引用。如果在定位对象过程中,有新的对象被分配或者刚记录下的对象突然变得无法访问,就会导致一些问题,比如部分对象无法被回收,更严重的是如果GC期间分配的一个GC Root对象引用了准备被回收的对象,那么该对象就会被错误地回收。因此,STW是为了确保垃圾回收的正确性和一致性,以避免出现内存泄漏和其他相关问题。
另外,STW也是为了尽量减少应用程序的停顿时间。由于垃圾回收过程中需要将整个堆内存冻结,并进行扫描和回收操作,这个过程可能需要一定的时间。如果不进行STW,垃圾回收器将无法进行工作,导致内存泄漏和性能问题。因此,STW是垃圾回收器在后台自动发起和自动完成的,通常在用户不可见的情况下将用户正常的工作线程全部停掉,以完成垃圾回收的过程。
虽然STW会导致应用程序的停顿,但是通过优化垃圾回收算法和调优JVM参数,可以减少STW的时间和影响,提高应用程序的性能和响应能力。
JVM内存参数设置
关于方法区中的元空间有两个参数:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N
-XX:MaxMetaspaceSize:设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize:指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M,达到该值就会触发 full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超 过-XX:MMaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。
full gc不单单只是回收堆,还会回收方法区。