深入理解Java虚拟机系列 - 总结

深入理解Java虚拟机系列 - 总结

  • 前言
  • 一. JVM 内存模型和 Java 对象模型
    • 1.1 JVM 内存模型包括哪些?作用分别是?
    • 1.2 JVM 内存模型中的各个区域的特点?
    • 1.3 对象分配内存的方式有哪些?
    • 1.4 对象的内存布局是怎样的?
      • ① 对象头
      • ② 实例数据
      • ③ 对齐填充
    • 1.5 对象的访问方式有哪些?
  • 二. 垃圾收集器与内存分配策略
    • 2.1 JVM 中判断对象死亡的方式有哪些?
      • ① 引用计数法
      • ② 可达性分析法
    • 2.2 JVM的引用类型有哪几种?
      • ① 强引用(Strong Reference)
      • ② 软引用(Soft Reference)
      • ③ 弱引用(Weak Reference)
      • ④ 虚引用(Phantom Reference)
    • 2.3 对象如何逃脱死亡命运?finalize()干啥的?
    • 2.4 垃圾收集算法有哪些?分别有什么特性?
      • ① 标记清除法
      • ② 复制法
      • ③ 标记整理法
    • 2.5 新老年代分别适用于什么算法?
    • 2.5 在GC的时候,有哪些特性?
    • 2.6 JVM 垃圾收集器有哪些?
  • 三. 虚拟机类加载机制
    • 3.1 类的生命周期包括哪些阶段?
      • ① 加载
      • ② 验证
      • ③ 准备(重点)
      • ④ 解析
      • ⑤ 初始化(重点)
    • 3.2 类加载器是什么?有哪些种类
    • 3.3 什么是双亲委派?什么情况下需要打破这个规则?
  • 四. Java内存模型和线程
    • 4.1 Java 内存模型有哪些规定?
    • 4.2 volatile 关键字的作用?
    • 4.3 什么是指令重排?目的是啥?
    • 4.4 volatile的大致原理
    • 4.5 举个volatile关键字的常见用法 - 双重检索?(重点)
    • 4.6 原子性、可见性和有序性分别介绍下?
    • 4.7 Synchronized和volatile的比较
    • 4.8 Java 锁的优化有哪些?
      • ① 自旋锁
      • ② 锁消除
      • ③ 锁粗化
      • ④ 偏向锁

前言

本篇文章是对:深入理解Java虚拟机系列文章 的一份精炼总结。

一. JVM 内存模型和 Java 对象模型

1.1 JVM 内存模型包括哪些?作用分别是?

针对JDK8来说,JVM内存模型包括:

  • 程序计时器:当前线程所执行的字节码的行动指示器。
  • 虚拟机栈:存放8大基本数据类型的数据、局部变量表、操作数栈、动态链接、方法出口。
  • 本地方法栈:类似于虚拟机栈,但是服务于Native修饰的函数。
  • :存放Java对象实例。
  • 元空间:保存元数据的地方,如方法、字段、类、包的描述信息。类加载器存储的位置就是元空间,每一个类加载器的存储区域都称作为一个元空间。

1.2 JVM 内存模型中的各个区域的特点?

如果按照线程是否私有来区分:

  • 线程私有:程序计时器、虚拟机栈(8大基本数据类型)、本地方法栈。
  • 线程共享:堆(包含方法区,存储常量和静态变量)。

其他特点:

  • 程序计时器:线程私有、唯一不会发生OOM的区域。
  • Java虚拟机栈:线程私有、存放了基本数据类型的变量。
  • 堆(GC堆):虚拟机内存中最大的一块、线程共享、在虚拟机启动的时候创建。存储了静态变量、常量等数据。

