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

【八股战神篇】Java虚拟机(JVM)高频面试题

目录

专栏简介

一 请解释Java虚拟机(JVM)及其主要功能

延伸

1. JVM的基本概念

2. JVM的主要功能

二 对象创建的过程了解吗

延伸

1.Java 创建对象的四种常见方式

三 什么是双亲委派模型

延伸

1.双亲委派机制的作用:

2.双亲委派模型的核心思想:

3.双亲委派模型的优势

四 JVM的内存区域

延伸

1.元空间中包含运行时常量池,都放在本地内存中,如何进行回收?

2.什么是直接内存?

3. 直接内存与堆内存的区别

五 垃圾回收算法

延伸

1.垃圾回收算法的标准

2.判断一个垃圾回收算法是否优秀,一般从三个方面进行判断:

3.如何判断是不是垃圾?

六 垃圾回收器

延伸

1.除了CMS和G1外,还有哪些垃圾回收器

七 什么是指令重排序?

延伸

1.指令重排序的原因

2.指令重排序有哪些类型?解释一下过程?

3.如何阻止指令重排序?


专栏简介

八股战神篇专栏是基于各平台共上千篇面经,上万道面试题,进行综合排序提炼出排序前百的高频面试题,并对这些高频八股进行关联分析,将每个高频面试题可能进行延伸的问题进行分析排序选出高频延伸八股题。面试官都是以点破面从一个面试题不断深入,目的是测试你的理解程度。本专栏将解决你的痛点,助你从容面对。本专栏已更新Java基础高频面试题、Java集合高频面试题、MySQL高频面试题、JUC Java并发高频面试题、JVM高频面试题,后续会继续更新Redis、操作系统、计算机网络、设计模式、场景题等,计划在七月前更新完毕(赶在大家高频面试前)点此链接订阅专栏“八股战神篇”。

一 请解释Java虚拟机(JVM)及其主要功能

Java虚拟机(JVM)是Java平台的核心组成部分,它是一个能够执行Java字节码的虚拟计算机。JVM的主要功能是提供一个平台无关的执行环境,使得Java程序可以在任何支持JVM的系统上运行。JVM通过将字节码转化为平台特定的机器码并执行,从而实现了Java的跨平台特性。

JVM的主要功能包括:

  • 加载和验证类:JVM通过类加载器加载Java类文件,并验证字节码的正确性,确保代码安全性。

  • 执行字节码:JVM通过解释器或即时编译器(JIT)将字节码转换为机器码并执行。

  • 内存管理与垃圾回收:JVM自动管理内存分配和回收,避免开发者手动处理内存。

  • 提供运行时环境:JVM为Java程序提供必要的运行时支持,包括堆、栈、方法区等内存区域,支持多线程、异常处理等特性。

延伸

1. JVM的基本概念

JVM(Java Virtual Machine)是一种虚拟化的计算机,它并不直接运行在硬件上,而是运行在操作系统上。JVM的设计允许Java程序跨平台运行,其背后的关键在于字节码。Java程序在编译后生成字节码文件(.class文件),这些字节码文件并不依赖于具体平台的硬件架构,而是JVM负责将字节码转换为特定平台的机器码并执行。

  • 字节码:Java程序编写后,由Java编译器(javac)生成字节码,而不是机器码。字节码是一种中间代码,不依赖于操作系统或硬件。

  • 跨平台性:不同操作系统或硬件架构下,JVM的实现是不同的,但它们都能解析相同的字节码文件,因此Java程序可以在不同平台上无缝运行。

2. JVM的主要功能

(1) 加载和验证类

JVM需要从文件系统或其他来源加载Java类文件。类加载的过程是由类加载器(ClassLoader)来完成的,类加载器负责读取.class字节码文件并将它们加载到JVM的内存中。

  • 类加载器:JVM中的类加载器分为不同类型,如引导类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)。它们负责加载Java标准库类、扩展库类以及用户定义的类。

  • 类的验证:加载的类需要通过字节码验证器检查,确保没有安全漏洞。例如,验证类的结构是否符合JVM规范,检查是否有非法的内存访问或不合规的操作。

(2) 执行字节码

一旦类加载完成并验证无误,JVM就开始执行字节码。执行过程分为两种方式:

  • 解释执行:JVM的解释器逐行读取字节码并执行。这种方式速度较慢,但可以立即执行字节码。

  • JIT编译:JVM通过即时编译器(JIT)将频繁使用的字节码片段编译成本地机器码,并缓存,以便后续直接执行。JIT编译极大地提升了程序的执行效率,尤其在长时间运行的应用程序中。

JIT编译例子: 当JVM执行到某段代码时,如果这段代码经常被调用,JIT编译器会将其编译成机器码,这样下次再执行时,就不需要解释执行,而是直接使用机器码执行,从而提高了性能。

(3) 内存管理与垃圾回收

JVM负责自动管理内存的分配与回收。内存管理的关键部分是垃圾回收(Garbage Collection,GC),它通过回收不再使用的对象来释放内存,避免了内存泄漏的问题。

  • 堆内存(Heap):所有对象实例都存放在堆中。JVM的垃圾回收器主要在堆中进行内存回收。

  • 栈内存(Stack):每个线程都有自己的栈,存储局部变量、操作数栈和方法调用的信息。栈内存的管理是由JVM自动处理的。

  • 垃圾回收器(GC):JVM有不同的垃圾回收算法,如串行GC、并行GC、G1GC等,目的是自动回收不再使用的对象,优化内存使用。

GC的工作过程通常分为几个步骤:

1. 标记阶段:标记所有可达的对象。

  • 标记阶段:标记所有可达的对象。

  • 清除阶段:清除不可达的对象,回收其占用的内存。

  • 压缩阶段:清理后的堆可能产生内存碎片,JVM会尝试压缩堆内存,减少碎片化。

(4) 提供运行时环境

JVM不仅负责执行字节码,还提供了一个运行时环境,支持多个关键功能:

  • 线程管理:JVM支持多线程执行,提供线程调度、同步等机制。

  • 异常处理:JVM负责抛出和捕获异常,确保程序在出现错误时能够处理异常,避免程序崩溃。

  • 方法调用:JVM管理方法的调用过程,包括栈帧的创建、方法参数的传递和返回值的处理。

JVM通过这些功能支持了Java程序的稳定性和高效执行。

二 对象创建的过程了解吗

