首页 > 系统相关 >【JVM详解&JVM优化】JVM内存模型

【JVM详解&JVM优化】JVM内存模型

时间:2024-11-01 13:51:06浏览次数:5  
标签:虚拟机 详解 线程 内存 JVM 方法 加载

一、介绍:

        JVM是java虚拟机, JVM(Java Virtual Machine)。对于Java不需要管理垃圾,jvm会自动帮助我们回收垃圾,但更好的掌握jvm如何帮助回收垃圾的,能让我们的系统更加稳定。

        所有的Java程序都需要在JVM中运行,JVM也是Java跨平台的原理所在,对于不同的平台,Windows,Linux,Mac OS等,有具体不同的JVM版本。这些JVM屏蔽了平台的不同,提供了统一的运行环境,让Java代码无需考虑平台的差异,运行在不同的环境中。


二、JVM内存模型

1. 怎么分析jvm内存

        使用Java自带的工具, jconsole 或者 jvisualvm

         jvisualvm 用的比较多,jvisualvm 装一个GC插件就可以了。

  • jdk安装路径\jdk1.8\bin\jconsole.exe

  • jdk安装路径\jdk1.8\bin\jvisualvm.exe

2. JVM 组成图

JVM大致分为四大块:

  • 类加载器子系统 :字节码加载

  • 运行时数据区 : 线程运行涉及到的区域

  • 执行引擎 :程序执行的引擎

  • 本地方法库 :接入其他语言lib库

而本地库接口也就是用于调用本地方法的接口,在此我们不细说,主要关注的是上述的4个组件

3.类加载过程

类是通过类加载器去加载的,类加载的过程包括了加载,验证,准备,解析和初始化这5个步骤

  1. 加载:找到字节码文件,读取到内存中.

  2. 验证:验证此字节码文件是不是真的是一个字节码文件

  3. 准备:为类中static修饰的变量分配内存空间并设置其初始值为0或null.但如果你的static修饰还加上了final,那么就会在准备阶段就会赋值.

  4. 解析:解析阶段会将java代码中的符号引用替换为直接引用.比如引用的是一个类,我们在代码中只有全限定名来标识它,在这个阶段会找到这个类加载到内存中的地址.

  5. 初始化:如刚才准备阶段所说的,这个阶段就是对变量的赋值的阶段.

以上过程都是在JVM执行的过程中自己完成的,我们无需干涉。

4. 类加载器有哪些

类加载器一般有4种,其中前3种是必然存在的

  • 启动类加载器:加载<JAVA_HOME>\jre\lib下的

  • 扩展类加载器:加载<JAVA_HOME>\jre\lib\ext下的

  • 应用程序类加载器:加载Classpath下的

  • 自定义类加载器,一般不会去自定义

5.类加载器的双亲委派模式

        双亲委派模式的加载规则,从最顶层的父类从下加载,优先使用爷爷加载,如果没有加载到再使用它爸比加载,如果它爸比也没有加载到,最后才到自己加载,如果自己也没有加载到就会报ClassNotFountException。在这过程中只要上一级加载到了,下一级就不会加载了,酱紫做的目的:

  • 不让我们轻易覆盖系统提供功能。

  • 也要让我们扩展我们功能。

 ① 而双亲委派机制是如何运作的呢?

        我们以应用程序类加载器举例,它在需要加载一个类的时候,不会直接去尝试加载,而是委托上级的扩展类加载器去加载,而扩展类加载器也是委托启动类加载器去加载。

        启动类加载器在自己的搜索范围内没有找到这么一个类,表示自己无法加载,就再让扩展类加载器去加载,同样的,扩展类加载器在自己的搜索范围内找一遍,如果还是没有找到,就委托应用程序类加载器去加载。如果最终还是没找到,那就会直接抛出异常了 → ClassNotFountException

② 而为什么要这么麻烦的从下到上,再从上到下呢

        这是为了安全着想,保证按照优先级加载。如果用户自己编写一个名为java.lang.Object的类,放到自己的Classpath中,没有这种优先级保证,应用程序类加载器就把这个当做Object加载到了内存中,从而会引发一片混乱。而凭借这种双亲委派机制,先一路向上委托,启动类加载器去找的时候,就把正确的Object加载到了内存中,后面再加载自行编写的Object的时候,是不会加载运行的。

结论:JDK自带的类是没法覆盖的,而引入的三方的JAR是可以自己定义相同的类来覆盖的。

6. JVM内存模型

JDK1.8以后,方法区被元空间替代,没有方法区了,元空间直接使用本地内存

