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

java--jvm虚拟机(都是要点)

请带着以下问题,学习并理解jvm


问题一: 为什么fullGC会对系统性能有影响?youngGC却几乎没有?

问题二: outofmemory是什么异常?什么时候会出现?如何处理?

问题三: 线程安全和不安全在jvm内存模型中是如何表现的?

问题四: 提升fullGC回收的速度的方法有哪些?

问题五: 为什么频繁fullGC常伴随CPU的满负荷

一,简介

Java Virtual Machine(JVM,Java 虚拟机)是 Java 平台的核心组件之一,
负责执行 Java 字节码并为 Java 程序提供运行时环境。JVM 使得 Java 程序具有“编写一次,随处运行”的能力,
允许 Java 程序在不同的平台上执行,而不需要重新编译。

1,主要组成部分

  • 类加载器子系统 (Class Loader Subsystem)

    • 负责将 .class 文件加载到内存中。
  • 运行时数据区 (Runtime Data Areas)

    • 方法区 (Method Area):存储类信息、常量、静态变量和方法代码。
    • 堆 (Heap):存储所有对象实例和数组。
    • 栈 (Java Stack):存储每个线程的局部变量和部分结果。每调用一个方法都会创建一个新的栈帧。
    • 程序计数器 (Program Counter Register):保存当前线程执行的字节码指令的地址。
    • 本地方法栈 (Native Method Stack):为每个线程保存本地方法的信息。
  • 执行引擎 (Execution Engine)

    • 解释器 (Interpreter):逐条解释执行字节码指令。
    • 即时编译器 (JIT Compiler):将热点代码编译为机器码以提高性能。
    • 垃圾收集器 (Garbage Collector):自动管理内存,回收不再使用的对象。

二,jvm工作原理

  • 启动:启动类加载器将启动类(包含 main 方法的类)加载到 JVM 中。
  • 类加载:类加载器根据需要动态加载其他类。
  • 字节码验证:验证加载的字节码以确保其安全性和正确性。
  • 解释/编译执行:执行引擎解释或编译字节码,然后执行相应的机器码。
  • 垃圾收集:垃圾收集器在后台运行,自动回收不再使用的内存。

三,内存模型

程序计数器(Program Counter Register)
虚拟机栈(JVM Stack)
本地方法栈(Native Method Stack)
堆(Heap)
方法区(Method Area)
运行时常量池(Runtime Constant Pool)
直接内存(Direct Memory)

程序计数器(Program Counter Register)
功能:程序计数器是一个较小的内存区域,用于记录当前线程所执行的字节码的地址。它是线程私有的,每个线程都有一个独立的程序计数器。
作用:在多线程环境中,程序计数器用于保持线程的执行状态,帮助线程在执行方法调用和返回时能够正确地恢复执行位置。

虚拟机栈(JVM Stack)
功能:虚拟机栈用于存储方法调用、局部变量、操作数栈、动态链接和方法出口等信息。每个线程都有一个独立的虚拟机栈。
结构:
栈帧(Stack Frame):每个方法调用对应一个栈帧,栈帧包含局部变量表、操作数栈、动态链接和方法返回地址等。
局部变量表(Local Variable Table):存储方法的参数和局部变量。
操作数栈(Operand Stack):执行方法时,操作数栈用于存放中间计算结果。

本地方法栈(Native Method Stack)
功能:本地方法栈与虚拟机栈类似,但是它用于支持 Native 方法的调用。Native 方法是用其他语言(如 C/C++)编写的方法,通过 JNI(Java Native Interface)与 Java 代码交互。
区别:本地方法栈用于支持本地代码的执行,而虚拟机栈主要用于 Java 字节码的执行。

堆(Heap)
功能:堆是 JVM 中最大的内存区域,用于存储所有的对象实例和数组。它是垃圾回收器(GC)主要关注的区域。
结构:
新生代(Young Generation):用于存储新创建的对象。新生代又分为 Eden 区和两个 Survivor 区(S0 和 S1)。对象在新生代经过若干次 GC 后,如果仍然存活,会被晋升到老年代。
老年代(Old Generation):用于存储长时间存活的对象。老年代的 GC 比较少,通常是 Full GC 或 Major GC。
永久代(PermGen)(Java 8 之前):存储类的元数据、静态变量等。Java 8 及之后版本中被方法区替代。
元空间(Metaspace)(Java 8 及之后):替代永久代,存储类的元数据,不再使用堆内存,而是使用本地内存。

