当前位置: 首页 > news >正文

总结JVM重要知识点

一.类加载和创建对象的过程

1.类加载

      1.编译 : 将源码文件(.java)编译成JVM可以解释的.class文件 . 语法分析>语义分析>注解处理 , 生成class文件

      2.加载 :

                装载 : 字节码本来存储在硬盘上 , 需要运行时 , 有类加载系统负责将类的信息加载到内存中(方法区) , 使用的是类加载器进行加载 , 充当的是一个快递员的身份. (双亲委派机制)下面会有:

                连接 : 效验class信息 , 为类的静态变量分配内存 , 初始化为默认值

                初始化 : 为变量赋值为正确的初始值

      3.解释 : 将字节码转换成操作系统可识别的执行指令.

                字节码解释器(直接解释) : jvm运行程序时 , 逐行对字节码指令进行翻译 , 效率低

                即时编译器(JIT)(编译解释) : 对某段代码整体编译后执行 , 效率高 , 但是编译需要耗费一定的时间.

      4.执行 : OS(操作系统)识别解释阶段生成的指令 , 调用系统的硬件执行最终的程序指令.

2.创建对象的过程(在我们使用new创建对象的时候)

  1. jvm会先去加载这个对象的类的字节码文件 , 并把其中static的属性放到方法区中保存 , 此时这些属性还是其类型对应的默认值 , (static final的变量除外 , jvm会直接对其进行赋值)

  2. 紧接着jvm会对这些方法区中的static变量进行赋值

  3. 在方法区中static属性赋完值后 , new关键字就会在堆中去是申请一块空间用来存储对象 , 此时对象中的成员属性就在内存中存在了 , 但会被赋予其默认值

  4. 在new关键字在堆中开辟空间后 , jvm就会调用对象的构造方法 , 在构造方法中对成员属性开始赋值 , 当构造方法执行完毕后 , 成员属性也随之完成了赋值 , 到这里 , 整个对象的创建过程才算完成

3.双亲委派机制

  把请求交由父类处理 , 是一种任务委派模式

加载一个类时 , 委托给父类加载器进行加载 , 如果父类加载器没有找到 , 就向上级委托 , 直到引导类加载器 , 父类加载器找到就直接返回 , 如果没有找到就委托给自定义加载器 , 如果还没有找到 , 就直接抛异常ClassNotFoundException

JVM内存结构

1.运行时数据区

线程的私有区域 : 程序计数器 , 虚拟机栈 , 本地方法栈

线程的共享区域 : 堆 , 方法区

线程的私有区域的生命周期与线程相同 , 随着线程的启动而创建 , 随着线程的结束而销毁

共享区域随着虚拟机的启动而创建 , 随着虚拟机的关闭而销毁

1.程序计数器

可以看做是当前线程所执行字节码的行号指示器 , 用来指向下一个将要执行的指令代码 , 有执行引擎来读取下一条指令 , 是唯一一个没有内存溢出的区域

一个线程的执行 , 就是通过字节码解释器来改变当前计数器的值 , 来获取下一条将要执行的字节码指令 , 从而保证线程的正常执行.

2.虚拟机栈

就是一个执行java方法的内存模型 , 每个方法在执行的同时都会创建一个栈帧 , 栈帧中有:

     局部变量表(存储一些方法内部的变量) , 操作栈(用于计算方法内部的值) , 动态链接(有时候要用到类中的一些变量 , 所以需要一个引用地址指向运行时常量) , 返回地址(用来返回之前调用它的方法)

栈帧就是用来记录方法执行的过程 , 方法执行的过程中 , 虚拟机会创建一个与之对应的栈帧 , 方法的执行与返回对于栈帧在虚拟机中的入栈和出栈

线程1在cpu1上面运行,线程2在cpu2上面运行,在cpu资源不足时其他线程将处于等待状态。在线程内部,每个方法的执行和返回都对应一个栈帧的入栈和出栈,每个运行中的线程当前只有一个栈帧处于活动状态

3.本地方法栈

用来管理本地方法的调用