7. 程序计数器的作用

程序计数器是线程私有的,虽然名字叫计数器,但主要用途还是用来确定指令的执行顺序,比如循环、分支、跳转、异常捕获等。而JVM对于多线程的实现是通过轮流切换线程实现的,所以为了保证每个线程都能按正确顺序执行,将程序计数器作为线程私有。程序计数器是唯一一个JVM没有规定任何OOM的区块(out of memory)。

程序计数器是一块非常小的内存空间,可以看做是当前线程执行字节码的行号指示器,每个线程都有一个独立的程序计数器,因此程序计数器是线程私有的一块空间。此外,程序计数器是Java虚拟机规定的唯一不会发生内存溢出的区域。

8. Java虚拟机栈

Java虚拟机栈也是线程私有的,每个方法执行都会创建一个栈帧,局部变量就存放在栈帧中,还有一些其他的动态链接之类的。

虚拟机会为每个线程分配一个虚拟机栈,每个虚拟机栈中都有若干个栈帧,每个栈帧中存储了局部变量表、操作数栈、动态链接、返回地址等。一个栈帧就对应Java代码中的一个方法,当线程执行到一个方法时,就代表这个方法对应的栈帧已经进入虚拟机栈并且处于栈顶的位置,每一个Java方法从被调用到执行结束,就对应了一个栈帧从入栈到出栈的过程。

  • 栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,线程私有。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程,栈帧随着方法调用而创建,随着方法结束而销毁

  • 局部变量表(储存方法参数和局部变量):局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。局部变量表的容量以变量槽(Variable Slot)为最小单位,Java虚拟机规范并没有定义一个槽所应该占用内存空间的大小,但是规定了一个槽应该可以存放一个32位以内的数据类型。

  • 操作数栈(用于计算的临时数据存储区):操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出栈(LIFO),当一个方法刚刚开始执行时,其操作数栈是空的,随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。一个完整的方法执行期间往往包含多个这样出栈/入栈的过程。

  • 动态链接(用来转化方法的内存地址直接引用的):在一个class文件中,一个方法要调用其他方法,

    需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于方法区中的运行时常量池。

  • 返回地址:方法的返回地址

9. 本地方法栈

本地方法栈与虚拟机栈的区别是,虚拟机栈执行的是Java方法,本地方法栈执行的是本地方法(Native Method),其他基本上一致,在HotSpot中直接把本地方法栈和虚拟机栈合二为一。

10. 方法区(1.6,1.7)

方法区主要用于存储虚拟机加载的类信息、常量、静态变量,以及编译器编译后的代码等数据。在jdk1.7及其之前,方法区是堆的一个“逻辑部分”(一片连续的堆空间),但为了与堆做区分,方法区还有个名字叫“非堆”,也有人用“永久代”(HotSpot对方法区的实现方法)来表示方法区。

从jdk1.7已经开始准备“去永久代”的规划,jdk1.7的HotSpot中,已经把原本放在方法区中的静态变量、字符串常量池等移到堆内存中,(常量池除字符串常量池还有class常量池等),这里只是把字符串常量池移到堆内存中;在jdk1.8中,方法区已经不存在,原方法区中存储的类信息、编译后的代码数据等已经移动到了元空间(MetaSpace)中,元空间并没有处于堆内存上,而是直接占用的本地内存(NativeMemory)。

去永久代的原因有:

  • 字符串存在永久代中,容易出现性能问题和内存溢出。

  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

  • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

11. 堆内存

堆和方法区一样(确切来说JVM规范中方法区就是堆的一个逻辑分区),就是一个所有线程共享的、存放对象的区域,也是GC的主要区域。其中的分区分为新生代、老年代。新生代中又可以细分为一个Eden、两个Survivor区(From、To)。Eden中存放的是通过new或者newInstance方法创建出来的对象,绝大多数都是很短命的。正常情况下经历一次gc之后,存活的对象会转入到其中一个Survivor区,然后再经历默认15次的gc,就转入到老年代。这是常规状态下,在Survivor区已经满了的情况下,JVM会依据担保机制将一些对象直接放入老年代。

堆内存主要用于存放对象和数组,它是JVM管理的内存中最大的一块区域,堆内存和方法区都被所有线程共享,在虚拟机启动时创建。在垃圾收集的层面上来看,由于现在收集器基本上都采用分代收集算法,因此堆还可以分为新生代(YoungGeneration)和老年代(OldGeneration),新生代还可以分为Eden、From Survivor、To Survivor。