对象的创建过程是 Java 虚拟机(JVM)中一个非常重要的环节,它主要分为五步。

第一步是进行类加载检查,当程序执行到 new 指令时,JVM 会先检查对应的类是否已经被加载、解析和初始化过。如果类尚未加载,JVM 会按照类加载机制(加载、验证、准备、解析、初始化)完成类的加载过程。这一步确保了类的元信息(如字段、方法等)已经准备好,为后续的对象创建奠定基础。

第二步是进行内存的分配,JVM 会为新对象分配内存空间。对象所需的内存大小在类加载完成后就可以确定,因此分配内存的过程就是从堆中划分一块连续的空间,主要有两种方式:

一种是通过指针碰撞,如果堆中的内存是规整的(已使用和空闲区域之间有明确分界),JVM 可以通过移动指针来分配内存。另一种是通过空闲列表,如果堆中的内存是碎片化的,JVM 会维护一个空闲列表,记录可用的内存块,并从中分配合适的区域。

此外,为了保证多线程环境下的安全性,JVM 还会采用两种策略避免内存分配冲突,一种是通过 CAS 操作尝试更新分配指针,如果失败则重试;另一种是每个线程在堆中预先分配一小块专属区域,避免线程间的竞争。

第三步是将零值初始化,JVM 会对分配的内存空间进行初始化,将其所有字段设置为零值(如 int 为 0,boolean 为 false,引用类型为 null)。这一步确保了对象的实例字段在未显式赋值前有一个默认值,从而避免未初始化的变量被访问。

第四步是设置对象头,其中包含Mark Word、Klass Pointer和数组长度。Mark Word 用于存储对象的哈希码、GC 分代年龄、锁状态标志等信息。Klass Pointer 指向对象所属类的元数据(即 Person.class 的地址)。

第五步是执行构造方法,用<init> 方法完成对象的初始化。构造方法会根据代码逻辑对对象的字段进行赋值,并调用父类的构造方法完成继承链的初始化。这一步完成后,对象才真正可用。

延伸

1.Java 创建对象的四种常见方式

(1)new 关键字(最常见)

使用 new 关键字可以直接创建对象,并调用 无参或有参构造方法 进行初始化。

示例:

Person person1 = new Person();
Person person2 = new Person("小粥", 18); 

适用于大部分场景,代码直观,易于理解。

(2)反射(Class 和 Constructor 两种方式)

反射机制可以在运行时动态创建对象,主要通过 Class.newInstance() 和 Constructor.newInstance() 两种方式实现。

img

Class.newInstance()

通过 Class 对象的 newInstance() 方法创建实例,只能调用无参构造方法。

Person person = Person.class.newInstance();
System.out.println(person);

限制:必须有无参构造方法,否则会抛出异常。

Constructor 类提供更灵活的创建方式,可以调用 任意构造方法(包括私有构造方法)。

Constructor<Person> constructor = Person.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // 允许访问私有构造方法
Person person = constructor.newInstance("小粥", 18);
System.out.println(person);

对比:

Class.newInstance() 只能调用无参构造方法,Constructor.newInstance() 可调用任意构造方法。

Constructor.newInstance() 可以通过 setAccessible(true) 访问私有构造方法。

(3)clone() 方法(对象克隆)

clone() 方法用于创建一个相同内容的新对象,不会调用构造方法。需要实现 Cloneable 接口,并重写 clone() 方法。

public class Person implements Cloneable {@Overridepublic Person clone() throws CloneNotSupportedException {return (Person) super.clone();}
}
Person person1 = new Person("小粥", 18);
Person person2 = person1.clone();
System.out.println(person1 == person2); // false

注意:clone() 只进行 浅拷贝,对象内部的引用类型变量仍然指向同一块内存。如果要实现 深拷贝,需要让所有引用类型的成员变量也实现 Cloneable 接口,并重写 clone() 方法。

(4)反序列化(Serializable)

反序列化可以将存储或传输的对象数据恢复成 Java 对象,不会调用构造方法。

Person person1 = new Person("小粥", 18);
byte[] bytes = SerializationUtils.serialize(person1);
Person person2 = (Person) SerializationUtils.deserialize(bytes);
System.out.println(person1 == person2); // false

特点:对象必须实现 Serializable 接口,否则无法序列化;反序列化创建的对象是全新的,与原对象无关;性能开销较大,适用于数据存储或网络传输,而不是频繁对象创建。

三 什么是双亲委派模型

双亲委派模型是 Java 类加载机制中的核心概念,它定义了类加载器之间的层次关系和加载规则。通过这种模型,Java 能够保证类的唯一性和安全性,同时避免重复加载类的问题。接下来我会详细讲述双亲委派模型的定义、层次结构和工作流程。

首先说一下什么是双亲委派模型,它其实是一种类加载机制,其规定了当一个类加载器收到类加载请求时,不会立即尝试自己去加载这个类,而是先将请求委托给父类加载器完成。只有当父类加载器无法加载该类(例如在父类的搜索范围内找不到对应的类)时,子类加载器才会尝试自己加载。这种机制确保了类的加载过程具有层次性,并且优先使用高层级的类加载器来加载核心类库。

接下来说一下类加载器的层次结构,主要分为四层,

第一层是启动类加载器(Bootstrap ClassLoader),它负责加载 JVM 核心类库(如 rt.jar 中的类),位于最顶层,通常由本地代码实现。

第二层是扩展类加载器(Extension ClassLoader),它负责加载 $JAVA_HOME/lib/ext 目录下的扩展类库。

第三层是应用程序类加载器(Application ClassLoader),它负责加载用户类路径(ClassPath)上的类,也称为系统类加载器。

第四层是自定义类加载器,开发者可以通过继承 ClassLoader 类实现自己的类加载器,用于加载特定需求的类。

这些类加载器之间形成了一个树状的层次结构,每个类加载器都有一个父加载器。

最后说一下双亲委派模型的工作流程,主要分为四步,

第一步是检查缓存,当前类加载器会先检查是否已经加载过目标类,如果已加载,则直接返回对应的 Class 对象。

第二步是委派父加载器,如果没有加载过,当前类加载器会将加载请求委派给父加载器处理。

第三步是递归向上,父加载器继续将请求委派给它的父加载器,直到到达 Bootstrap ClassLoader。

第四步是尝试加载,如果父加载器无法加载目标类,则子加载器会尝试自己加载。

延伸