4.堆

       在JVM运行过程中创建的对象和产生的数据都被存储在堆中,堆是线程共享的内存区域,也是垃圾收集器进行垃圾回收最主要的内存区域。

解决程序的运行问题,即程序如何执行,或者说如何处理数据.

解决的是数据存储的问题,即数据怎么放,放在哪儿

一个 JVM 实例只存在一个堆内存,堆也是 Java 内存管理的核心区域

java8 及之后堆内存分为:新生区(新生代)+老年区(老年代)

新生区分为 Eden(伊甸园)区和 Survivor(幸存者)区

 为什么要进行分区(分代)?

1.将对象根据存活的概率进行分类 , 对存货时间长的对象 , 放到老年区 , 从而减少扫描垃圾的时间以及GC频率

2.针对分类进行不同的垃圾回收算法 , 可以扬长避短

分代的收集思想Minor GC、Major GC、Full GC

JVM 在进行 GC 时,并非每次都新生区和老年区一起回收的,大部分时候回收的都是指新生区.针对 HotSpot VM 的实现,它里面的 GC 按照回收区域又分为两大类型:一种是部分收集,一种是整堆收集.

部分收集:不是完整收集整个 java 堆的垃圾收集.其中又分为:

新生区收集(Minor GC/Yong GC):只是新生区(Eden,S0,S1)的垃圾收集.

老年区收集(Major GC / Old GC):只是老年区的垃圾收集.

整堆收集(Full GC):收集整个 java 堆和方法区的垃圾收集.

整堆收集出现的情况:

     1.调用System.gc()时会进行full GC

     2.当堆空间中对象的数量已经达到了预设的阈值 

     3.当堆空间中的对象已经被分配到了连续的内存区域,无法再分配更多的内存时。

在进行Full GC时 , java虚拟机会停止掉所有的正在运行的线程 , 而且,Full GC的执行时间通常比局部回收(Partial GC)要长得多,因为它需要扫描整个堆空间以确定哪些对象可以被回收。因此,应该尽可能地避免Full GC的发生,以减少应用程序的停顿时间。

5.方法区

方法区也是被共享的 , 用于存储已被虚拟机加载的类的信息 , 常量 , 静态变量 , 即时编译器编译后的代码 , 还包括一个特殊的区域"运行时常量池"

方法区看做是一块独立于 java 堆的内存空间

常量池的好处?

常量池避免了频繁的创建和销毁对象从而影响了系统的性能 , 其也实现了对象的共享

Integer常量池:

            Integer 的 valueOf 方法很简单,它判断变量是否在 IntegerCache 的最小值(-128)和最大值(127)之间,如果在,则返回常量池中的内容,否则 new 一个 Integer 对象。

字符串常量池 :

            第一种使用 new 创建的对象,存放在堆中。每次调用都会创建一个新的对象。

            第二种:先在栈上创建一个 String 类的对象引用变量 str,然后通过符号引用去字符串常量池中找有没有 “abcd”,如果没有,则将“abcd”存放到字符串常量池中,并将栈上的 str 变量引用指向常量池中的“abcd”。如果常量池中已经有“abcd”了,则不会再常量池中创建“abcd”,而是直接将 str 引用指向常量池中的“abcd”。

JDK7 及以后的版本中将字符串常量池放到了堆空间中。因为方法区的回收效率很低,我们开发中会有大量的字符串被创建, 回收效率低。放到堆里,能及时回收内存

为什么方法区的回收效率低呢?

        方法区的回收在 Full GC 的时候才会执行永久代的垃圾回收,而 Full GC 是老年代的空间不足、方法区不足时才会触发。这就导致字符串常量池回收效率不高

2.垃圾标记算法

     就是用来判断该对象是否是垃圾 , 是否有引用指向该对象

(1)引用计数算法

    给堆内存中的每一个对象记录一个引用个数 , 引用个数为0的认为是垃圾 , 但是无法解决循环引用的问题.

   