方法区(Method Area)
功能:方法区是 JVM 的一部分,用于存储已加载的类信息、常量池、静态变量和即时编译器编译后的代码等。
区别:方法区是 JVM 堆的一部分,但它专门用于存储类相关的数据,与堆中对象的数据分开管理。

运行时常量池(Runtime Constant Pool)
功能:运行时常量池是方法区的一部分,用于存储类文件中的常量(如字符串字面量、类和方法的引用)。
结构:在类加载时,常量池中的常量会被加载到方法区的运行时常量池中。

直接内存(Direct Memory)
功能:直接内存是 JVM 堆外的内存区域,通过 java.nio 包的 ByteBuffer 类进行访问。直接内存用于提高 I/O 操作的效率。
特点:直接内存的分配和释放不受 JVM 垃圾回收的直接管理,但它需要通过 Unsafe 类或 JNI 进行操作。

四,垃圾回收

垃圾回收(Garbage Collection, GC)是 Java 虚拟机(JVM)内存管理中的一个重要方面,它负责自动回收不再使用的对象,以释放内存空间。垃圾回收算法的选择和实现直接影响到应用程序的性能、
响应时间和资源使用效率。以下是一些常见的垃圾回收算法和策略的介绍:

1. 垃圾回收算法概述

垃圾回收算法可以大致分为以下几种类型:

引用计数(Reference Counting)
标记-清除(Mark-Sweep)
标记-整理(Mark-Compact)
复制算法(Copying)
分代收集(Generational Collection)

2. 引用计数(Reference Counting)

原理:

  • 每个对象维护一个计数器,记录对它的引用数量。当引用计数变为零时,表示对象不再被使用,可以被回收。

优点:

  • 实现简单,回收实时性高。

缺点:

  • 无法处理循环引用(即两个或多个对象互相引用),导致内存泄漏。
  • 需要额外的内存开销来维护引用计数。

3. 标记-清除(Mark-Sweep)

原理:

  • 标记阶段:从根对象(如栈中的引用)开始,遍历所有可达的对象,并标记这些对象。
  • 清除阶段:遍历堆中的所有对象,回收那些没有被标记的对象。

优点:

  • 实现简单,能有效地回收不再使用的对象。

缺点:

  • 碎片化:标记和清除的过程中可能产生内存碎片。
  • 性能开销:清除阶段可能会对堆内存进行大量扫描和清理,影响性能。

4. 标记-整理(Mark-Compact)

原理:

  • 标记阶段:与标记-清除算法相同,标记所有可达的对象。
  • 整理阶段:将所有被标记的对象移动到堆的一端,清理空闲的内存区域。

优点:

  • 无碎片化:整理阶段将对象紧凑地移动到堆的一端,避免了内存碎片问题。

缺点:

  • 性能开销:需要移动对象,可能导致额外的开销。

5. 复制算法(Copying)

原理:

  • 将堆分为两个相等的区域(如 Survivor 区和 Eden 区)。在回收时,将活动对象从一个区域复制到另一个区域。
  • 完成后,清空原来的区域,交换两个区域的角色。

优点:

  • 简化管理:只需要处理两个区域中的对象,避免了内存碎片化。
  • 高效:对于新生代对象,复制算法可以高效地回收。

缺点:

  • 内存开销:需要额外的内存来维护两个区域。
  • 对象移动:需要将对象从一个区域复制到另一个区域,可能会导致性能开销。

6. 分代收集(Generational Collection)

原理:
        根据对象的生命周期将堆划分为不同的区域(新生代和老年代)。
        新生代:用于存放新创建的对象,通常使用复制算法。
        老年代:用于存放经过多次 GC 仍然存活的对象,通常使用标记-整理算法。
        长期存活对象:会从新生代晋升到老年代。
优点:

  •         优化:新生代通常包含大量短生命周期的对象,使用复制算法可以高效地处理这些对象。老年代则处理长期存活的对象,减少了 GC 的频率。
  •         灵活:可以根据对象的生命周期优化垃圾回收策略。

缺点:
        复杂性:需要管理多个区域和不同的垃圾回收算法。

7. 常见的垃圾回收器