1.双亲委派机制的作用:

1,保证类加载的安全性,通过双亲委派机制让顶层的类加载器去加载核心类,避免恶意代码去替换jdk中的核心类库。确保核心类库的安全性和完整性。

2,避免同一个类被多次加载,上层的类加载器如果加载过类,直接返回给类避免重复加载。

2.双亲委派模型的核心思想:
  • 每个类加载器都拥有一个父加载器,在加载类时,都会先将加载请求委派给父加载器。

  • 通过这种方式,Java 确保了核心类库不会被覆盖或篡改。比如 java.lang.String 类总是由 Bootstrap ClassLoader 加载,不可能被应用程序的类加载器所替代。

3.双亲委派模型的优势

类的唯一性:通过双亲委派模型,同一个类只会被一个类加载器加载一次,从而避免了重复加载的问题。

安全性:核心类库(如 java.lang.String)由 Bootstrap ClassLoader 加载,防止用户自定义的恶意类冒充核心类库。

模块化管理:不同层级的类加载器负责加载不同范围的类,便于实现模块化和隔离性。

四 JVM的内存区域

JVM 的内存区域可以分为线程共享和线程私有两部分,每个部分都有明确的职责和作用,保障 Java 程序的高效运行。JDK1.7 和 1.8 时内存结构略有不同,接下来我先讲解一下 JDK1.7 时 JVM 的内存结构,然后再说一下 JDK1.8 时发生了哪些变动。

首先是线程共享的部分,一共有两个,

一个是堆(Heap),所有对象实例和数组都在这里分配内存,垃圾回收器(GC)会管理其中的对象回收。堆中还包含了字符串常量池(String Constant Pool),用于存储字符串字面量和常量。

另一个是方法区(Method Area):用于存储类元信息、常量、方法字节码等。其中运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译期生成的各种字面量和符号引用。

然后是线程私有的部分,一共有三个,

第一个是虚拟机栈(VM Stack),每个线程启动时都会创建一个虚拟机栈,它存储方法调用过程中产生的栈帧,包括局部变量、操作数栈、方法返回地址等,每个方法调用都会创建一个新的栈帧,方法执行结束后栈帧出栈。

第二个是本地方法栈(Native Method Stack),专门用于存储本地方法(Native Method)的调用信息,与虚拟机栈类似,但用于 JNI(Java Native Interface)调用。

第三个是程序计数器(Program Counter Register),记录当前线程正在执行的字节码指令地址。它是 JVM 运行时最小的内存区域,每个线程都有一个独立的程序计数器。

最后是本地内存

里面包含直接内存(Direct Memory),由 NIO(New Input/Output)直接分配,不受 JVM 堆的管理,通常用于高性能数据传输,如缓冲区(Buffer)。这个内存结构保证了 JVM 在执行 Java 代码时能够高效管理对象、执行方法调用,并支持多线程并发。

JDK 1.8 时 JVM 的内存结构主要有两点不同,

一个是方法区(Method Area)在 JDK 1.8 被替换为元空间(Metaspace),且元空间使用本地内存。

另一个是运行时常量池(Runtime Constant Pool)在 JDK 1.7 属于方法区的一部分,而在 JDK 1.8 变成元空间的一部分。

延伸

1.元空间中包含运行时常量池,都放在本地内存中,如何进行回收?

运行时常量池与元空间:运行时常量池是JVM用于存储编译期生成的常量(如字符串字面量、方法和字段的符号引用等)的地方。在Java 8之前,常量池是存放在永久代(PermGen)中的,而Java 8之后,永久代被移除,运行时常量池被放到了元空间(Metaspace)中。

元空间的内存管理:元空间使用的是本地内存(Native Memory),而不是堆内存。它的内存分配和回收由操作系统管理,而非JVM的垃圾回收器(GC)。元空间中的内存一般是较难回收的,因为类的元数据信息和运行时常量池数据一旦加载,很少会被卸载,除非对应的类被卸载。

回收问题:虽然元空间的内存管理依赖操作系统,但在某些情况下,内存可能会出现膨胀,导致应用程序占用过多的内存。如果元空间持续增长且没有及时进行类卸载操作,那么内存就会持续被占用。JVM提供了一些选项(如-XX:MaxMetaspaceSize)来限制元空间的大小,以防止内存过度使用。

2.什么是直接内存?

直接内存(Direct Memory)是指不属于JVM堆内存管理的一部分,而是通过Java的nio(Non-blocking I/O)库来直接操作的内存区域。直接内存由操作系统直接管理,它允许Java程序绕过JVM堆内存进行数据的读写操作,从而提高性能,特别是在需要频繁进行I/O操作的场景下。直接内存的分配通常使用DirectByteBuffer来实现,且不经过垃圾回收(GC)机制的管理。

3. 直接内存与堆内存的区别
  • 堆内存:是JVM管理的内存区域,所有的Java对象(如new创建的对象)都存储在堆内存中。堆内存受JVM垃圾回收器(GC)管理,会自动清理不再使用的对象。

  • 直接内存:由操作系统直接管理,JVM不直接控制。它通常用于高效的数据读写(如NIO网络编程、文件操作等)。直接内存不经过GC处理,因此可以避免GC的干扰,减少内存管理的开销。

五 垃圾回收算法

垃圾回收(Garbage Collection,简称 GC)是 Java 虚拟机(JVM)中自动管理内存的重要机制,它通过一系列算法来识别和回收不再使用的对象,从而释放堆内存。接下来我会详细讲述常见的四种垃圾回收算法及其工作原理。

第一个是标记-清除算法(Mark-Sweep),它是最基础的垃圾回收算法,主要分为两个阶段,一个是标记阶段,从根对象(GC Roots)开始,递归遍历所有可达对象,并标记为“存活”; 另一个是清除阶段,遍历整个堆内存,回收未被标记的对象所占用的空间。

此算法主要存在两个问题,一个是内存碎片化,回收后的内存可能会产生大量不连续的碎片,导致大对象无法分配内存;另一个是效率较低,需要两次遍历堆内存,耗时较长。

第二个是复制算法(Copying),它通过将内存划分为两块(From 和 To),每次只使用其中一块,解决了标记-清除算法的内存碎片化问题,主要分为两个阶段,一个是复制阶段,当一块内存用完时,将存活的对象复制到另一块内存中,并按顺序排列;另一个是清理阶段,直接清空原来的内存块,无需额外的标记或清除操作。

