(JVM)带你一起研究JVM的语法糖功能 和 JVM的即时编译器
1. 语法糖
所谓的语法糖,其实就是指java编译器把*.java源码编译为*.class字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是java编译器给我们第一个额外福利
以下代码的分析,借助了javap工具,idea的反编译功能,idea插件jclasslib等工具。
另外,编译器转换的结果直接就是class字节码,只是为了便于阅读,给出了几乎等价的java源码方式,并不是编译器还会转换出中间的java源码。
1.1 默认构造器
public class Candy1{}
编译成class后的代码
public class Candy1{// 这个无参构造是编译器帮助我们加上的public Candy1(){super();// 即父类Object的无参构造方法,即调用 java/lang/Object."<init>":()V}
}
1.2 自动拆装箱
这个特性是JDK5开始加入的,代码片段1:
public class Candy2{public static void main(String[] args){Integer x = 1;int y = x;}
}
这段代码在JDK5之前是无法编译通过的,必须改写为如下:
public class Candy2{public static void main(String[] args){Integer x = Integer.valueOf(1);int y = x.intValue();}
}
显然之前版本的代码太麻烦了,需要在基本类型和包装类型之间来回转换(尤其是集合类中操作的都是包装类型),因此这些转换的事情在JDK5以后都由编译器在编译阶段完成。
1.3 泛型集合取值
泛型也是在JDK5开始加入的特性,但java在编译泛型代码会执行泛型擦除的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当作了Object类型来处理:
public class demo4 {public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();list.add(10); // 实际调用的是 List.add(Object e)Integer x = list.get(0); // 实际调用的是 Object obj = List.get(int index);}
}
所以在取值时,编译器真正生成字节码中,还要额外做一个类型转换的操作:
// 需要将Object转为Integer类型
Integer x = (Integer)list.get(0);
如果前面的x变量类型修改为int基本类型那么最终生成的字节码是:
// 需要将Object转为Integer,并执行拆箱操作
int x = ((Integer)list.get(0)).intValue();
泛型擦除,在字节码中,所有的对象类型都被转为了Object了
所幸,这些麻烦事不用我们自己做
擦除的是字节码上的泛型信息,可以看到LocalVariableTypeTable仍然保留了方法参数泛型的信息
Classfile /E:/Java/学习案例/JVM/JVM/src/test/java/Class/demo4.classLast modified 2024年10月29日; size 478 bytesMD5 checksum 4d2758e590977925b85935c072dc7242Compiled from "demo4.java"
public class Class.demo4minor version: 0major version: 55flags: (0x0021) ACC_PUBLIC, ACC_SUPERthis_class: #8 // Class/demo4super_class: #9 // java/lang/Objectinterfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:#1 = Methodref #9.#18 // java/lang/Object."<init>":()V#2 = Class #19 // java/util/ArrayList#3 = Methodref #2.#18 // java/util/ArrayList."<init>":()V#4 = Methodref #7.#20 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;#5 = Methodref #2.#21 // java/util/ArrayList.add:(Ljava/lang/Object;)Z#6 = Methodref #2.#22 // java/util/ArrayList.get:(I)Ljava/lang/Object;#7 = Class #23 // java/lang/Integer#8 = Class #24 // Class/demo4#9 = Class #25 // java/lang/Object#10 = Utf8 <init>#11 = Utf8 ()V#12 = Utf8 Code#13 = Utf8 LineNumberTable#14 = Utf8 main#15 = Utf8 ([Ljava/lang/String;)V#16 = Utf8 SourceFile#17 = Utf8 demo4.java#18 = NameAndType #10:#11 // "<init>":()V#19 = Utf8 java/util/ArrayList#20 = NameAndType #26:#27 // valueOf:(I)Ljava/lang/Integer;#21 = NameAndType #28:#29 // add:(Ljava/lang/Object;)Z#22 = NameAndType #30:#31 // get:(I)Ljava/lang/Object;#23 = Utf8 java/lang/Integer#24 = Utf8 Class/demo4#25 = Utf8 java/lang/Object#26 = Utf8 valueOf#27 = Utf8 (I)Ljava/lang/Integer;#28 = Utf8 add#29 = Utf8 (Ljava/lang/Object;)Z#30 = Utf8 get#31 = Utf8 (I)Ljava/lang/Object;
{public Class.demo4();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 5: 0public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=2, locals=3, args_size=10: new #2 // class java/util/ArrayList3: dup4: invokespecial #3 // Method java/util/ArrayList."<init>":()V7: astore_18: aload_19: bipush 1011: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;14: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z17: pop18: aload_119: iconst_020: invokevirtual #6 // Method java/util/ArrayList.get:(I)Ljava/lang/Object;23: checkcast #7 // class java/lang/Integer26: astore_227: returnLineNumberTable:line 7: 0line 8: 8line 9: 18line 10: 27
}
SourceFile: "demo4.java"
1.4 可变参数
可变参数也是JDK5开始加入的新特性:
public class demo5 {public static void foo(String... args){String[] arr = args;// 直接赋值System.out.println(arr);}public static void main(String[] args) {foo("hello","world");}
}
可变参数String… args 其实是一个String[] args, 从代码的赋值语句中就可以看出来
同样java编译器会在编译期间就将上述代码转为:
public class demo5 {public static void foo(String... args){String[] arr = args;// 直接赋值System.out.println(arr);}public static void main(String[] args) {foo(new String[]{"hello","world"});}
}
如果调用了foo()则等价代码为foo(new String[]{}),创建了一个空的数组,而不会传递null进去
1.5 foreach循环
仍是jdk5开始引入的语法糖,数组的循环:
public class demo6 {public static void main(String[] args) {int[] array = {1,2,4,5}; // 数组赋值的简化写法,也算是语法糖for (int i : array) {System.out.println(i);}}
}
编译器编译后,转换为:
public class demo6 {public static void main(String[] args) {int[] array = new int[]{1,2,4,5};for (int i=0;i<array.length;++i) {int x = array[i]; // x 就是自己定义接受的变量 i System.out.println(x);}}
}
集合的循环:
public void test1_List(int[] args){List<Integer> list = Arrays.asList(1,2,3,4,5,6);for (Integer i : list) {System.out.println(i);}
}
实际被编译器转换为迭代器的调用:
public class demo6 {public static void main(String[] args) {}public void test1_List(int[] args){List<Integer> list = Arrays.asList(1,2,3,4,5,6);Iterator list = list.iterator(); // 获取迭代器while(iter.hasNext()){// 对迭代器中包含的对象进行循环Integer e = (Integer)iter.next();// Object类型转为Integer类型System.out.println(e);}}
}
foreach循环写法,能够配合数组,以及所有实现了iterable接口的集合类一起使用,其中Iterable用来获取集合的迭代器(Iterator)
1.6 switch字符串
从JDK7开始,switch可以作用于字符串和枚举类,这个功能其实也是语法糖,例如:
package Class;public class demo7 {public static void main(String[] args) {}public static void cc(String str){switch (str){case "hello":{System.out.println("h");break;}case "world":{System.out.println("w");break;}}}
}
switch配合String和枚举使用时,变量不能为null,原因分析完语法糖转换后的代码应当自然清除
会被编译器转换为:
public static void cc(String str){byte x = -1;switch (str.hashCode()){case 99162322: // hello 的哈希值if (str.equals("hello")){x = 0;}break;case 113318802: // world的哈希值if (str.equals("world")){x=1;}break;}switch (x){case 0:System.out.println("h");break;case 1:System.out.println("w");}
}
可以看到执行了两边switch,第一遍根据字符串的hashCode和equals将字符串的转换为相应byte类型,第二遍才是利用byte执行进行比较。
为什么第一遍时必须即比较hashCode,又利用equals比较呢?hashCode是为了提高效率,减少可能的比较;而equals是为了防止hashCode冲突,例如BM
和C.
这两个字符串的hashCode值都是2123。
1.7 switch枚举
switch枚举的例子,原始代码:
enum Sex{MALE,FEMALE
}
public class demo8 {public static void foo(Sex sex){switch (sex){case MALE:System.out.println("男");break;case FEMALE:System.out.println("女");break;}}
}
/*** 定义一个合成类(仅JVM使用,对我们不可见)* 用来映射枚举的 ordinal 与数组元素的关系* 枚举的 ordinal 表示枚举对象的序号,从 0 开始* 即 MALE 的 ordinal()=0,FEMALE 的 ordinal() = 1*/
static class $MAP{static int[] map = new int[2];static {map[Sex.MALE.ordinal()] = 1;map[Sex.FEMALE.ordinal()] = 2;}
}
public static void foo(Sex sex){int x = $MAP.map[sex.ordinal()];switch (x){case 1:System.out.println("男");break;case 2:System.out.println("女");break;}
}
1.8 枚举类
JDK 7 新增了枚举类,以前面的性别枚举为例:
enum Sex{MALE,FEMALE
}
转换后的代码:
public final class Sex extends Enum<Sex>{public static final Sex MALE;public static final Sex FEMALE;private static final Sex[] $VALUES;static {MALE = new Sex("MALE",0);FEMALE = new Sex("FEMALE",1);$VALUES = new Sex[]{MALE, FEMALE};}private Sex(String name,int ordinal){super(name,ordinal);}public static Sex[] values(){return $VALUES.clone();}public static Sex ValueOf(String name){return Enum.valueOf(Sex.class,name);}
}
本质上枚举还是创建了一个对象,只不过继承了一个枚举类型的对象
枚举中的值是对象中的成员变量
1.9 try-with-resources
JDK7开始新增了对需要关闭的资源处理的特殊语法try-with-resources:
try(资源变量=创建资源对象){}catch(){}
其中资源对象需要实现 AutoCloseable 接口,例如:InputStream、OutputStream、Connection、Statement、ResultSet 等接口都实现了AutoCloseable,使用try-with-resources可以不用谢finally语句块,编译器会帮助生成关闭资源代码
public class demo9{public static void main(String[] args){try(InputStream is = new FileInputStream("./1.text")){System.out.println(is);}catch(IOException e){e.printStackTrace();}}
}
转换为:
public class demo10 {public static void main(String[] args) {try{FileInputStream is = new FileInputStream("./1.text");Throwable t = null;try{System.out.println(is);}catch (Throwable e1){t = e1;throw e1;// t 就是代码出现的异常,}finally {if (is!=null){if (t!=null){try {is.close();}catch (Throwable e2){// 如果close出现异常,那么会作为被压制异常添加t.addSuppressed(e2);}}else {// 当代码并未出现异常,close出现的异常就是最后catch块中的eis.close();}}}}catch (IOException e){e.printStackTrace();}}
}
为什么要设计一个addSuppresed(Throwable e)(添加被压制异常)的方法呢?
- 是为了防止异常信息的丢失
1.10 方法重写时的桥接方法
都知道,方法重写时对放回值分为两种情况:
- 父子类的返回值完全一致
- 子类返回值可以是父类返回值的子类
class A{public Number m(){return 1;}
}
class B extends A{// 子类m 方法返回值是 Integer 是父类 m 方法返回值 Number 的子类@Overridepublic Integer m(){return 2;}
}
对于子类,编译器会做如下处理
class B extends A{public Integer m(){return 2;}// 此方法才是真正重写了父类public Number m() 方法public synthetic bridge Number m(){// 调用 public Integer m()return m();}
}
其中桥接方法比较特殊,仅对java虚拟机可见,并且与原来的public Integer m() 没有命名冲突,可以用下面的反射代码来验证:
for(Method m: B.class.getDeclaredMethods()){System.out.println(m);
}
输出:
public java.lang.Integer 方法地址;
public java.lang.Number 方法地址;
1.11 匿名内部类
public class demo12{public static void test(final int x){Runnable runnable = new Runnable(){@Overridepublic void run(){System.out.println("ok: "+x);}};}
}
转换后
final class demo12$1 implements Runnable{int val$x;demo12$1(int x){this.val$x=x;}public void run(){System.out.println("ok: " + this.val$x);}
}
在编译后,会创建一个新的类,并实现Runnable接口,以此来实现run方法
在创建 demo12$1 对象时,将x的值赋值给 demo$1 对象的val$x属性,x不应该再发生改变,如果改变了那么val$x属性没有机会再跟着一起变化,这解释了为什么匿名内部类引用局部变量时,局部变量必须时final的原因
2. 即时编译
2.1 分层编译
(TieredCompliation)
例:
public static void main(String[] args) {for (int i = 0; i < 200; i++) {long start = System.nanoTime();for (int j = 0; j < 1000; j++) {new Object();}long end = System.nanoTime();System.out.println("触发数:"+i+",耗时:"+(end-start));}
}
JVM将执行状态分成 5 个层次:
- 解释执行(Interpreter)
- 使用c1即时编译执行(不带 profiling)
- 使用c1即时编译执行(带基本的 profiling)
- 使用c1即时编译执行(带完全的profiling)
- 使用c2即时编译执行
profiling 是指在运行过程中收集一些程序执行状态的数据,例如:
- 方法的调用次数
- 循环的回边次数
这种优化手段称之为 逃逸分析 ,发现新建的对象是否可以逃逸,可以使用如下选项关闭逃逸分析
-XX: -DoEscapeAnalysis
即时编译器(JIT)与解释器的区别
- 解释器是将字节码解释为机器码,下次即使遇到相同的字节码,仍会执行重复的解释
- JIT是将一些字节码编译为机器码,并存入Code Cache,下次遇到相同的代码,直接执行,无需再编译
- 解释器是将字节码解释为针对所有平台都通用的机器码
- JIT 会根据平台类型,生成平台特定的机器码
对于占据大部分的不常用代码,我们无需耗费事件将其编译成机器码,而是采用解释执行的方式运行。
另一方面,对于仅占据小部分的热点代码,我们则可以将其编译成机器码,以达到理想的运行速度。执行效率上简单比较一下 Interpreter < C1 < C2 ,总的目标是发现热点代码(hotspot名称的由来,优化之)
2.2 方法内联
/*** 方法内联*/
@Test
public void test1(){int x = 0;for (int i = 0; i < 500; i++) {long start = System.nanoTime();for (int j = 0; j < 1000; j++) {x = square(9);}long end = System.nanoTime();System.out.println("触发数:"+i+",耗时:"+(end-start)+",\t 相乘数值:"+x);}
}
private static int square(final int a){return a * a;
}
如果发现square是热点方法,并且长度不太长时,会进行内联。
内联操作:把方法内代码拷贝、粘贴到调用者的位置
这种方法可以大大降低对方法的解析速度
2.3 反射优化
public class demo2 {public static void foo(){System.out.println("foo...");}public static void main(String[] args) throws NoSuchMethodException, IOException, InvocationTargetException, IllegalAccessException {Method foo = demo2.class.getMethod("foo");for (int i =0; i<=16;i++){System.out.printf("%d\t",i);// 调用反射foo.invoke(null);}System.in.read();}
}
在进行反射时,底层代码会进行判断,当调用超过15次时,JVM会直接使用类进行调用
private static int inflationThreshold = 15;static int inflationThreshold() {return inflationThreshold;
}public Object invoke(Object obj, Object[] args)throws IllegalArgumentException, InvocationTargetException
{// 从工厂中拿取反射次数阈值(默认15)if (++numInvocations > ReflectionFactory.inflationThreshold()&& !method.getDeclaringClass().isHidden()&& generated == 0&& U.compareAndSetInt(this, GENERATED_OFFSET, 0, 1)) {try {MethodAccessorImpl acc = (MethodAccessorImpl)new MethodAccessorGenerator(). // 方法访问器生成器 在这里就会直接生成调用方法generateMethod(method.getDeclaringClass(),method.getName(),method.getParameterTypes(),method.getReturnType(),method.getExceptionTypes(),method.getModifiers());parent.setDelegate(acc);} catch (Throwable t) {// Throwable happens in generateMethod, restore generated to 0generated = 0;throw t;}}return invoke0(method, obj, args);
}
故而,在超出反射阈值后,JVM会给你使用使用类调用方法
- demo2.foo(); // 就是这行代码
通过查看 ReflectionFactory源码可知:
- sun.reflect.noInflation 可以用来禁用膨胀(直接生成GeneratedMethodAccessor1,但首次生成比较耗时,如果仅反射调用以此,不划算)
- sun.reflect.inflationThreshold 可以修改膨胀阈值
3. 😊👉前篇知识点
- 深入JAVA底层 JVM(Java 虚拟机)!带你认识JVM、程序计数器、JVM栈和方法栈还有堆内存!
- 在JVM中,类是如何被加载的呢?带你认识类加载的一整套流程!
4. 💕好文相推
- 还不了解Git分布式版本控制器?本文将带你全面了解并掌握
- 带你认识依赖、继承和聚合都是什么!有什么用?
- 2-3树思想与红黑树的实现与基本原理
- !全网最全! ElasticSearch8.7 搭配 SpringDataElasticSearch5.1 的使用
相关文章:
(JVM)带你一起研究JVM的语法糖功能 和 JVM的即时编译器
1. 语法糖 所谓的语法糖,其实就是指java编译器把*.java源码编译为*.class字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是java编译器给我们第一个额外福利 以下代码的分析,借助了javap工具…...
【Linux】ClickHouse 部署
搭建Clickhouse集群时,需要使用Zookeeper去实现集群副本之间的同步,所以需要先搭建zookeeper集群 1、卸载 # 检查有哪些clickhouse依赖包: [rootlocalhost ~]# yum list installed | grep clickhouse# 移除依赖包: [rootlocalho…...
js的小知识
以下是一些 JavaScript 的小知识点,适合不同水平的开发者: 1. 变量声明 使用 let、const 和 var 声明变量。let 和 const 块级作用域,而 var 是函数作用域。const 声明的变量不可重新赋值,但对象的属性仍然可以修改。 2. 箭头函…...

一些swift问题
写得比较快,如果有问题请私信。 序列化和反序列化 反序列化的jsonString2只是给定的任意json字符串 private func p_testDecodeTable() {let arr ["recordID123456", "recordID2"]// 序列化[string] -> json datalet jsonData try? JSO…...

Nginx安装配置详解
Nginx Nginx官网 Tengine翻译的Nginx中文文档 轻量级的Web服务器,主要有反向代理、负载均衡的功能。 能够支撑5万的并发量,运行时内存和CPU占用低,配置简单,运行稳定。 写在前 uWSGI与Nginx的关系 1. 安装 Windows 官网 Stabl…...

汽车免拆诊断案例 | 2010款起亚赛拉图车发动机转速表指针不动
故障现象 一辆2010款起亚赛拉图车,搭载G4ED 发动机,累计行驶里程约为17.2万km。车主反映,车辆行驶正常,但组合仪表上的发动机转速表指针始终不动。 故障诊断 接车后进行路试,车速表、燃油存量表及发动机冷却温度…...
在ubuntu上安装最新版的clang
方法一: 执行如下的命令: # 下载安装脚本wget https://apt.llvm.org/llvm.sh chmod x llvm.sh # 开始下载, 输入需要安装的版本号。 sudo ./llvm.sh <version number>方法二 添加软件下载源。 请根据自己的Ubuntu系统版本添加&…...

使用Django REST framework构建RESTful API
使用Django REST framework构建RESTful API Django REST framework简介 安装Django REST framework 创建Django项目 创建Django应用 配置Django项目 创建模型 迁移数据库 创建序列化器 创建视图 配置URL 配置全局URL 配置认证和权限 测试API 使用Postman测试API 分页 过滤和排序…...

「Mac畅玩鸿蒙与硬件14」鸿蒙UI组件篇4 - Toggle 和 Checkbox 组件
在鸿蒙开发中,Toggle 和 Checkbox 是常用的交互组件,分别用于实现开关切换和多项选择。Toggle 提供多种类型以适应不同场景,而 Checkbox 支持自定义样式及事件回调。本篇将详细介绍这两个组件的基本用法,并通过实战展示它们的组合应用。 关键词 Toggle 组件Checkbox 组件开…...
Kotlin协程suspend的理解
suspend修饰符,它可以告诉编译器,该函数需要在协程中执行。作为开发者,您可以把挂起函数看作是普通函数,只不过它可能会在某些时刻挂起和恢复而已。协程的挂起就是退出方法,等到一定条件到来会重新执行这个方法&#x…...

基于AI深度学习的中医针灸实训室腹针穴位智能辅助定位系统开发
在中医针灸的传统治疗中,穴位取穴的精确度对于治疗效果至关重要。然而,传统的定位方法,如体表标志法、骨度折量法和指寸法,由于观察角度、个体差异(如人体姿态和皮肤纹理)以及环境因素的干扰,往…...

51单片机教程(二)- 创建项目
1 创建项目 创建项目存储文件夹:C51Project 打开Keil5软件,选择 Project -> New uVision Project: 选择项目路径,即刚才创建的文件夹 选择芯片,选择 Microchip(微型集成电路)࿰…...
Rust 图形界面开发——使用 GTK 创建跨平台 GUI
第五章 图形界面开发 第一节 使用 GTK 创建跨平台 GUI GTK(GIMP Toolkit)是一个流行的开源跨平台图形用户界面库,适用于创建桌面应用程序。结合 Rust 的 gtk-rs 库,开发者能够高效地构建现代化 GUI 应用。本节将详细探讨 GTK 的…...

Hellinger Distance(赫林格距离)
Hellinger Distance(赫林格距离)是一种用于衡量两个概率分布相似度的距离度量。它通常用于概率统计、信息论和机器学习中,以评估两个分布之间的相似性。Hellinger距离的值介于0和1之间,其中0表示两个分布完全相同,1表示…...
【系统架构设计师】七、设计模式
7.1 设计模式概述 设计经验在实践者之间日益广泛地利用,描述这些共同问题和解决这些问题的方案就形成了所谓的模式。 7.1.1 设计模式的历史 建筑师Christopher Alexander首先提出了模式概念,他将模式分为了三个部分: 特定的情景ÿ…...

新工具可绕过 Google Chrome 的新 Cookie 加密系统
一位研究人员发布了一款工具,用于绕过 Google 新推出的 App-Bound 加密 cookie 盗窃防御措施并从 Chrome 网络浏览器中提取已保存的凭据。 这款工具名为“Chrome-App-Bound-Encryption-Decryption”,由网络安全研究员亚历山大哈格纳 (Alexander Hagenah…...

模型拆解(三):EGNet、FMFINet、MJRBM
文章目录 一、EGNet1.1编码器:VGG16的扩展网络 二、EMFINet2.1编码器:三分支并行卷积编码器2.2CFFM:级联特征融合模块2.3Edge Module:突出边缘提取模块2.4Bridge Module:桥接器2.5解码器:深度特征融合解码器…...
齐次线性微分方程的解的性质与结构
内容来源 常微分方程(第四版) (王高雄,周之铭,朱思铭,王寿松) 高等教育出版社 齐次线性微分方程定义 d n x d t n a 1 ( t ) d n − 1 x d t n − 1 ⋯ a n − 1 ( t ) d x d t a n ( t ) x 0 \frac{\mathrm{d}^nx}{\mathrm{d}t^n} a_1(t)\frac{\mathrm{d}^{n-1}x}{\math…...
Python-Celery-基础用法总结-安装-配置-启动
文章目录 1.安装 Celery2.配置 Celery3.启动 Worker4.调用任务5.任务装饰器选项6.任务状态7.定期任务8.高级特性9.监控和管理 Celery 是一个基于分布式消息传递的异步任务队列。它专注于实时操作,但也支持调度。Celery 可以与 Django, Flask, Pyramid 等 Web 框架集…...
vue中的nextTick() - 2024最新版前端秋招面试短期突击面试题【100道】
nextTick() - 2024最新版前端秋招面试短期突击面试题【100道】 🔄 在Vue.js中,nextTick 是一个重要的方法,用于在下次DOM更新循环结束之后执行回调函数。理解 nextTick 的原理和用法可以帮助你更好地处理DOM更新和异步操作。以下是关于 next…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...
提升移动端网页调试效率:WebDebugX 与常见工具组合实践
在日常移动端开发中,网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时,开发者迫切需要一套高效、可靠且跨平台的调试方案。过去,我们或多或少使用过 Chrome DevTools、Remote Debug…...
HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散
前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说,在叠衣服的过程中,我会带着团队对比各种模型、方法、策略,毕竟针对各个场景始终寻找更优的解决方案,是我个人和我司「七月在线」的职责之一 且个人认为,…...