Java 的垃圾回收器基于以上算法设计,并提供了不同的 GC 策略和实现:

Serial GC:

使用标记-清除和标记-整理算法,适合单核处理器。
-XX:+UseSerialGC

Parallel GC(吞吐量优先 GC):

适合多核处理器,使用多线程并行处理新生代和老年代的垃圾回收。
-XX:+UseParallelGC

Concurrent Mark-Sweep (CMS) GC(低延迟 GC):

主要使用标记-清除算法,降低了垃圾回收的暂停时间。
-XX:+UseConcMarkSweepGC

G1 GC(Garbage First GC):

旨在实现低延迟和高吞吐量,通过将堆划分为多个区域(Region)来优化 GC。
-XX:+UseG1GC

ZGC(Z Garbage Collector):

旨在提供低延迟的垃圾回收,支持大内存(TB级别)。
-XX:+UseZGC

Shenandoah GC:

提供低延迟和可预测的 GC 暂停时间,适用于大内存应用。
-XX:+UseShenandoahGC

JDK 1.8 的默认垃圾回收算法
默认垃圾回收器
        Parallel GC(也称为 Throughput Collector)
特点

  • 新生代:Parallel GC 在新生代使用 复制算法(Copying Algorithm),将对象从 Eden 区复制到 Survivor 区(S0 和 S1)。当一个 Survivor 区满时,存活的对象会被复制到另一个 Survivor 区或老年代。
  • 老年代:在老年代使用 标记-整理算法(Mark-Compact),对象会被整理到堆的一端,回收未被引用的对象,并且解决内存碎片问题。

JDK 11 的默认垃圾回收算法
默认垃圾回收器
        G1 GC(Garbage-First Garbage Collector)
特点

  • 新生代:G1 GC 也会使用类似复制算法的机制,在新生代中对对象进行管理。
  • 老年代:G1 GC 使用 分代收集 和 区域划分,将堆分为多个小块(Region)。G1 GC 在回收时会优先回收垃圾最多的区域,并进行 并行 和 并发 的垃圾回收。
  • 性能:G1 GC 旨在提供低延迟和高吞吐量,适合处理大内存和高负载的应用。

默认配置

  • 新生代和老年代的区域划分:G1 GC 将堆划分为多个大小相同的区域(Region),新生代和老年代都使用这些区域。
  • 垃圾回收参数:默认使用 -XX:+UseG1GC。可以通过 -XX:MaxGCPauseMillis 设置期望的最大 GC 暂停时间。

JDK 17 的默认垃圾回收算法
默认垃圾回收器
        G1 GC(Garbage-First Garbage Collector)
特点

  • 新生代:G1 GC 在新生代中使用与 JDK 11 相似的复制算法。
  • 老年代:G1 GC 在老年代中使用标记-整理和分代收集策略。G1 GC 在 JDK 17 中继续优化性能,改进了 并发标记 和 并发整理 机制。
  • 性能:G1 GC 仍然提供低延迟的 GC 机制,同时更好地支持大内存应用。

默认配置

  • 新生代和老年代的区域划分:堆依然被划分为多个 Region。G1 GC 的区域划分和垃圾回收策略在 JDK 17 中继续优化。
  • 垃圾回收参数:默认使用 -XX:+UseG1GC。G1 GC 在 JDK 17 中进一步优化了 GC 暂停时间和吞吐量,可以通过 -XX:MaxGCPauseMillis 和 -XX:InitiatingHeapOccupancyPercent 等参数来调整。

总结
JDK 1.8 默认使用 Parallel GC,主要通过标记-清除和标记-整理算法进行垃圾回收,适合于吞吐量优先的场景。
JDK 11 和 JDK 17 默认使用 G1 GC,它是一个低延迟的垃圾回收器,适合处理大内存和要求较低 GC 暂停时间的应用。G1 GC 通过将堆划分为多个区域,并优先回收垃圾最多的区域,来提供高效的垃圾回收服务。

五,youngGC与fullGC

Young GC(新生代垃圾回收)

概述
Young GC 是指对 新生代(Young Generation) 内存区域的垃圾回收。新生代主要用于存储新创建的对象,这些对象在生命周期的早期通常具有较短的存活时间。Young GC 是对这些对象的回收操作,目的是清理掉短生命周期的对象,腾出空间给新的对象。