此算法的优点是效率高且不会产生内存碎片,但缺点是需要双倍的内存空间。

第三个是标记-整理算法(Mark-Compact),它是对标记-清除算法的改进,它在标记阶段完成后,会将所有存活对象向一端移动,从而避免内存碎片化。主要分为两个阶段,一个是标记阶段,与标记-清除算法相同,标记所有存活对象;另一个是整理阶段,将存活对象移动到内存的一端,清理边界外的内存。

此算法适合老年代(Old Generation),因为老年代中的对象存活率较高,复制成本较大。

第四个是分代收集算法(Generational Collection),它是目前主流 JVM 的垃圾回收策略,它基于对象的生命周期将堆内存划分为新生代(Young Generation)和老年代(Old Generation)。

对于新生代,大多数对象朝生夕灭,采用复制算法进行垃圾回收。新生代进一步划分为 Eden 区和两个 Survivor 区(From 和 To);对于老年代,存活时间较长的对象存储在此,采用标记-清除或标记-整理算法进行垃圾回收。

这种算法结合了不同算法的优点,针对不同代的特点选择合适的回收策略,从而提升整体性能。

延伸

1.垃圾回收算法的标准

java垃圾回收的过程都会由单独的GC线程完成,不管哪一个回收算法,都会有部分阶段暂停所有的用户进程。这个过程称为Stop The World 简称为STW,这个时间进行回收垃圾,如果STW的时间过程就会影响用户的使用。

2.判断一个垃圾回收算法是否优秀,一般从三个方面进行判断:

吞吐量

吞吐量指的是CPU用于执行用户代码的时间 / CPU总执行时间。即 吞吐量 = 执行用户代码时间 / (执行用户时间 + GC时间)。吞吐量越大执行效率越高。

最大暂停时间

最大暂停时间指的是在所有垃圾回收过程中的STW时间的最大值。STW时间越大用户使用系统的影响就越大。

堆使用效率

不同的垃圾回收算法,对堆内存的使用方式是不同的。比如标记清除算法可以使用完整的堆内存,而复制算法会将堆内存一分为二,每次只能使用一半的内存。从堆使用的效率来看,标记清除算法要由于复制算法。

上面的三种评价标准:吞吐量、最大暂停时间、堆使用效率,不可兼得。一般来说,堆内存越大,最大暂停时间越长,想要减少暂停时间就必须降低吞吐量。不同的垃圾回收算法使用与不同的场景。

3.如何判断是不是垃圾?

在 Java 中,垃圾回收(GC)主要通过 ** 和 GC Root Tracing(可达性分析)** 来判断对象是否可回收。

引用计数法:每个对象维护一个 引用计数器,当有新的引用指向该对象时,计数器加一;当引用失效时,计数器减一。如果计数器变为 0,表示该对象不再被使用,可以被回收。

但是这样会出现一个问题,当栈中没有引用指向堆中的内存,而堆中的对象互相引用。这样造成堆中的对象无法被回收。

这就是循环引用。

例如:AB实例对象在栈上已经没有变量引用了,由于计数器还是1无法回收,出现了内存泄漏

可达性分析法:JVM 采用 可达性分析作为主要的垃圾判断算法,通过一组称为 GC Roots 的对象作为起点,查找所有可达对象。如果某个对象无法从 GC Roots 访问,则认为它是垃圾,需回收。接下来会详细讲述GC Roots、对象的引用类型和生存状态。

第一,GC Roots 主要包括:

JVM 栈中的引用(方法的局部变量、参数等)。

静态变量引用(存储在方法区的类变量)。

运行时常量池中的引用(如字符串常量 String)。

JNI 本地方法引用(即 Native 方法中的引用)。

第二,对象的引用类型(可达性判断)

根据引用强度,Java 将对象引用分为以下四种类型:

强引用(StrongReference):使用 new 关键字创建的对象,不会被回收。

软引用(SoftReference):在内存不足时会被回收,适用于 缓存。

弱引用(WeakReference):不论内存是否足够,只要发生 GC,弱引用的对象都会被回收。

虚引用(PhantomReference):无法通过虚引用访问对象,主要用于 跟踪对象的回收状态。

第三,对象的生存状态

可达:对象能被 GC Roots 访问,说明仍然存活。

可回收(第一次标记):对象已经不可达,但 可能复活(如 finalize() 方法中重新引用 GC Roots)。

不可复活(第二次标记):若对象在 finalize() 之后仍然不可达,则被认定为垃圾,等待回收。

六 垃圾回收器

Java 的垃圾回收机制通过标记垃圾对象和回收无用内存,提升内存利用率,降低程序停顿时间。垃圾回收器是具体实现算法的工具,最常用的两种收集器是 CMS 和 G1 ,分别适用于不同的场景,接下来我会分别进行讲述。

第一个是 CMS 收集器,CMS(Concurrent Mark Sweep)是以最小化停顿时间为目标的垃圾收集器,适用于需要高响应的应用场景(如 Web 应用)。其基于“标记-清除算法”,回收流程包括以下阶段:

首先停止所有用户线程,启用一个GC线程进行初始标记(Stop The World)标记 GC Roots 能直接引用的对象,停顿时间短。

其次由用户线程和 GC 线程并发执行,进行并发标记用户线程和 GC 线程并发执行,完成从 GC Roots 开始的对象引用分析。

然后,启动多个GC 线程进行重新标记(Stop The World),修正并发标记期间用户线程对对象引用的变动,停顿时间稍长但可控。

最后,启动多个用户线程和一个GC 线程,进行并发清除清理不可达对象,清理完成后把GC线程进行重置。

CMS 的优点是以响应时间优先,停顿时间短,但也有两个缺点,一个是由于CMS采用“标记-清除”,会导致内存碎片积累,另一个是由于在并发清理过程中仍有用户线程运行,可能生成新的垃圾对象,需在下次 GC 处理。

第二个是 G1 收集器,G1(Garbage-First)收集器以控制 GC 停顿时间为目标,兼具高吞吐量和低延迟性能,适用于大内存、多核环境。其基于“标记-整理”和“标记-复制算法”,回收流程包括以下阶段:

首先,停止所有用户线程,启用一个GC线程进行初始标记(Stop The World)标记从 GC Roots 可达的对象,时间短。

其次,让用户线程和一个GC 线程并发工作,用GC 线程进行并发标记分析整个堆中对象的存活情况。