(2)可达性分析算法

 在内存中 , 从根对象(GC root , 一组活跃的对象) 向下一直找引用 , 找到的对象就不是垃圾, 没找到的就是垃圾.

哪些可以作为GC root呢?

     在虚拟机栈中 , 当前栈顶的栈帧是活跃的 , 当前栈帧里面的局部变量锁指向堆内存中对象的就可以作为GC root

 对象的finalization机制

   只要记住对象在被回收之前会调用一次finalize方法, 它就是用来执行一些清理操作 , 比如释放资源 , 但是它的执行时间是不固定的, 也不能保证一定会被执行。这是因为垃圾收集器的行为是不可预测的,可能会出现一些意外的情况,例如在finalize()方法执行过程中发生了异常等。因此,程序员不能依赖finalize()方法来进行资源的释放和清理操作,而应该使用try-with-resources或finally块等机制来确保及时释放资源。

垃圾回收算法

1.标记清除算法

   最简单残暴的算法, 直接将垃圾干掉 , 但存在内存碎片的问题

内存碎片:可能有10M的空余内存,但程序申请9M内存空间却申请不下来(10M的内存空间是垃圾清除后的,不连续的)。

2.标记复制算法

 把存活的对象复制这样没有了内存碎片的问到另一块空间 , 复制完成后 , 把原有的整块空间干掉, 题 , 但是内存的利用率很低 , 得有一块新的区域给复制过去

3.标记压缩算法

 把存活的对象一道一边, 垃圾移到另一边 , 再将垃圾一起清理掉

stop the world(应用停止访问)

回收垃圾的时候 , 程序有短暂的时间是不能正常运行的 , 又由于垃圾回收是会导致stop the world,所以分代的一个原因是为了使stop the world持续的时间尽可能短。

年轻代特点:区域相对老年代较小,对象生命周期短、存活率低,回收频繁,适合标记复制算法。

老年代特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。这种情况存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记- 清除或者是标记-清除与标记-整理的混合实现。

总结:不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点使用不同的回收算法, 以提高垃圾回收的效率,同时使stop the world持续的时间尽可能短。

垃圾回收器

回答思路:单线程-->多线程-->CMS-->G1

单线程缺点(新生代Serial,老年代Serial Old):只有一个线程进行垃圾回收,效率低

多线程(新生代Paraller,老年代Paraller Old):多线程垃圾回收器内部提供多个线程进行垃圾回收,在多 cpu 情况下大大提升垃圾回收效率。

两者的共同缺点:会暂停其他用户线程,也就是Stop the World

所以垃圾回收器的优化思路就是停顿的时间能不能尽可能的短,用户线程和 GC 线程能不能去并发执行,让垃圾收集过程中用户也不会感到明显的卡顿。

1.CMS

老年代推出了一款垃圾回收器CMS,采用的是标记清除算法

1.初始标记:Stop The World,仅使用一条初始标记线程对所有与 GC Roots 直接关联的对象进行标记。

2.并发标记:垃圾回收线程,与用户线程并发执行。此标记阶段标记出间接关联的对象。

3.重新标记:Stop The World,使用多条标记线程并发执行,将刚才并发标记过程中新出现的废弃对象标记出来。

4.并发清除:只使用一条 GC 线程,与用户线程并发执行,清除刚才标记的对象。 这个过程非常耗时。

所以整个思路就是:将耗时时间长的阶段与用户线程一起并发工作,因此,总体上说,CMS 收集器的内存回收过程是与用户线程一起并发执行的。

CMS中的问题:

1、CPU敏感(在多核的情况下使用最好,如果没有多少核使用这个没有多少意义,还要不停的上下文切换)

2、浮动垃圾和内存碎片:有可能重写标记阶段完后,又产生了一些垃圾,但是这些垃圾只能等下次垃圾回收在清除,同时标记清除算法的缺点也非常明显,会产生内存碎片,申请大对象(例如数组要求连续空间)可能申请不下来。

2.G1

G1垃圾回收器是在 Java7 之后引入的一个新的垃圾回收器,对老年代和新生代都可以进行回收。