新生代的组成
Eden 区:新创建的对象首先被分配到 Eden 区。
Survivor 区:Eden 区中存活的对象会被移动到 Survivor 区,通常有两个 Survivor 区,S0 和 S1。

Young GC 的过程

对象分配:新创建的对象首先分配到 Eden 区。
Minor GC:当 Eden 区满时,会触发 Young GC(也称为 Minor GC)。在 Young GC 过程中,垃圾回收器会标记和清除 Eden 区中不再被引用的对象。
对象晋升:存活的对象会被移动到 Survivor 区。如果对象在 Survivor 区经过一定次数的 GC 仍然存活,就会被晋升到老年代。
GC 频率:由于新生代的空间通常较小,Young GC 的频率较高。它旨在快速回收短生命周期的对象,以减少对老年代的压力。

Young GC 的特点

回收速度快:由于新生代中的对象大多是短生命周期的,回收过程通常很快。
暂停时间较短:Young GC 通常是应用程序的暂停时间较短的 GC 操作,但它会频繁发生。
主要回收新创建的对象:清理短生命周期对象,避免它们占用老年代的空间。

Full GC(完全垃圾回收)

概述
Full GC 是指对 整个堆内存(包括新生代和老年代) 进行垃圾回收的操作。Full GC 包括对新生代和老年代的回收,它通常发生在以下几种情况下:

老年代内存不足:当老年代内存不足时,触发 Full GC 来回收老年代中的垃圾对象。
永久代或元空间内存不足:在 Java 8 之前,永久代(PermGen)用于存储类的元数据,内存不足时也会触发 Full GC。Java 8 及之后版本中的元空间(Metaspace)类似地可能导致 Full GC。
手动触发:使用 System.gc() 方法手动触发 Full GC,虽然不推荐在生产环境中使用。

Full GC 的过程

标记-清除:与新生代的 Young GC 类似,Full GC 也使用标记-清除或标记-整理算法来清理无用对象。
标记-整理:在老年代中,Full GC 可能会使用标记-整理算法,将存活的对象移动到堆的一端,回收未被引用的对象,解决内存碎片问题。
类加载器的回收:在 Full GC 中,还可能会回收不再使用的类及其元数据。
Full GC 的特点
回收范围广:Full GC 涉及整个堆的回收,包括新生代和老年代。
暂停时间较长:由于回收范围广,Full GC 通常会导致较长时间的应用暂停。
频率较低:相比 Young GC,Full GC 的发生频率较低,但每次发生时的停顿时间较长。


Young GC 和 Full GC 的对比

特性  Young GC Full GC
回收范围 仅回收新生代(Young Generation) 回收整个堆(包括新生代和老年代)
触发条件新生代内存不足,通常在 Eden 区满时触发老年代内存不足,永久代或元空间内存不足
停顿时间 较短,频繁发生较长,较少发生
对应用的影响 相对较小,但可能会影响吞吐量和响应时间较大,可能会导致显著的应用暂停
回收策略 复制算法(Eden 区到 Survivor 区)标记-清除、标记-整理等复杂算法

六,jvm异常

当 JVM 堆内存不够用时,通常会出现以下几种异常或错误:

1. OutOfMemoryError

OutOfMemoryError 是 JVM 在运行时抛出的错误,表示 JVM 已经耗尽了可用的堆内存。这个错误有几个不同的具体类型,分别针对不同的内存不足情况:

java.lang.OutOfMemoryError: Java heap space

说明:这是最常见的堆内存不足错误,表示 Java 堆(Heap)内存空间不足,无法分配足够的内存来创建新的对象。
触发条件:通常发生在堆内存设置不够、内存泄漏、或应用程序创建了大量对象但未能及时释放它们时。

java.lang.OutOfMemoryError: GC overhead limit exceeded

说明:这是指 JVM 在进行垃圾回收时,尽管进行了频繁的 GC,但回收的内存仍然远远不足以满足应用程序的需求。这个错误通常表明应用程序存在内存泄漏或配置不合理的问题。
触发条件:当 GC 占用的 CPU 时间过多,且回收的内存总量占用总内存的比例过低时,会触发此错误。这个错误可以通过设置 -XX:-UseGCOverheadLimit 来关闭 GC 开销限制检测。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