然后,停止所有用户线程,让多个GC 线程进行最终标记(Stop The World),修正并发标记阶段产生的引用变动,识别即将被回收的对象。

最后,让多个GC 线程进行筛选回收根据收集时间预算,优先回收回收价值最高的 Region。回收完成后把GC线程进行重置。这是 G1 的核心优化,基于堆分区,将回收工作集中于垃圾最多的区域,避免全堆扫描。

G1 具有三个优点,

其一,将堆内存划分为多个 Region,可分别执行标记、回收,提升效率。

第二,采用“标记-整理”和“标记-复制”,实现内存紧凑化。

第三,方便控制停顿时间,通过后台维护的优先队列,动态选择高价值 Region,极大减少了全堆停顿的频率。

但G1缺点是:调优复杂,对硬件资源要求较高。

延伸

1.除了CMS和G1外,还有哪些垃圾回收器

除了 CMS(并发标记清除)和 G1(Garbage First)之外,JVM 还提供了其他几种垃圾回收器,主要包括 Serial、Parallel、ZGC等。

(1)Serial 垃圾回收器

特点:单线程回收,适用于 单核 CPU 以及 小型 Java 应用。在 GC 过程中会 Stop-The-World(STW),即暂停所有应用线程,影响响应时间。

适用场景:适用于单 CPU 机器,堆内存较小(如 100MB~2GB)。主要用于桌面应用或测试环境。

优缺点: 实现简单,额外开销小;单线程垃圾回收,STW 时间较长,不适用于高并发环境。

使用参数:-XX:+UseSerialGC

(2)Parallel 垃圾回收器(吞吐量优先)

特点:多线程并行进行垃圾回收,提高 GC 效率。吞吐量优先:适用于批量任务处理,GC 期间仍然会 STW。适用于 多核 CPU 环境,可以最大化 CPU 资源利用率。

适用场景:高吞吐量需求的应用(如大数据计算、离线任务)。适用于 堆内存中等(几 GB 级别)的服务器端应用。

优缺点:多线程回收,吞吐量高,适合 批处理任务。GC 期间仍然会导致 较长的暂停时间,不适用于 低延迟应用。

使用参数:-XX:+UseParallelGC

(3)ZGC(低延迟垃圾回收器)

特点:目标是超低延迟,最大 GC 停顿时间 <1ms。支持超大堆内存(最大 16TB),适用于大规模服务。并发执行大部分 GC 任务,减少 STW 时间。

适用场景:适用于低延迟应用(如金融、游戏、交易系统)。大堆内存(数百 GB 级别) 的应用。

优缺点:GC 过程 几乎无感知,适用于 高响应要求场景。相比 G1,CPU 开销稍高,仍在优化中。

使用参数:-XX:+UseZGC

七 什么是指令重排序?

指令重排序(Instruction Reordering)是指在程序执行过程中,处理器根据一定的优化规则,改变指令的执行顺序,但不改变指令的执行结果。指令重排序通常发生在编译器、处理器或 JVM 的优化过程中,它的目的是提高程序执行效率,如减少 CPU 等资源的等待时间。

在 Java 的并发编程中,指令重排序是一个重要的概念,因为它可能导致多线程环境下的程序行为不符合预期,进而引发 竞态条件数据不一致 等问题。

延伸

1.指令重排序的原因
  • 编译优化:为了提高程序的性能,编译器可以对指令顺序进行优化。例如,编译器可能会将某些不相干的指令交换位置,以提高 CPU 的流水线效率。

  • 硬件优化:现代 CPU 具有指令流水线(Pipeline)和乱序执行(Out-of-Order Execution)等优化特性,这使得它们在执行指令时可能会改变指令的顺序,尽管逻辑顺序未被改变。

  • JVM 优化:在 Java 中,JVM 在执行字节码时,也可能进行一些优化,比如将某些指令重新排序,以提高执行效率。

2.指令重排序有哪些类型?解释一下过程?

指令重排序主要有两种类型:编译器重排序和处理器重排序。

  1. 编译器重排序:编译器在生成机器代码时可能会对指令进行重排序,目的是为了优化代码的执行效率。它主要发生在程序的编译阶段,在不改变程序最终行为的前提下,调整指令的顺序。

  2. 处理器重排序:处理器会在执行过程中对指令进行乱序执行,以提高CPU的执行效率。这通常发生在硬件层面,尤其是为了利用指令流水线和并行处理能力。

3.如何阻止指令重排序?

阻止指令重排序的方法主要有两种:使用 volatile 关键字和使用 synchronized 关键字。

  1. 使用 volatile 关键字:声明一个变量为volatile,可以防止该变量的值在不同线程之间发生重排序。volatile确保对该变量的写操作对所有线程都是可见的,并且禁止对该变量的重排序。

  2. 使用 synchronized 关键字:通过 synchronized 来同步代码块或方法,保证在一个线程中执行完同步代码后,其他线程才能执行相同的同步代码,从而防止指令重排序。

相关文章:

【八股战神篇】Java虚拟机(JVM)高频面试题

目录 专栏简介 一 请解释Java虚拟机&#xff08;JVM&#xff09;及其主要功能 延伸 1. JVM的基本概念 2. JVM的主要功能 二 对象创建的过程了解吗 延伸 1.Java 创建对象的四种常见方式 三 什么是双亲委派模型 延伸 1.双亲委派机制的作用&#xff1a; 2.双亲委派模型…...

微店商品详情接口开发指南

接口概述 微店商品详情接口&#xff08;/api/v1/product/detail&#xff09;用于获取商品的完整信息&#xff0c;包括标题、价格、库存、SKU、主图等数据&#xff0c;支持OAuth2.0鉴权。 点击获取key和secret 请求方式 GET https://open.weidian.com/api/v1/product/detail …...

拦截指定注解(FeignClient),补偿重试

拦截指定注解&#xff08;FeignClient&#xff09;&#xff0c;补偿重试&#xff1b;对代码无入侵 避免正常调用和重试逻辑调用重复插入&#xff1b; 根据自己的业务需求 插入新数据时 是否需要删除之前的旧数据&#xff0c;防止数据覆盖 import cn.hutool.core.util.ObjectUti…...

使用 GitHub Pages 部署单页面应用教程