以前学习堆的分区时候,了解到堆的分区主要为:伊甸园、幸存者区以及老年区的知识。

学习G1之前要忘记之前堆的分区的相关知识,因为 G1 把堆内存分割为很多不相关的区域(Region)

使用不同的 Region 来表示 Eden、幸存者 0 区,幸存者 1 区,老年代等。G1 GC 有计划地避免在整个 Java 堆中进行全区域的垃圾收集。G1 跟踪各 个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时 间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。  

  • 并行与并发:G1能充分利用多CPU,多核环境下的硬件优势。GC线程并行执行,与用户线程并发执行

  • 分代收集:能够采用不同的方式去处理新创建的对象和已经存活了一段时间的对象,不需要与其他收集器进行合作。

  • 空间整合:G1从整体上来看基于“标记-整理”算法实现的收集器,从局部上看是基于复制算法实现的(也就是一个Region是标记清除,两个region之间是复制),因此G1运行期间不会产生空间碎片。

    优点:能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配,不会产生内存碎片,有利于长时间运行。同时可以根据用户所期望的 GC 停顿时间来指定回收计划,尽可能的满足用户期望。

排查JVM的问题

 对于还在正常运行的系统来说

  1.可以使用jmap来查看JVM中各个区域的使用情况

  2.可以使用jstack来查看线程运行的情况 , 比如哪些线程阻塞 , 是否出现了死锁

  3.可以通过jstat命令来查看垃圾回收的情况 , 特别是full GC , 如果发现full GC比较频繁 , 那么就得进行调优

对于已经发生了OOM的系统

1.⼀般生产系统中都会设置当系统发⽣了OOM时,生成当时的dump文件

2.我们可以利⽤jsisualvm等⼯具来分析dump⽂件

3.根据dump文件找到异常的实例对象,和异常的线程(占用CPU高),定位到具体的代码

4.然后再进行详细的分析和调试

一个独享从进入到JVM , 再到GC清除的经历

1.首先把字节码文件内容加载到方法区

2.根据类的信息在堆中创建对象

3.对象首先会在堆中的Eden区 , 经过一次Minor GC后 , 对象如果存活 , 就会进入到Suvivor区 , 在后续的每次的MinorGC中 , 如果对象一直存活 , 就会在Suvivor区来回移动 , 每移动一次年龄就会增长1

4.当年龄超过15后 , 如果对象仍然存活 , 就会进入到老年代

5.如果经过Full GC , 被标记为垃圾对象 , 那么就会被GC清除掉.

           

相关文章:

总结JVM重要知识点

一.类加载和创建对象的过程 1.类加载 1.编译 : 将源码文件(.java)编译成JVM可以解释的.class文件 . 语法分析>语义分析>注解处理 , 生成class文件 2.加载 : 装载 : 字节码本来存储在硬盘上 , 需要运行时 , 有类加载系统负责将类的信息加载到内存中(方法区) , 使用的是类…...

奇技淫巧第8期

学无止境。 下面是对去年11月至今年5月的零散知识点总结。 春节期间好好放松了一两个月,来校后又懒散的度过了一两个月,直到论文评审意见下来,才开启冲刺模式狂干了一两个月。总的来说,这半年来摸的时间比较多。好,不废…...

这个 归并排序详解过程 我能吹一辈子!!!

文章目录 归并排序概念归并排序算法思路归并排序递归实现归并排序非递归实现 归并排序概念 1945年,约翰冯诺依曼(John von Neumann)发明了归并排序,这是典型的分治算法的应用。 归并排序(Merge sort)是建立…...

docker版jxTMS使用指南:自动生成代码

本文讲解4.0版jxTMS的自动生成代码功能, 整个系列的文章请查看:docker版jxTMS使用指南:4.0版升级内容 docker版本的使用,请参考:docker版jxTMS使用指南 任何一个管理系统都需要对管理对象进行管理,包括最…...

聚观早报 | 小冰启动GPT克隆人计划;ofo创始人在美创业改做咖啡