说明:当应用程序试图创建一个大小超过 JVM 允许的最大数组时,抛出此错误。
触发条件:试图分配的数组太大,超出了 JVM 对数组大小的限制。

2. 内存不足的常见原因

堆内存设置不足:JVM 的堆内存设置过低,无法满足应用程序的需求。可以通过 -Xmx 参数增加最大堆内存大小,例如 -Xmx2g 设置最大堆内存为 2GB。

内存泄漏:应用程序中存在内存泄漏,导致对象无法被回收,从而不断消耗堆内存。

大对象分配:试图分配一个过大的对象或数组,超出了堆内存的限制。

垃圾回收不足:GC 策略不适合应用场景,导致内存回收不及时,增加了堆内存压力。

3. 如何诊断和解决

监控和分析:

使用 JVM 提供的工具(如 jstat, jmap, jconsole)和外部工具(如 VisualVM, GCViewer)来监控堆内存使用情况,分析 GC 日志,识别内存使用模式和可能的内存泄漏。

调整 JVM 参数:

增加堆内存大小:通过 -Xms 和 -Xmx 调整堆的初始和最大大小。例如,-Xmx4g 设置最大堆内存为 4GB。
调整 GC 策略:选择合适的垃圾回收器和调整其参数。例如,使用 -XX:+UseG1GC 启用 G1 垃圾回收器并调整相关参数。

代码优化:

检查和修复内存泄漏:使用内存分析工具查找并修复内存泄漏,例如通过 Heap Dump 分析,找到持有大量对象引用的地方。
优化对象创建和生命周期管理:避免过度创建和缓存对象,合理管理对象的生命周期。

配置优化:

配置适当的 -XX:MaxGCPauseMillis 来调整 GC 暂停时间,以平衡 GC 停顿时间和吞吐量。
配置适当的 -XX:NewRatio、-XX:SurvivorRatio 等参数来优化新生代和老年代的内存分配。

4. 示例配置

# 增加最大堆内存到 4GB
java -Xmx4g -Xms4g -jar myapp.jar

# 启用 G1 GC 并设置最大 GC 暂停时间为 200ms
java -Xmx4g -Xms4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar myapp.jar

# 禁用 GC 开销限制检测
java -Xmx4g -Xms4g -XX:-UseGCOverheadLimit -jar myapp.jar

七,jvm的线程独享区与共享区

上面已经介绍了jvm的内存模型,我们再将其按照线程独享与共享进行划分

线程独享:

程序计数器(Program Counter Register)

作用:每个线程有一个独立的程序计数器,记录当前线程正在执行的字节码的行号。
特性:它是线程私有的,线程切换不会影响其他线程的程序计数器状态。

虚拟机栈(JVM Stack)

作用:每个线程都有自己的虚拟机栈,用于存储方法的局部变量、操作栈、动态链接、方法出口等信息。
特性:栈帧在每个方法调用时创建并销毁,线程的栈帧和栈内数据对其他线程不可见。

本地方法栈(Native Method Stack)

作用:类似于虚拟机栈,但专门用于处理 native 方法调用。
特性:线程的本地方法栈也是私有的,对其他线程不可见。

线程局部变量(ThreadLocal)

作用:ThreadLocal 提供线程局部变量,每个线程都有自己独立的变量副本。
特性:线程局部变量对其他线程不可见,适合用来存储线程特定的数据,如用户会话、数据库连接等。

线程共享:

堆内存(Heap Memory)

作用:用于存储对象和数组,是 Java 对象的主要存储区域。
特性:堆内存对所有线程共享,每个线程都可以访问和操作堆中的对象。因为堆是共享的,所以必须通过适当的同步机制来确保线程安全。

方法区(Method Area)(在 Java 8 及以后版本中称为元空间 Metaspace)

作用:用于存储类的信息、常量、静态变量等。
特性:方法区是所有线程共享的,类的元数据和静态变量存储在方法区中。因此,类的加载、卸载、静态变量的访问等操作需要同步,以避免线程不安全的问题。

运行时常量池(Runtime Constant Pool)

作用:方法区的一部分,存储类、接口、字段、方法的常量。
特性:运行时常量池对所有线程共享,访问和修改常量池需要考虑线程安全问题。