## 简介 GitHub Pages 是 GitHub 提供的一个静态网站托管服务&#xff0c;可以免费托管个人、项目或组织页面。本教程将指导您如何部署一个单页面应用到 GitHub Pages。 ## 前提条件 - 拥有 GitHub 账号 - 已安装 Git - 已安装 Node.js&#xff08;如果使用前端框架&#x…...

day16-17-磁盘管理

1. 磁盘分类 磁盘接口 硬盘 大小 sata接口 机械硬盘、固态硬盘 机械&#xff1a;4tb 10k性能要求不高 sas接口 机械硬盘、固态硬盘 机械&#xff1a;900G 15k性能好&#xff0c;容量低 pcie-e接口 固态硬盘 tb级别 4tb 8tb 性能要求高&#xff0c;数据库&#xff0c…...

【神经网络与深度学习】扩散模型之通俗易懂的解释

引言&#xff1a; 扩散模型&#xff08;Diffusion Models&#xff09;是近年来深度学习领域的一项重要突破&#xff0c;尤其在生成式人工智能&#xff08;Generative AI&#xff09;中展现了惊人的能力。它的核心思想类似于一个孩子学习搭建乐高城堡的过程——先拆散&#xff0…...

Linux Bash 中 $? 的详细用法

Bash (Bourne Again SHell) 是使用最广泛的 SHell 脚本语言之一&#xff0c;因为它与 Unix 和 Linux 系统兼容。它提供了许多内置函数和变量&#xff0c;使脚本编写更高效&#xff0c;更不容易出错。其中一个变量是 $?&#xff0c; 它是 Bash 脚本错误处理的一个组成部分。这个…...

嵌入式培训之系统编程(一)标准IO、文件操作

目录 一、系统编程概述 二、标准IO &#xff08;一&#xff09;&#xff08;以计算机为中心&#xff09;标准IO &#xff08;二&#xff09;io的分类 &#xff08;三&#xff09;man命令 三、文件读写操作 &#xff08;一&#xff09;文件操作步骤 &#xff08;二&#…...

NVIDIA Earth-2 AI 天气模型 DLI 课程:解锁全球风云的未来之匙

电影闲聊引发思索之言&#xff1a; 曾几何时&#xff0c;当我们闲聊起那些描绘美国气候的大电影时&#xff08;龙卷风-后天等美国大片&#xff09;&#xff0c;仿佛被带入了一个个奇幻而真实的气象世界。从狂风暴雨到烈日炎炎最后到冰天雪地&#xff0c;电影里的场景让我们对气…...

至此(day1-day4)代码详解(ai辅助整理)

至此&#xff08;day1-day4&#xff09;代码详解 ipl10.nas ; 第一阶段引导程序 ; 功能&#xff1a;读取磁盘数据并跳转到第二阶段加载程序 ; 编译参数&#xff1a;nask -o ipl10.bin ipl10.nasCYLS EQU 10 ; 预设读取柱面数&#xff08;实际值由BIOS决定&#xff09;ORG…...

STM32F103_LL库+寄存器学习笔记12.2 - 串口DMA高效收发实战2:进一步提高串口接收的效率

导言 通过优化代码算法&#xff0c;在串口空闲中断回调里不需要暂时关闭DMA接收&#xff0c;达到提高串口接收的效率。在IDLE接收中断里关闭DMA接收会导致接收过程中有数据丢失风险&#xff08;关DMA的瞬间如果有数据到来&#xff0c;会丢帧&#xff01;&#xff09;。 回顾一…...

conda 设置env后,环境还是安装在c盘的解决方式:

1|设置 envs 文件夹权限 右键【envs】文件夹&#xff0c;选择【属性】 选择【安全】&#xff0c;点击【编辑】 选中【Users(用户名\Users)】&#xff0c;选中运行所有权限&#xff0c;如图所示 点击【确认】&#xff0c;确保修改被保存 2、环境变量path设置 选择【高级系统设置…...

设计模式 - 工厂模式