今日要闻:小冰启动“GPT克隆人计划”;ofo创始人在美创业改做咖啡;OpenAI正准备新的开源AI模型;青年失业率首破20%创新高;微软收购动视暴雪获批 小冰启动“GPT克隆人计划” 5 月 16 日,小冰公司…...

面试造航母,入职拧螺丝,工资离了个大谱...

有粉丝跟我吐槽说:金三银四去面试软件测试岗,真的是面试造航母,入职拧螺丝,工资还低 这种现象很正常,因为找一个测试员,当然希望他能做的业务越多越好,最好像机器猫一样,啥事儿都能…...

Python+selenium自动化元素定位防踩坑

在自动化UI测试过程中常常会在元素定位阶段就踩坑,碰到困扰已久的问题。 以下是个人整理元素定位报错原因和解决方法。 踩坑一:StaleElementReferenceException selenium.common.exceptions.StaleElementReferenceException: Message: stale element re…...

【计算机组成原理】实验一

文章目录 实验一 数据传送实验1. 实验目的2. 实验仪器3. 原理概述4. 实验内容步骤4.1 手动实验环境的建立4.2 手控传送实验 5. 实验结论及问题讨论 实验一 数据传送实验 1. 实验目的 2. 实验仪器 3. 原理概述 4. 实验内容步骤 4.1 手动实验环境的建立 1)初始待令状态 上电或…...

前端022_广告模块_修改功能

广告模块_修改功能 1、需求分析2、Mock添加查询数据3、Mock修改数据4、Api调用回显数据5、提交修改后的数据6、效果1、需求分析 需求分析 当点击 编辑 按钮后,弹出编辑窗口,并查询出分类相关信息进行渲染。修改后点击 确定 提交修改后的数据。 2、Mock添加查询数据 请求URL…...

makefile 学习(3):C++的编译及库文件的生成与链接

1. 介绍 C语言的相关后缀 .a 文件是一个静态库文件.c,.c ,.cp,.cpp,.cc,.cxx 这几种后缀都可以表示c的源文件.h ,.hpp c语言的头文件.i 是c预处理文件.o 目标文件.s汇编语言的文件.so 动态库或者共享库或者称为运行时库 2. C编译 2.1 预处理 g -E helloworld.cpp # 虽…...

Ceph crush运行图

Crush map介绍 ceph集群中由monitor负责维护的运行图包括: Monitor map:监视器运行图osd map:osd运行图PG map:PG运行图Crush map:crush运行图Mds map:mds运行图 crush map是ceph集群物理拓扑的抽象&…...

【分布族谱】泊松分布和二项分布、正态分布的关系

文章目录 泊松分布和二项分布的关系和正态分布的关系 泊松分布 如果在有限时间 ( 0 , 1 ) (0,1) (0,1)内进行 n n n次伯努利实验,那么每次伯努利实验所占用的时间为 1 n \frac{1}{n} n1​,按照自然规律,一件事情肯定是时间越长越容易发生&am…...

关于QTreeWidget的setData函数

当使用 Q T r e e W i d g e t I t e m QTreeWidgetItem QTreeWidgetItem 的 s e t D a t a setData setData 方法时,需要传递三个参数,分别是列索引、角色和数据。 列索引:表示要设置数据的列的索引。 Q T r e e W i d g e t I t e m QTre…...

Microsoft Office 2003的安装

哈喽,大家好。今天一起学习的是office2003的安装,这个老版本的office可是XP操作系统的老搭档了,有兴趣的小伙伴也可以来一起试试手。 一、测试演示参数 演示操作系统:Windows XP 不建议win7及以上操作系统使用 系统类型&#xff…...

使用Spring Boot和Spring Cloud实现多租户架构:支持应用多租户部署和管理

使用Spring Boot和Spring Cloud实现多租户架构:支持应用多租户部署和管理 一、概述1 什么是多租户架构?2 多租户架构的优势3 实现多租户架构的技术选择 二、设计思路1 架构选型1.1 Spring Boot1.2 Spring Cloud 2 数据库设计3 应用多租户部署3.1 应用隔离…...