在方法中new 一个对象会先在堆中创建它,然后在栈内存中将变量指向堆中地址。大家可以先看看以下代码,并用说明其中的builder对象,在多线程高并发的环境下,有没有可能出现不安全的情况(也就是打印出783123123)这种情况。并说明为什么。

    public void prr() {StringBuilder builder = new StringBuilder();builder.append("783");gt(builder);}public void gt(Object st) {try {((StringBuilder) st).append("123");System.out.println(st.toString());} finally {((StringBuilder) st).delete(0, ((StringBuilder) st).length());}}

如果能准确回答出这个问题,那么一定堆jvm的内存有了一定的理解了。恭喜你
产生多线程问题的原因
1,要有共享资源
2,要有多线程竞争使用资源

我们可以对上面的代码增加一个测试

    public static void main(String[] args) {BinarySearch example = new BinarySearch();for (int i = 0; i < 10000; i++) {new Thread(example::prr).start();}}

输出结果肯定无783123123这种情况,主要原因是不是共享资源
如果我们将代码改变一下就能明显发现问题

public class BinarySearch {StringBuilder builder = new StringBuilder();public void prr() {builder.append("783");gt(builder);}public void gt(Object st) {try {((StringBuilder) st).append("123");System.out.println(st.toString());} finally {((StringBuilder) st).delete(0, ((StringBuilder) st).length());}}public static void main(String[] args) {BinarySearch example = new BinarySearch();for (int i = 0; i < 10000; i++) {new Thread(example::prr).start();}}
}

这时再进行测试就能发现问题。
分析:

主要原因是由于方法的执行是新建栈进行对局部变量表的操作进行的,而虚拟机栈为线程独享的,所以并不会产生多线程并发问题。


 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 卡牌游戏(200分) - 三语言AC题解(Python/Java/Cpp)
  • HarmonyOS应用开发者高级认证,Next版本发布后最新题库 - 多选题序号5
  • vue3前端架构---打包配置
  • ScriptableObject使用
  • 新手小白做视频素材去哪里下载?8个无水印视频素材库分享
  • mysql 主从复制 读写分离 MHA
  • Android APK混淆处理方案分析
  • MySQL 存储引擎详解
  • 关于 夜莺n9e 的简易部署
  • 动态接口调优:在Mojo模型中调整模型的输入输出接口
  • 学习记录:ESP32控制舵机 FREERTOS BLE
  • RuoYi-Vue-Plus(动态添加移除数据源)
  • 构建查询洞察 UI
  • WEB前端10- Fetch API(同步/异步/跨域处理)
  • 基于Markdown的文档网站生成工具-VitePress框架
  • 【347天】每日项目总结系列085(2018.01.18)
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • 【许晓笛】 EOS 智能合约案例解析(3)
  • Django 博客开发教程 16 - 统计文章阅读量
  • export和import的用法总结
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • Joomla 2.x, 3.x useful code cheatsheet
  • Js基础——数据类型之Null和Undefined
  • Js基础知识(一) - 变量
  • Linux链接文件
  • linux学习笔记
  • node学习系列之简单文件上传
  • REST架构的思考
  • Spring思维导图,让Spring不再难懂(mvc篇)
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • 阿里云应用高可用服务公测发布
  • 关于字符编码你应该知道的事情
  • 要让cordova项目适配iphoneX + ios11.4,总共要几步?三步
  • 远离DoS攻击 Windows Server 2016发布DNS政策
  • #APPINVENTOR学习记录
  • #pragma data_seg 共享数据区(转)
  • (04)odoo视图操作
  • (CVPRW,2024)可学习的提示:遥感领域小样本语义分割
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束
  • (第三期)书生大模型实战营——InternVL(冷笑话大师)部署微调实践
  • (二十九)STL map容器(映射)与STL pair容器(值对)
  • (佳作)两轮平衡小车(原理图、PCB、程序源码、BOM等)
  • (十八)Flink CEP 详解
  • (图文详解)小程序AppID申请以及在Hbuilderx中运行
  • (一)、软硬件全开源智能手表,与手机互联,标配多表盘,功能丰富(ZSWatch-Zephyr)
  • (原創) 博客園正式支援VHDL語法著色功能 (SOC) (VHDL)
  • (转)iOS字体
  • (转)程序员疫苗:代码注入
  • (转载)Google Chrome调试JS
  • .Net CF下精确的计时器
  • .NET CORE使用Redis分布式锁续命(续期)问题
  • .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调