简单工厂模式 public class CoffeeFactory {public Coffee get(string coffeeType) {Coffee coffee null;if ("American".equals(coffeeType)) {coffee new AmericanCoffee();} else if ("Latte".equals(coffeeType)) {coffee new LatteCoffee();}retur…...

动态规划-LCR 090.打家劫舍II-力扣(LeetCode)

一、题目解析 本题与打家劫舍的最大区别在于房子不是线性分布的了&#xff0c;而是首尾相连的环形分布&#xff0c;即如果偷了第一间房子&#xff0c;那么最后一间房子就不能偷了&#xff0c;因为它们是相连的。 二、算法原理 在分析之前我们可以先讨论上面提到的第一间房子偷…...

2025 年暑假 LBE 大空间市场火爆程度预测:技术驱动与消费升级下的增长引擎

一、市场爆发的底层逻辑 根据 DeepSeek 行业报告显示&#xff0c;2025 年 LBE 大空间市场将呈现结构性爆发&#xff0c;核心驱动力来自三大技术突破&#xff1a; 空间计算能力跃迁&#xff1a;上海移动已开通全球最大规模商用 5G-A 3CC 网络&#xff0c;主城区及十大重点场景…...

【AI 大模型】盘古大模型简介 ( 创建空间 | 体验模型 | 部署模型 )

文章目录 一、盘古大模型简介1、创建空间2、体验模型3、部署模型 总结 : 盘古大模型 是 开发部署 盘古基础模型 , 或 在 盘古模型 基础上进行 微调训练 的 大模型 的平台 , 是 开发训练 大模型的平台 ; 不适合 中小企业 和 个人开发者 开发 大模型应用 ; 一、盘古大模型简介 1、…...

2025年护网行动蓝队防御全解析:构建智能动态防御体系

2025年&#xff0c;随着网络攻击手段的智能化、混合化升级&#xff0c;护网行动中的蓝队防御已从传统的被动防护转向“动态感知、智能研判、主动反制”的立体化模式。如何在攻防不对称的对抗中实现“看得见、防得住、溯得清”&#xff1f;本文将结合前沿技术与实战经验&#xf…...

【Java高阶面经:微服务篇】3.熔断机制深度优化:从抖动治理到微服务高可用架构实战

一、熔断抖动的本质剖析与核心成因 1.1 熔断机制的核心价值与抖动危害 熔断机制作为微服务弹性架构的核心组件,通过模拟电路断路器逻辑,在服务出现异常时自动阻断请求链,防止故障扩散引发雪崩。但频繁的“熔断-恢复-熔断”抖动会导致: 用户体验恶化:请求成功率波动大,响…...

HTML回顾

html全称:HyperText Markup Language(超文本标记语言) 注重标签语义,而不是默认效果 规则 块级元素包括: marquee、div等 行内元素包括: span、input等 规则1:块级元素中能写:行内元素、块级元素(几乎什么都能写) 规则2:行级元素中能写:行内元素,但不能写:块…...

Leetcode百题斩-字典树

208. Implement Trie (Prefix Tree)[medium] 做完了哈希&#xff0c;来看看数据结构&#xff0c;做做字典树。字典树在搜索方面的作用还是蛮大的&#xff0c;主要是能实现前缀联想以及正确性匹配相关的功能。 字典树又名前缀树&#xff0c;顾名思义就是维护字符串的前缀。这个…...

大数据Spark(五十九):Standalone集群部署

文章目录 Standalone集群部署 一、节点划分 二、搭建Standalone集群 1、将下载好的Spark安装包上传解压 2、配饰spark-env.sh 3、配置workers 4、将配置好的安装包发送到node2、node3节点上 5、启动Standalone集群 三、提交任务测试 Standalone集群部署 Standalone 模…...

Vue 3 ~ 3.5 版本useTemplateRef使用

注意&#xff0c;useTemplateRef版本要在 3.5 以后才可使用&#xff0c;版本低的 ref 替代问题也不大&#xff5e; 2024 年 9 月 1 日发布的 组合式 API&#xff1a;辅助 | Vue.js&#xff0c;引入一个小小的新 API useTemplateRef()&#xff0c;它用于访问实际的 DOM 节点。 …...

使用F5-tts复刻音色

最近第一人称视角的视频很火&#xff0c;想试试看复刻一下电视剧中某个角色的音色。看了下字节的API&#xff0c;嗯。。。138元一个音色&#xff0c;还不包括合成语音的费用&#xff0c;算了还是看看开源项目吧。 随便搜了搜&#xff0c;发现了两个项目一个是openvoice&#x…...

使用亮数据代理IP+Python爬虫批量爬取招聘信息训练面试类AI智能体(附完整源码)

文章目录 一、为什么要用代理IP&#xff1f;(重要&#xff01;)二、环境准备&#xff08;5分钟搞定&#xff09;三、爬虫核心代码解析&#xff08;含反反爬技巧&#xff09;四、数据清洗的3个关键步骤五、训练AI智能体的实战技巧六、法律风险防范&#xff08;必须看&#xff01…...

[软件工程]第二章题目汇总

1 [单选题] 原型化模型是&#xff08; &#xff09;。 A、适用于客户需求被明确定义的情况 B、很难产生有意义产品的一种冒险模型 C、提供一个精确表述的形式化规格说明 D、适用于客户需求难以清楚定义的情况 2 [单选题] 下列关于增量模型的说法正确的是&#xff08; &…...

Java EE进阶1:导读

1.发展历程 2.学习内容 前⾯的课程中,学习的是Java基础,JavaEE主要学习Java的应用,也就是学习Java在企业中是如何应用的 Java更多场景是业务开发,更狭义点可以理解为web开发.所以咱们的学习也是围绕着如何使用Java来做web开发 2.1 什么是Web开发&#xff1f; web&#xff08…...

Unity自定义shader打包SpriteAtlas图集问题

Unity打包图集还是有一些坑的&#xff0c;至于图集SpriteAtlas是什么请参考我之前写的文章&#xff1a;【Sprite Atlas】Unity新图集系统SpriteAtlas超详细使用教程_spriteatlas 使用-CSDN博客 问题&#xff1a; 今天碰到的问题是&#xff0c;shader绘制的时候&#xff0c;因…...

系统集成项目管理工程师学习笔记之启动过程组

第十章 启动过程组 制定项目章程 定义 制定项目章程是编写一份正式批准项目并授权项目经理在项目活动中使用组织资源的文件的过程。 正式批准的项目文件 作用 1、明确项目与组织战略目标之间的直接联系 2、确立项目的正式地位 3、展示组织对项目的承诺 本过程仅开展一…...

vscode 常用调试

一、文件执行 python script.py {"name": "Python 调试程序: 当前文件","type": "debugpy","request": "launch","program": "${file}","console": "integratedTerminal"…...

Java 07异常

异常 指的是程序在编译和执行的过程中&#xff0c;出现的非正常的情况&#xff1b; 当然语法错误并不属于错误异常体系 最大的Throwable; 分为两个&#xff1a;Error ExceptionError 严重级别问题 常见的 堆内存溢出 栈内存溢出Exception 分为两个子类 RuntimeException 运…...

2025年PMP 学习二十三 16章 高级项目管理

2025年PMP 学习二十三 16章 高级项目管理 文章目录 2025年PMP 学习二十三 16章 高级项目管理高级项目管理战略管理战略管理的组成要素&#xff1a;企业战略转化为战略行动的阶段&#xff1a; 组织战略类型战略组织类型组织级项目管理OPM&#xff08;公司项目管理&#xff09; 组…...

【Java高阶面经:微服务篇】1.微服务架构核心:服务注册与发现之AP vs CP选型全攻略

一、CAP理论在服务注册与发现中的落地实践 1.1 CAP三要素的技术权衡 要素AP模型实现CP模型实现一致性最终一致性(Eureka通过异步复制实现)强一致性(ZooKeeper通过ZAB协议保证)可用性服务节点可独立响应(支持分区存活)分区期间无法保证写操作(需多数节点可用)分区容错性…...

ISCC 2025决赛 wp

PWN Dilemma 64位程序没有开启PIE&#xff0c;并且过滤了execve&#xff0c;不能使用system这些的了&#xff0c;所以要考虑ORW来做 进入main函数分析&#xff0c;这里有两个函数一个func_1一个func_2。 这两个函数都有漏洞&#xff0c;以下是详细分析&#xff1a; 对于func…...

C++(5)switch语句 循环while

这是一个电影评分的程序 default 就是 如果上述的都没有执行 就统一的执行default的内容。 然后记得break ___________________________________ 循环 &#xff08;while&#xff09; while的使用方式 输出 0-9的while循环...

操作系统----软考中级软件工程师(自用学习笔记)

目录 1、计算机系统层次结构 2、程序顺序执行的特征 3、程序并发执行的特征 4、三态模型 5、同步与互斥 6、信号量机制 7、PV操作 8、死锁 9、进程资源图 10、死锁避免 11、线程 12、程序局部性原理 13、分页存储管理 14、单缓冲器 15、双缓冲区 16、磁盘调度算…...

利用Spring Boot和Redis构建高性能缓存系统

利用Spring Boot和Redis构建高性能缓存系统 引言 在现代Web应用中&#xff0c;缓存是提升系统性能的关键技术之一。Redis作为一种高性能的内存数据库&#xff0c;广泛应用于缓存场景。本文将介绍如何利用Spring Boot和Redis构建一个高性能的缓存系统&#xff0c;涵盖Redis的基…...

每日一题:1、虚拟IPv4地址转换为32位整数(JS)

题目背景 我们需要处理一种特殊的虚拟IPv4地址&#xff0c;这种地址由4个小节组成&#xff0c;每节之间用#分隔。与标准IPv4地址不同&#xff0c;虚拟IPv4地址的第一节范围是1~128&#xff0c;后三节的范围是0~255。我们需要将这种虚拟IPv4地址转换为一个唯一的32位整数。如果…...

[Vue]组件介绍和父子组件间传值

组件介绍 Vue3的 .vue文件中的主要部分分别分为三个&#xff1a;<template>、<script>、<style> <template>&#xff1a; 结构&#xff0c;相当于原html中的<head><body><footer>部分。原本的index.html现在只做一个容器&#xff0…...

Vue3 中使用 provide/inject 实现跨层级组件传值失败的原因及解决方案

1、基础用法 父组件&#xff1a; <script setup> import { ref, provide } from vue; import ChildComponent from ./ChildComponent.vue; const parentData ref(初始数据); // 提供数据 provide(parentData, parentData); </script>子组件&#xff1a; <sc…...

Git Hooks 和 自动生成 Commit Message

前言&#xff1a; 企业编程必须始终依赖流程&#xff0c;而不是个人。个人能力很重要&#xff0c;应该鼓励&#xff0c;但不能指望它&#xff0c;否则软件质量将不一致&#xff0c;没有可持续性。一旦顶级程序员跳槽&#xff0c;公司就会陷入困境。企业应该努力改进工作流程&am…...

【小明剑魔视频Viggle AI模仿的核心算法组成】

Viggle AI 作为一款先进的生成式视频AI工具&#xff0c;其核心技术栈融合了多项前沿算法。以下是深度解析其核心算法架构及实现原理&#xff1a; 一、核心算法组成 1. 运动控制生成&#xff08;Motion Control Generation&#xff09; 算法框架&#xff1a;基于扩散模型&…...

Linux学习心得问题整理(二)

day05 Linux基础入门 Linux语法解析 如何理解ssh远程连接?如何使用ssh使用远程连接服务&#xff1f; ssh进也称远程服务终端&#xff0c;常见连接方式可以包括windows和Linux两种方式 首先咱们使用windows窗口进行连接&#xff0c;这里就采用xshell连接工具来给大家做演示吧…...

百度网盘加速补丁v7.14.1.6使用指南|PC不限速下载实操教程

软件介绍 本加速补丁可突破百度网盘限速限制&#xff0c;无需会员、无次数限制&#xff0c;实测下载速度可达带宽峰值。 三步极速配置教程 1. 环境准备 → 卸载电脑原有百度网盘客户端&#xff08;避免冲突&#xff09; → 关闭杀毒软件/安全卫士&#xff08;防止误删补丁&am…...

RocketMQ消息拉取模式详解

RocketMQ提供了两种消息拉取模式&#xff0c;Pull模式&#xff08;主动拉取&#xff09;和 Push模式&#xff08;长轮询&#xff09;。 一、消息拉取模式分类 1. Pull模式&#xff08;主动拉取&#xff09; 特点&#xff1a;消费者主动向Broker发送请求拉取消息实现类&#…...

C++23 容器从其他兼容范围的可构造性与可赋值性 (P1206R7)

文章目录 背景与动机提案内容与实现细节提案 P1206R7实现细节编译器支持 对开发者的影响提高灵活性简化代码向后兼容性 总结 C23标准引入了对容器构造和赋值的新特性&#xff0c;这些特性使得容器能够更灵活地从其他兼容范围初始化&#xff0c;并支持从范围赋值。这些改进由提案…...

深入解析 HTTP 中的 GET 请求与 POST 请求​

在互联网的世界里&#xff0c;数据的传输与交互无时无刻不在发生。HTTP&#xff08;超文本传输协议&#xff09;作为 Web 应用的基石&#xff0c;承载着浏览器与服务器之间的通信重任。而 GET 请求和 POST 请求&#xff0c;作为 HTTP 协议中最为常用的两种请求方法&#xff0c;…...

华三(H3C)IRF堆叠心跳的LACP MAD、BFD MAD和ARP MAD差异

华三&#xff08;H3C&#xff09;IRF堆叠心跳的三种MAD&#xff08;多主检测&#xff09;机制——LACP MAD、BFD MAD和ARP MAD在实现原理、组网要求及适用场景上存在显著差异。以下是三者的对比分析&#xff1a; 一、核心区别对比 特性LACP MADBFD MADARP MAD检测原理扩展LAC…...

thread 的mutex优化

std::mutex mtx; int shared_data 0;void increment() {std::lock_guard<std::mutex> lock(mtx); // 自动加锁shared_data; // 临界区 } // 离开作用域时自动解锁std::lock_guard 在离开作用域时自动解锁的行为是基于 C 的 RAII (Resource Acquisition Is Initializa…...

深入解析前端 JSBridge:现代混合开发的通信基石与架构艺术

引言&#xff1a;被低估的通信革命 在移动互联网爆发式增长的十年间&#xff0c;Hybrid App&#xff08;混合应用&#xff09;始终占据着不可替代的地位。作为连接 Web 与 Native 的神经中枢&#xff0c;JSBridge 的设计质量直接决定了应用的性能上限与开发效率。本文将突破传…...

打破次元壁,VR 气象站开启气象学习新姿势​

在教育领域&#xff0c;VR 气象站同样发挥着巨大的作用&#xff0c;为气象教学带来了全新的模式&#xff0c;打破了传统教学的次元壁&#xff0c;让学生们以全新的姿势学习气象知识。​ 在传统的气象教学中&#xff0c;学生们主要通过课本、图片和老师的讲解来学习气象知识。这…...