12. 元空间 (1.8)

上面说到,jdk1.8中,已经不存在永久代(方法区),替代它的一块空间叫做“元空间”,和永久代类似,都是JVM规范对方法区的实现,但是元空间并不在虚拟机中,而是使用本地内存,元空间的大小仅受本地内存限制,但可以通过-XX:MetaspaceSize和-XX:MaxMetaspaceSize来指定元空间的大小

Jdk1.8去除了方法区,取而代之是元空间,直接使用本地内存

13. 执行引擎

执行引擎包含即时编译器(JIT)和垃圾回收器(GC),对即时编译器我们简单介绍一下,主要重点在于垃圾回收器.

即时编译器

看到这个东西的存在可能有些人会感到疑问,不是通过javac命令就把我们的java代码编译成字节码文件了吗,这个即时编译器又是干嘛的?

我们需要明确一个概念就是,计算机实际上只认识0和1,这种由0和1组成的命令集称之为”机器码”,而且会根据平台不同而有所不同,可读性和可移植性极差.我们的字节码文件包含的并不是机器码,不能由计算机直接运行,而需要JVM”解释”执行.JVM将字节码文件中所写的命令解释成一个个计算机操作命令,再通知计算机进行运算.

总结:Javac把源文件编译成字节码文件,即使编译JIT把字节码文件中的命令编译成机器码即计算机操作命令去执行。

14. OOM是什么,怎么处理

OOM OutOfMemoryError堆内存溢出

堆内存中主要存放对象、数组等当这些对象所占空间超过最大堆容量时,且垃圾回收期无法进行回收时就会产生OutOfMemoryError的异常。首先需要定位问题是内存分配过小还是算法问题导致的。可以把堆快照下载下来分析问题出在哪个位置,然后去找到相关代码进行分析是否是算法问题,或者同时处理的数据量太大导致。另外也需要通过JVM监视工具去监视内存大小,分析内存是否只够。然后进行大小调整。

堆内存异常示例如下:

/**
* 设置最大堆最小堆:-Xms20m -Xmx20m
* 运行时,不断在堆中创建OOMObject类的实例对象,且while执行结束之前,GC Roots(代码中的oomObjectList)到对象(每一个OOMObject对象)之间有可达路径,垃圾收集器就无法回收它们,最终导致内存溢出。
*/
public class HeapOOM {
   static class OOMObject {
   }
   public static void main(String[] args) {
       List<OOMObject> oomObjectList = new ArrayList<>();
       while (true) {
           oomObjectList.add(new OOMObject());
       }
   }
}

运行后会报异常,在堆栈信息中可以看到 java.lang.OutOfMemoryError: Java heap space 的信息,说明在堆内存空间产生内存溢出的异常。

新产生的对象最初分配在新生代,新生代满后会进行一次Minor GC,如果Minor GC后空间不足会把该对象和新生代满足条件的对象放入老年代,老年代空间不足时会进行Full GC,之后如果空间还不足以存放新对象则抛出OutOfMemoryError异常。常见原因:内存中加载的数据过多如一次从数据库中取出过多数据;集合对对象引用过多且使用完后没有清空;代码中存在死循环或循环产生过多重复对象;堆内存分配不合理;网络连接问题、数据库问题等。

不会自己改ide的对空间,通过虚拟机参数修改该运行空间。

①.  IDEA修改内存:↓ ↓ ↓ ↓ ↓

可以参考我的这篇文章

https://blog.csdn.net/longshehui/article/details/142481228?spm=1001.2014.3001.5501

②.  单个应用修改内存

15. StackOverflowError

虚拟机栈/本地方法栈溢出,StackOverflowError:当线程请求的栈的深度大于虚拟机所允许的最大深度,则抛出StackOverflowError,简单理解就是虚拟机栈中的栈帧数量过多(一个线程嵌套调用的方法数量过多)时,就会抛出StackOverflowError异常。最常见的场景就是方法无限递归调用,如下 Exception in thread “Thread-0” java.lang.StackOverflowError的异常。

总结:在线程较少的时候,某个线程请求深度过大,会报StackOverflow异常,解决这种问题可以适当加大栈的深度(增加栈空间大小),也就是把-Xss的值设置大一些,但一般情况下是代码问题的可能性较大;在虚拟机产生线程时,无法为该线程申请栈空间了,会报OutOfMemoryError异常,解决这种问题可以适当减小栈的深度,也就是把-Xss的值设置小一些,每个线程占用的空间小了,总空间一定就能容纳更多的线程,但是操作系统对一个进程的线程数有限制,经验值在3000~5000左右。在jdk1.5之前-Xss默认是256k,jdk1.5之后默认是1M,这个选项对系统硬性还是蛮大的,设置时要根据实际情况,谨慎操作。