智聚北京!相约全球人力资源数智化峰会

人力资源是推动经济社会发展的第一资源。作为我国经济压舱石的中央企业在对标世界一流企业和管理提升方面的持续创新,各行业领军企业围绕组织变革、管理升级、全球化发展走深走实。人力资源管理正从传统职能管理与管控,向紧贴业务战略实现、组织边界和人…...

工业缺陷检测数据及代码(附代码)

介绍 目前,基于机器视觉的表面缺陷检测设备已广泛取代人工视觉检测,在包括3C、汽车、家电、机械制造、半导体与电子、化工、制药、航空航天、轻工等多个行业领域得到应用。传统的基于机器视觉的表面缺陷检测方法通常采用常规图像处理算法或人工设计的特征加分类器。一般而言…...

CentOS 安装MongoDB 6.0

一、安装依赖 yum install libcurl openssl xz-libs 二、下载安装包 安装包下载地址https://www.mongodb.com/try/download/community这里我选择的是 选择RedHat / CentOS 7.0平台的原因是我的操作系统使用的是CentOS 7.0的,需要下载与操作系统匹配的安装包 三、…...

美团面试,被拷打了一小时....

刚从美团走出来,被拷打了一小时…越想越觉得可惜,回想面试经过,好好总结了几个点,发现面试没过的主要原因是在几个关键的问题没有给到面试官想要的答案。从而失去了这次宝贵的机会。 根据你的工作经历,说说你对质量保证…...

017+C语言中函数栈帧的创建与销毁(VS2022环境)

0.前言 您好,这里是limou3434的一篇个人博文,感兴趣的话您也可以看看我的其他文章。本次我将和您一起学习在C语言中函数栈帧的概念。 1.学习函数栈帧的意义 局部变量是怎么穿创建的?为什么局部变量的值是随机的函数是怎么传参的&#xff1…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理&#xff1a…...

leetcodeSQL解题:3564. 季节性销售分析

leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

【SpringBoot自动化部署】

SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...

ThreadLocal 源码

ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物,因为每个访问一个线程局部变量的线程(通过其 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,这些类希望将…...

用 FFmpeg 实现 RTMP 推流直播

RTMP(Real-Time Messaging Protocol) 是直播行业中常用的传输协议。 一般来说,直播服务商会给你: ✅ 一个 RTMP 推流地址(你推视频上去) ✅ 一个 HLS 或 FLV 拉流地址(观众观看用)…...

C#最佳实践:为何优先使用as或is而非强制转换

C#最佳实践:为何优先使用as或is而非强制转换 在 C# 的编程世界里,类型转换是我们经常会遇到的操作。就像在现实生活中,我们可能需要把不同形状的物品重新整理归类一样,在代码里,我们也常常需要将一个数据类型转换为另…...

Linux中INADDR_ANY详解

在Linux网络编程中&#xff0c;INADDR_ANY 是一个特殊的IPv4地址常量&#xff08;定义在 <netinet/in.h> 头文件中&#xff09;&#xff0c;用于表示绑定到所有可用网络接口的地址。它是服务器程序中的常见用法&#xff0c;允许套接字监听所有本地IP地址上的连接请求。 关…...

SpringCloud优势

目录 完善的微服务支持 高可用性和容错性 灵活的配置管理 强大的服务网关 分布式追踪能力 丰富的社区生态 易于与其他技术栈集成 完善的微服务支持 Spring Cloud 提供了一整套工具和组件来支持微服务架构的开发,包括服务注册与发现、负载均衡、断路器、配置管理等功能…...

从0开始学习R语言--Day17--Cox回归

Cox回归 在用医疗数据作分析时&#xff0c;最常见的是去预测某类病的患者的死亡率或预测他们的结局。但是我们得到的病人数据&#xff0c;往往会有很多的协变量&#xff0c;即使我们通过计算来减少指标对结果的影响&#xff0c;我们的数据中依然会有很多的协变量&#xff0c;且…...