1.3 对象分配内存的方式有哪些?

  • 指针碰撞(在Java堆内存连续的情况下):所有用过的内存放在一侧,空闲的内存放在另一侧,中间放着一个指针作为分界点的指示器,那所谓分配内存就是仅仅把这个指针向空闲内存的一侧挪一小段与对象大小相等的距离。(例如Serial、ParNew使用时会用到
  • 空闲列表(Java对内存不连续的情况下):虚拟机维护一个列表,记录哪些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并且更新列表上的记录。(例如CMS收集器使用时会用到

1.4 对象的内存布局是怎样的?

Java对象的内存存储布局如下,分为三块区域:

① 对象头

对象头又包括:

  • MarkWord:存储自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏相关时间戳等
  • 类型指针:类型指针即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
  • 数组长度(如果是数组类型才有)

② 实例数据

真正保存实例数据的地方。

③ 对齐填充

填充Java对象,让它为8的整数倍。

1.5 对象的访问方式有哪些?

  • 使用句柄
  • 直接指针。

二. 垃圾收集器与内存分配策略

2.1 JVM 中判断对象死亡的方式有哪些?

① 引用计数法

有一个地方在引用,计数器+1,引用失效时,计数器-1。任何时刻计数器为0的对象就是不可能在被使用的。:

  • 实现简单,判断效率高。
  • 但是难以解决对象之间循环引用的问题。

② 可达性分析法

算法的主要思路:

  1. 通过一系列的称为“GC Root”的对象作为起始点。
  2. 从这些节点开始向下搜索,搜索走过的路径称为引用链。
  3. 当一个对象到GC roots没有任何引用链相连(即从GC root到这个对象不可达),则说明这个对象是不可用的。

可以作为Root对象包括:

  • 虚拟机栈中引用的对象。(本地变量表)
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中(native方法)引用的变量。

2.2 JVM的引用类型有哪几种?

一共4种,强度从大到小排序:

① 强引用(Strong Reference)

一般是new出来的对象。只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

② 软引用(Soft Reference)

软引用是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列入回收范围内进行第二次回收。

③ 弱引用(Weak Reference)

被弱引用关联的对象只能生存到下一次GC发生之前GC工作的时候,无论当前内存是否足够,都会回收到只被弱引用关联的对象。

例如ThreadLocal类中底层ThreadLocalMapKey就是一个弱引用。

④ 虚引用(Phantom Reference)

虚引用是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就就是能在这个对象被GC回收的时候得到一个系统通知。

2.3 对象如何逃脱死亡命运?finalize()干啥的?

要真正宣告一个对象死亡的话,至少要经历两次标记过程。(GC回收器的算法都会经历2次标记)

如何摆脱死亡的命运呢?大致流程如下:

  1. 对象在进行可达性分析法后发现没有与GC Root相连的引用链,那么他会被第一次标记并且进行筛选.
  2. 筛选的条件是这个对象是否有必要执行finalize()方法。
  3. 如果这个finalize()方法被覆盖过(重写)且没被执行过,那么这个对象会被判为有必要执行finalize方法的。
  4. 若有必要执行,则这个对象会放到一个叫F-Queue的队列之中,并在稍后由一个低优先级的Finalizer线程去触发这个finalize()方法。
  5. **如果这个finalize()方法中成功的让此对象重新与引用链上的任何一个对象关联(即可达),那么在二次标记的时候,就会把这个对象移出“即将回收”的集合。**相反,如果执行后,这个对象还是不可达的,那么他就会被回收。

总结:重写一个对象的finalize函数,让这个对象做到可达,即可能让这个对象逃脱一次被GC的命运。

2.4 垃圾收集算法有哪些?分别有什么特性?

① 标记清除法

算法划分为两个阶段。

  1. 标记阶段:标记出所有需要回收的对象。
  2. 清除阶段:统一回收所有被标记的对象。

缺点:

  • 效率问题:标记和清除两个阶段的效率都不高
  • 空间问题:标记清除之后会产生大量不连续的内存碎片

② 复制法

算法流程如下:

  1. 将可用内存容量划分为大小相等的两块,每次只使用其中的一块。
  2. 当这一块的内存用完了,就将还存活着的对象复制到另一块上,然后再把自己曾经使用过的那一块内存空间清理掉。

优缺点:

  • 优点:实现简单,效率高
  • 缺点:代价是内存会缩小到原来的一半,开销大(有一半拿来复制用)。

③ 标记整理法

算法流程如下:

  1. 标记:对要存活的的对象进行标记
  2. 整理:让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

优点:

  • 标记清除法的升级版,解决了内存碎片的问题。
  • 适用于老年代的GC算法。

2.5 新老年代分别适用于什么算法?

  • 新生代:每次GC都会有大批对象死去,只有少量存活。采用复制算法。
  • 老年代:对象存活率高、没有额外空间对他进行分配担保。采用标记清除or标记整理算法进行回收。

2.5 在GC的时候,有哪些特性?

  • STW(Stop The World)枚举根节点时必须停顿,避免引用关系发生变化。使用OopMap来实现快速定位GC Roots的枚举。
  • 发生GC的时候,程序一定到达了某个安全点(Safepoint)或者安全区域。

2.6 JVM 垃圾收集器有哪些?

新生代GC

  • Serial(单线程)
  • ParNew(多线程)
  • Parallel Scavenge(多线程,目标:达到一个可控制的吞吐量。支持自适应调节策略)

老年代GC

  • Serial Old(单线程、标记整理)
  • Parallel Old(参考Parallel Scavenge,只是服务的范围不一样、标记整理)
  • CMS(目标:获取最短回收停顿时间,并发收集,标记清除)

独立的GC

  • G1(标记整理、复制算法、并行与并发、分代、可预测停顿)

三. 虚拟机类加载机制

3.1 类的生命周期包括哪些阶段?

① 加载

加载阶段需要做三件事情:

  • 获取二进制字节流。
  • 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据的访问接口。

② 验证

确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机自身的安全。验证的内容包括:

  • 文件格式验证:验证字节流是否符合Class文件格式的规范,并能被当前版本的虚拟机处理。
  • 元数据验证:对字节码描述的信息进行语义分析,以保证其符合Java语言规范。
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的。
  • 符号引用验证:对类自身以外的信息进行匹配性校验。目的:确保解析动作能够正常执行。

③ 准备(重点)

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,仅仅针对被static修饰的变量。

但是值得注意的是!!!如以下代码:

public static int value = 123;

在准备阶段,赋值初始值的时候,value的值是0,而不是123。但是如果代码是:

public static final int value = 123;

那这个时候value在准备阶段会赋值为123。static final修饰的字段在javac编译时生成constantValue属性,在类加载的准备阶段直接把constantValue的值赋给该字段。

④ 解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

  • 符号引用:符号引用以一组符号来描述所引用的目标,符号可以使任何形式的字面量
  • 直接引用:直接引用是直接指向目标的指针、相对偏移量或者是一个能够够间接定位到目标的句柄。

⑤ 初始化(重点)

在初始化阶段,会根据我们自己制定的Java代码去初始化类变量和其他资源。换句话说,初始化阶段是执行类构造器< clinit >()方法的过程。

< clinit >()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{})合并而成。
说白了就是:static语句块和声明的static变量。

初始化阶段有以下几个重点知识:

  1. 编译器收集的顺序是由语句在源文件中出现的顺序做决定。
  2. 静态语句块中只能访问到定义在静态语句块之前的变量。
  3. 定义在静态语句块之后的变量,允许前面的静态语句块赋值,但不允许访问。
  4. 虚拟机会保证在子类的< clinit >()方法执行之前,父类的< clinit >()方法已经执行完毕。即父类中定义的静态语句块要优先于子类的变量赋值操作。
  5. 接口中不能使用静态语句块,但是仍然有变量初始化的赋值操作。

3.2 类加载器是什么?有哪些种类

如果能通过一个类的全限定名称来获取描述该类的二进制字节流,那这样的角色称作为类加载器。

若两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,这两个类就必定不相等。

类加载器的种类如下:

  • 启动类加载器(Bootstrap ClassLoader:由C++语言实现,是JVM自身的一部分。
  • 扩展类加载器(Extension ClassLoader:负责加载< JAVA_HOME>\lib\ext目录中的或者被java.ext.dirs系统变量所制定的路径中的所有类库。(开发者可以直接使用)
  • 应用程序类加载器(Application ClassLoader):负责加载用户类路径(ClassPath)上所制定的类库。

关系图如下:在这里插入图片描述

3.3 什么是双亲委派?什么情况下需要打破这个规则?

双亲委派模型的工作流程:

  1. 如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。
  2. 因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个请求的时候,子加载器才会尝试自己去加载。

什么情况下要打破?

  1. 某些情况下父类加载器需要委托子类加载器去加载class文件。
  2. 例如JDBC中的Driver接口的实现是由不同的数据库服务商来提供,有Mysql、Oracle、KingbaseES等数据库。它是由启动类加载器来实现加载(顶层父类),但是具体实现却在子类,因此需要用户程序类加载器加载。
  3. 这个时候就需要打破双亲委派模型,需要优先委托子类加载器来加载Driver的具体实现

四. Java内存模型和线程

4.1 Java 内存模型有哪些规定?

  1. 所有的变量都存储在主内存中。
  2. 每条线程有属于自己的工作内存,其中保存了被该线程使用到的变量的主内存副本拷贝
  3. 线程对变量的所有操作都必须在工作内存当中进行,而不能直接读写主内存中的变量
  4. 不同的线程之间无法直接访问对方工作内存中的变量(私有),线程间变量值的传递需要通过主内存来完成。

如图:
在这里插入图片描述

4.2 volatile 关键字的作用?

volatile 关键字是Java虚拟机提供的最轻量级的同步机制。当一个变量定义为volatile后,它具备两种特性(不保证原子性):

  • 可见性:当一条线程修改了volatile修饰的变量的值,那么该新值对于其他线程来说立即可见。
  • 禁止指令重排序。 相当于增加了一个内存屏障。

4.3 什么是指令重排?目的是啥?

问题1:什么是指令重排?

指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。


问题2:指令重排的目的是什么?

指令重排的目的是为了在不改变程序执行结果的前提下,优化程序的运行效率(指的是不改变单线程下的程序执行结果)。

4.4 volatile的大致原理

  1. 加了volatile修饰的变量,处理器会多出一个带有Lock的汇编指令。

  2. Lock前缀指令主要做了两件事情:
    第一个:将当前处理器缓存行的数据回写到内存当中。
    第二个:这个写回内存的操作会使其他CPU缓存了该内存地址的数据无效。(内存屏障的功能之一)

  3. 换句话说,lock指令加了一个内存屏障,禁止在内存屏障前后的指令执行重排序优化。

4.5 举个volatile关键字的常见用法 - 双重检索?(重点)

背景:

  1. instance = new Singleton();并不是一个原子操作,会被编译成三条指令(按顺序)。1.给instance分配内存。2.初始化其构造器。3.将instance对象指向分配的内存空间。
  2. 也因此在多线程情况下,如果不加入volatile,可能会发生重排序,导致可能的执行顺序是132,从而导致某些线程访问到未初始化的变量。
  3. 也因此用volatile来禁止重排序,通过加入内存屏障的方式来保证执行的顺序为123。

双重检索

public class Singleton {
    private volatile static Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton.getInstance();
    }
}

4.6 原子性、可见性和有序性分别介绍下?

  • 原子性:Java内存模型直接保证的原子性变量操作包括:read、load、assign、use、store、write

  • 可见性:可见性指一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

  • 有序性:Java程序中天然的有序性可以概括为:如果在本线程内观察,所有的操作都是有序的。Java提供了volatile和synchronized两个关键字来保证线程之间操作的有序性。

4.7 Synchronized和volatile的比较

  • volatile不会进行加锁操作(它只是一种稍弱的同步机制),而Synchronized会进行加锁。
  • volatile变量作用类似于同步变量的读写操作,从内存可见性角度来看:1.写入volatile变量——>退出同步代码块。2.读取volatile变量——>进入同步代码块。
  • volatile不如Synchronized安全(前者无锁,后者有)
  • volatile不能同时保证内存可见性和原子性,但是Synchronized可以。

4.8 Java 锁的优化有哪些?

① 自旋锁

如果持有锁的线程能够在很短的时间内释放资源,那么那些正在等待的线程就不用做内核态和用户态的转换而进入堵塞、挂起状态。只要等待一小段时间,就能在其他线程释放资源的瞬间可以立即获得锁。

  • 优点:减少CPU上下文的切换,因此对于占用锁资源时间短或者锁竞争不激烈的代码块性能会高一点。
  • 缺点:如果竞争激烈,那么可能导致长时间自旋,浪费CPU。

② 锁消除

锁消除是指虚拟机在编译器运行时,对一些代码要求同步,但是被检测到实际上不存在共享数据的竞争,那么对于这一类锁,会进行消除。

例如StringBuffer.append函数,就由synchronized关键字修饰。

public String method(String s1, String s2, String s3) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    sb.append(s3);
    return sb.toString();
}
// 其中append源码:
@Override
public synchronized StringBuffer append(String str) {
     toStringCache = null;
     super.append(str);
     return this;
 }

这种时候我们可以添加参数进行锁的消除。

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks

③ 锁粗化

将两个同步代码块合并成一个,以降低多次锁请求、同步、释放带来的系统性能消耗。

④ 偏向锁

偏向锁是指一段同步代码块一直被一个线程访问,那么该线程会自动获取锁,降低获取锁的代价。 其目标是在只有一个线程执行同步代码块的时候能够提高性能。

  • 特点:偏向锁只有遇到其他线程尝试竞争偏向锁的时候,持有偏向锁的线程才会释放锁,也就是说线程不会主动释放锁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/283801.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手摸手系列之SpringBoot+Vue2项目整合高德地图实现车辆实时定位功能

前言 最近在做一个物流内陆运输的项目&#xff0c;其中的一个关键功能是根据车辆的GPS数据在页面上实时显示车辆位置信息。由于我们已经获得了第三方提供的GPS数据&#xff0c;所以接下来的任务是将这些数据整合到我们的系统中&#xff0c;并利用高德地图API来展示车辆的实时位…

【数据结构】栈【详解】

目录 栈的定义&#xff1a; 栈的声明与定义&#xff1a; 头文件的包含&#xff1a; 对栈的基本操作&#xff1a; 栈的初始化&#xff1a; 摧毁栈: 入栈&#xff1a; ​编辑 出栈&#xff1a; ​编辑 输出栈顶位置&#xff1a; 输出栈的当前大小&#xff1a; 判空操…

小兔鲜儿 uniapp - 购物车模块

目录 加入购物车​ 接口相关​ 购物车列表​ 静态结构​ 登录状态​ 列表渲染​ 删除购物车 接口相关​ 参考代码 修改商品信息​ 接口相关​ ​修改商品数量​ 修改商品选中/全选​ 底部结算信息​ 计算总钱数(总金额)​ 带返回按钮的购物车​ 完成加入购物车…

读书笔记1-C++ Primer Plus

C是在C语言基础上开发的一种集面向对象编程&#xff08;OOP&#xff09;、通用编程和传统的过程化编程于一体的编程语言。本书是根据2003年的ISO/ANSI C标准编写的&#xff0c;通过大量短小精悍的程序详细而全面地阐述了C的基本概念和技术。 全书分17章和10个附录&#xff0c;分…

UE5.1_Gameplay Debugger启用

UE5.1_Gameplay Debugger启用 重点问题&#xff1a; Gamplay Debugger启用不知道&#xff1f; Apostrophe、Tilde键不知道是哪个&#xff1f; Gameplay调试程序 | 虚幻引擎文档 (unrealengine.com) Gameplay Debugger

2023下半年的总结

我从八月下旬开始写的&#xff0c;到现在差不多有半年了&#xff0c;总结一下吧&#xff01; 1.计算机视觉 在计算机视觉方面&#xff0c;想必两个有名的深度学习框架&#xff08;TensorFlow和PyTorch&#xff09;大家都很清楚吧&#xff0c;以及OpenCV库。对于人脸识别&…

王道考研计算机网络——应用层

如何为用户提供服务&#xff1f; CS/P2P 提高域名解析的速度&#xff1a;local name server高速缓存&#xff1a;直接地址映射/低级的域名服务器的地址 本机也有告诉缓存&#xff1a;本机开机的时候从本地域名服务器当中下载域名和地址的对应数据库&#xff0c;放到本地的高…

cargo设置国内源 windows+linux

cargo默认的源比pip的源好多了&#xff0c;但是有时候速度还是很慢 一、部分国内源&#xff08;排名不分先后&#xff09; 这些源的格式用在具体的配置文件中 中国科学技术大学 [source.crates-io] replace-with ustc[source.ustc] registry "git://mirrors.ustc.ed…

用LCD循环右移显示“Welcome to China“

#include<reg51.h> //包含单片机寄存器的头文件 #include<intrins.h> //包含_nop_()函数定义的头文件 sbit RSP2^0; //寄存器选择位&#xff0c;将RS位定义为P2.0引脚 sbit RWP2^1; //读写选择位&#xff0c;将RW位定义为P2.1引脚 sbit EP2^2; //使能…

【形式语言与自动机/编译原理】CFG-->Greibach-->NPDA(2)

本文将详细讲解《形式语言与自动机》&#xff08;研究生课程&#xff09;或《编译原理》&#xff08;本科生课程&#xff09;中的上下文无关文法&#xff08;CFG&#xff09;转换成Greibach范式&#xff0c;再转成下推自动机&#xff08;NPDA&#xff09;识别语言是否可以被接受…

内侧APP分发平台:移动应用开发的加速器

在数字化时代&#xff0c;移动应用已成为企业触达用户的重要渠道。为了迅速占领市场&#xff0c;开发者需要一种能够快速发布和测试移动应用的解决方案。内侧APP分发平台应运而生&#xff0c;它通过简化应用的封装、测试和分发流程&#xff0c;极大地提升了移动应用的上市速度。…

WPF+Halcon 培训项目实战(13):HS 鼠标绘制图形

文章目录 前言相关链接项目专栏运行环境匹配图片矩形鼠标绘制Halcon添加右键事件Task封装运行结果个人引用问题原因推测 圆形鼠标绘制代码运行结果 后面安排 前言 为了更好地去学习WPFHalcon&#xff0c;我决定去报个班学一下。原因无非是想换个工作。相关的教学视频来源于下方…

《深入理解JAVA虚拟机笔记》对象的创建和访问、对象头

对象的创建 当 Java 虚拟机遇到一条字节码 new 指令时&#xff0c;首先将去检查这个指令的参数是否能做常量池中定位到一个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有&#xff0c;那必须先执行相应的类加载过程。 在类加载…

2021-03-17 51单片机设计洗衣机

缘由51单片机设计洗衣机_其他-CSDN问答 通过控制两个继电器循环工作状态&#xff0c;模拟洗衣机间歇正反转。设定正转3s&#xff0c;停止2s&#xff0c;然后反转3s&#xff0c;停止2s&#xff0c;循环上述动作。求代码和proteus仿真图。 #include "reg52.h" sbit L…

金融帝国实验室(Capitalism Lab)官方正版游戏『2024新年特卖优惠』

「金融帝国实验室」&#xff08;Capitalism Lab&#xff09;Enlight 官方正版游戏「2024新年特卖」 ■优惠时限&#xff1a;2024.01.01&#xff5e;01.31 ■游戏开发商&#xff1a;Enlight Software Ltd. 请您认准以下官方正版游戏购买链接&#xff1a;支持“支付宝&am…

08 通信协议之UART

引言&#xff1a; 从本文开始&#xff0c; 本个专题之后的几篇文章都是讲解嵌入式开发中几种常见的通信协议的&#xff0c; 比如UART, I2C&#xff0c;SPI&#xff0c; CAN总线这些我就不讲了&#xff0c; 没用到过&#xff0c; 学是学不完的&#xff0c; 等用到的时候再去学习…

如何开发一个google插件(二)

前言 在上一篇文章如何开发一个google插件(一)里主要介绍了google插件的基本结构。 在这篇文章中主要结合reactwebpack进行一个代码演示&#xff0c;源码地址&#xff1a;源码地址 下载源码后打开浏览器的扩展程序管理->加载已解压的扩展程序&#xff0c;即可调试插件 此…

2024年安全员-B证证模拟考试题库及安全员-B证理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年安全员-B证证模拟考试题库及安全员-B证理论考试试题是由安全生产模拟考试一点通提供&#xff0c;安全员-B证证模拟考试题库是根据安全员-B证最新版教材&#xff0c;安全员-B证大纲整理而成&#xff08;含2024年…

java struts2教务管理系统Myeclipse开发mysql数据库struts2结构java编程计算机网页项目

一、源码特点 java struts2 教务管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助 struts2 框架开发&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境 为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库…

7.12全排列②(LC47-M)

算法&#xff1a; 这道题目和46.全排列 (opens new window)的区别在与给定一个可包含重复数字的序列&#xff0c;要返回所有不重复的全排列。 所以就是多了个去重操作。 还是一样的套路&#xff1a; 先排序&#xff1a; Arrays.sort(nums); 再去重&#xff1a; // used[…