三、结尾

标签:虚拟机,详解,线程,内存,JVM,方法,加载
From: https://blog.csdn.net/longshehui/article/details/143425354

相关文章

  • Python内存模型
    Python深浅拷贝一.变量的内存模型x=1print(id(x))#94454455464992print(id(1))#94454455464992print(id(5))#94454455465120x=5print(id(x))#94454455465120print(id(1))#94454455464992print(id(5))#94454455465120"""Python更改变量值,发生变化......
  • Nuxt.js 应用中的 components:extend 事件钩子详解
    title:Nuxt.js应用中的components:extend事件钩子详解date:2024/11/1updated:2024/11/1author:cmdragonexcerpt:components:extend是Nuxt.js中的一个生命周期钩子,允许开发者扩展新的组件到项目中。通过这个钩子,开发者可以动态地添加额外的组件,从而增强项目的功能......
  • maven 的scop 详解
    在Maven中,scope定义了一个依赖的使用范围,它决定了依赖在编译、测试、运行、打包等阶段的可见性和作用方式。了解这些作用域有助于控制依赖的加载时机和范围,从而优化构建和运行过程。Maven的scope主要有以下几种:1.compile(默认)描述:compile是Maven中的默认作用域,不指......
  • GitLab上怎么创建项目(步骤详解)
    创建项目在GitLab上需经过几个主要步骤:1.注册并登录账户;2.创建新项目;3.初始化及配置项目;4.代码管理与协作;5.持续集成/持续部署(CI/CD)的设置。首步为确保用户拥有一个GitLab账户并成功登录,紧接着便能通过简明的步骤创建并初始化自己的新项目,进而利用GitLab的多样功能进行代码管理和......
  • 聚类算法——Kernel K-Means (核K-均值聚类)聚类算法详解
    KernelK-Means聚类算法详解目录引言聚类算法概述K-Means算法回顾KernelK-Means算法概述KernelK-Means的工作原理核心思想与标准K-Means的区别KernelK-Means的算法步骤初始化计算核矩阵簇分配质心更新迭代与收敛数学基础目标函数核技巧(KernelTrick)特征映......
  • 聚类算法——Spherical K-Means聚类算法详解
    SphericalK-Means聚类算法详解聚类分析是数据挖掘和机器学习中的重要任务之一,其目的是将数据集中的对象分组,使得同一组内的对象相似度高,而不同组之间的对象相似度低。K-Means聚类算法是最经典和最广泛使用的聚类算法之一。然而,K-Means算法在处理高维稀疏数据或基于余弦相......
  • 一文详解精细化工行业持续增长的策略与路径解析
    随着全球经济的快速发展和科技的不断进步,精细化工行业正面临着前所未有的挑战和机遇。在这个过程中,数字化转型已成为推动行业持续增长的关键因素。精细化工行业,作为化学工业的一个重要分支,其产品广泛应用于医药、农药、涂料、香料等多个领域,对提高产品质量、优化生产流程、降低成......
  • STM32--CAN和RS485等半双工形式总线的多设备挂载接法详解
    在工业通信和自动化系统中,CAN总线和RS-485总线是两种常用的半双工通信总线。对于这些总线的多设备挂载接法,通常有两种方式:星型接法和手拉手型接法。本文将详细介绍这两种接法的特点、优缺点以及如何正确接线。星型接法星型接法是指所有设备都连接到一个中心节点,形成放射状......
  • STM32--STM32 微控制器详解
    STM32是STMicroelectronics(意法半导体)推出的一系列基于ARMCortex微控制器家族的产品。它们广泛应用于嵌入式系统领域,因其丰富的特性、低功耗和合理的价格而受到青睐。STM32的特点处理器核心:STM32微控制器基于不同的ARMCortex核心,如Cortex-M0、M3、M4、M7等。存储:包括闪......
  • Java面试题中高级进阶(JVM篇Java内存)
    前言本来想着给自己放松一下,刷刷博客,突然被几道面试题难倒!说说Java内存结构?说说对象分配规则?描述一下JVM加载class文件的原理机制?似乎有点模糊了,那就大概看一下面试题吧。好记性不如烂键盘***12万字的java面试题整理***Java内存结构方法区和堆是所有线程共享的内存区域;而j......