首页 文章 精选 留言 我的

精选列表

搜索[快速入门],共10000篇文章
优秀的个人博客,低调大师

一分钟快速了解边缘计算

无论IT端还是OT端的主流厂商都将目光聚焦在了“边缘计算”这个焦点上,边缘计算(Edge computing)是相对云计算而言的,它是指收集并分析数据的行为发生在靠近数据生成的本地设备和网络中。边缘计算又被叫做分布式云计算、雾计算或第四代数据中心。 1、什么是边缘计算 边缘计算是接近事物,或者将是在数据和行动源头进行的,更靠近设备端和用户。像一只八爪鱼,有两个强大的记忆系统:云计算是大脑记忆系统,边缘计算是八个爪子上的吸盘。也就是说,云计算就像章鱼大脑只有一个,而边缘计算有八条腿可以思考并解决问题,一个爪子就是一个小型机房,靠近具体实物。 2、为什么需要边缘计算 到2020年,互联网会有500亿用户,每天产生大约几PB的数据,如果这些数据直接上传数据中心进行处理,不仅会浪费巨大的网络带宽,也会给存储带来负担。如果有了边缘计算,那么别人还在加载不出来,你缺已经下载完一部几百兆电影,就很快乐。所以也可以说,边缘计算和5G是相辅相成。 3、边缘计算应用场景 边缘计算在物联网时代不断增长的数据催生了需求,边缘计算的典型应用场景有:工业制造、安全监控、ARVR、智能交通、自动驾驶、智慧家居、智慧城市、智慧路灯、风力发电、医疗保健、无人机等。

优秀的个人博客,低调大师

JDK 14如期发布,16个新特性快速预览

JDK 14已经于2020年3月17日如期发布。本文介绍JDK 14特性。 JEP 305: instanceof的模式匹配(预览) 通过对instanceof运算符进行模式匹配来增强Java编程语言。 模式匹配允许程序中的通用逻辑,即从对象中有条件地提取组件,可以更简洁,更安全地表示。 这是JDK 14中的预览语言功能。 动机 几乎每个程序都包含某种逻辑,这些逻辑结合了对表达式是否具有某种类型或结构的测试,然后有条件地提取其状态的组件以进行进一步处理。例如,以下是在Java程序中常见的instanceof-and-cast用法: if (obj instanceof String) { String s = (String) obj; // 使用s } 上述示例中,为了能够安全地将obj转为我们期望的String类型,需要通过instanceof运算符对obj进行类型判断。这里发生了三件事: 测试obj是否是一个String 将obj转换为String 声明新的局部变量s,以便我们可以使用字符串值。 这种模式很简单,并且所有Java程序员都可以理解,但是由于一些原因,它不是最优的。 语法乏味 同时执行类型检测和类型转换并不是必要的 String类型在程序中出现了3次,这混淆了后面更重要的逻辑 重复的代码容易滋生错误 在JDK 14中,上述代码可以改为下面的方式: if (obj instanceof String s) { // 使用s } 这样整个代码看上去更加简洁。 描述 类型测试模式由指定类型的谓词和单个绑定变量组成。在下面的代码中,短语String是类型测试模式: if (obj instanceof String s) { // 使用s } else { // 不能使用s } 如果obj是String的实例,则将其强制转换为String并分配给绑定变量s。绑定变量在if语句的true块中,而不在if语句的false块中。 与局部变量的范围不同,绑定变量的范围由包含的表达式和语句的语义确定。例如,在此代码中: if (!(obj instanceof String s)) { .. s.contains(..) .. } else { .. s.contains(..) .. } true块中的s表示封闭类中的字段,false块中的s表示由instanceof运算符引入的绑定变量。 当if语句的条件变得比单个instanceof更复杂时,绑定变量的范围也会相应地增长。 例如,在此代码中: if (obj instanceof String s && s.length() > 5) {.. s.contains(..) ..} 绑定变量s在&&运算符右侧以及true块中。仅当instanceof成功并分配给s时,才评估右侧。 另一方面,在此代码中: if (obj instanceof String s || s.length() > 5) {.. s.contains(..) ..} 绑定变量s不在||右侧的范围内运算符,也不在true块的范围内。s指的是封闭类中的一个字段。 Joshua Bloch的经典著作Effective Java中有一段代码示例: @Override public boolean equals(Object o) { return (o instanceof CaseInsensitiveString) && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); } 这段代码可以使用新的语法写成: @Override public boolean equals(Object o) { return (o instanceof CaseInsensitiveString cis) && cis.s.equalsIgnoreCase(s); } 这个特性很有意思,因为它为更为通用的模式匹配打开了大门。模式匹配通过更为简便的语法基于一定的条件来抽取对象的组件,而instanceof刚好是这种情况,它先检查对象类型,然后再调用对象的方法或访问对象的字段。 JEP 343: 打包工具(孵化) 该特性旨在创建一个用于打包独立Java应用程序的工具。 动机 许多Java应用程序需要以一流的方式安装在本机平台上,而不是简单地放置在类路径或模块路径上。对于应用程序开发人员来说,交付简单的JAR文件是不够的。他们必须提供适合本机平台的可安装软件包。这允许以用户熟悉的方式分发,安装和卸载Java应用程序。例如,在Windows上,用户希望能够双击一个软件包来安装他们的软件,然后使用控制面板删除该软件。在macOS上,用户希望能够双击DMG文件并将其应用程序拖到Application文件夹中。 打包工具还可以帮助填补其他技术的空白,例如Java Web Start(已从JDK 11中删除)和pack200(已在JDK 11中弃用,可能在以后的版本中删除)。开发人员可以使用jlink将JDK分解为所需的最小模块集,然后使用打包工具生成一个压缩的、可安装的映像,该映像可以部署到目标计算机。 为了以前满足这些要求,JDK 8分发了一个名为javapackager的打包工具。但是,作为删除JavaFX的一部分,该工具已从JDK 11中删除。 描述 jpackage工具将Java应用程序打包到特定于平台的程序包中,该程序包包含所有必需的依赖项。该应用程序可以作为普通JAR文件的集合或作为模块的集合提供。受支持的特定于平台的软件包格式为: Linux:deb和rpm macOS:pkg和dmg Windows:MSI和EXE 默认情况下,jpackage会以最适合其运行系统的格式生成一个软件包。 以下是基本用法: $ jpackage --name myapp --input lib --main-jar main.jar 用法 1. 基本用法:非模块化应用 假设你有一个由JAR文件组成的应用程序,所有应用程序都位于lib目录下,并且主类在lib/main.jar中。下列命令 $ jpackage --name myapp --input lib --main-jar main.jar 将以本地系统的默认格式打包应用程序,并将生成的打包文件保留在当前目录中。如果main.jar中的MANIFEST.MF文件没有Main-Class属性,我们必须显式地指定主类: $ jpackage --name myapp --input lib --main-jar main.jar --main-class myapp.Main 打包的名称是myapp。要启动该应用程序,启动器将从输入目录复制的每个JAR文件都放在JVM的类路径上。 如果希望生成默认格式以外的软件安装包,可以使用--type选项。例如要在macOS上生成pkg文件(而不是dmg文件),我们可以使用下面的命令: $ jpackage --name myapp --input lib --main-jar main.jar --type pkg 2. 基本用法:模块化应用 如果你有一个模块化应用程序,该应用程序由lib目录中的模块化JAR文件和/或JMOD文件组成,并且主类位于myapp模块中,则下面的命令 $ jpackage --name myapp --module-path lib -m myapp 能够将其打包。如果myapp模块无法识别主类,则必须明确指定: $ jpackage --name myapp --module-path lib -m myapp/myapp.Main JEP 345: G1的NUMA内存分配优化 通过实现可识别NUMA的内存分配,提高大型计算机上的G1性能。 动机 现代的多插槽计算机越来越多地具有非统一的内存访问(non-uniform memory access,NUMA),即内存与每个插槽或内核之间的距离并不相等。插槽之间的内存访问具有不同的性能特征,对更远的插槽的访问通常具有更大的延迟。 并行收集器中通过启动-XX:+UseParallelGC能够感知NUMA,这个功能已经实现了多年了,这有助于提高跨多插槽运行单个JVM的配置的性能。其他HotSpot收集器没有此功能,这意味着他们无法利用这种垂直多路NUMA缩放功能。大型企业应用程序尤其倾向于在多个多插槽上以大堆配置运行,但是它们希望在单个JVM中运行具有可管理性优势。 使用G1收集器的用户越来越多地遇到这种扩展瓶颈。 描述 G1的堆组织为固定大小区域的集合。一个区域通常是一组物理页面,尽管使用大页面(通过 -XX:+UseLargePages)时,多个区域可能组成一个物理页面。 如果指定了+XX:+UseNUMA选项,则在初始化JVM时,区域将平均分布在可用NUMA节点的总数上。 在开始时固定每个区域的NUMA节点有些不灵活,但是可以通过以下增强来缓解。为了为mutator线程分配新的对象,G1可能需要分配一个新的区域。它将通过从NUMA节点中优先选择一个与当前线程绑定的空闲区域来执行此操作,以便将对象保留在新生代的同一NUMA节点上。如果在为变量分配区域的过程中,同一NUMA节点上没有空闲区域,则G1将触发垃圾回收。要评估的另一种想法是,从距离最近的NUMA节点开始,按距离顺序在其他NUMA节点中搜索自由区域。 该特性不会尝试将对象保留在老年代的同一NUMA节点上。 此分配政策中不包括Humongous区。对于这些区,将不做任何特别的事情。 JEP 349: JFR事件流 公开JDK Flight Recorder数据以进行连续监视。 动机 HotSpot VM通过JFR产生的数据点超过500个,但是使用者只能通过解析日志文件的方法使用它们。 用户要想消费这些数据,必须开始一个记录并停止,将内容转储到磁盘上,然后解析记录文件。这对于应用程序分析非常有效,但是监控数据却十分不方便(例如显示动态更新数据的仪表盘)。 与创建记录相关的开销包括: 发出在创建新记录时必须发生的事件 写入事件元数据(例如字段布局) 写入检查点数据(例如堆栈跟踪) 将数据从磁盘存储复制到单独的记录文件 如果有一种方法,可以在不创建新记录文件的情况下,从磁盘存储库中读取正在记录的数据,就可以避免上述开销。 描述 jdk.jfr模块里的jdk.jfr.consumer包,提供了异步订阅事件的功能。用户可以直接从磁盘存储库读取记录数据,也可以直接从磁盘存储流中读取数据,而无需转储记录文件。可以通过注册处理器(例如lambda函数)与流交互,从而对事件的到达进行响应。 下面的例子打印CPU的总体使用率,并持有锁10毫秒。 try (var rs = new RecordingStream()) { rs.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1)); rs.enable("jdk.JavaMonitorEnter").withThreshold(Duration.ofMillis(10)); rs.onEvent("jdk.CPULoad", event -> { System.out.println(event.getFloat("machineTotal")); }); rs.onEvent("jdk.JavaMonitorEnter", event -> { System.out.println(event.getClass("monitorClass")); }); rs.start(); } RecordingStream类实现了接口jdk.jfr.consumer.EventStream,该接口提供了一种统一的方式来过滤和使用事件,无论源是实时流还是磁盘上的文件。 public interface EventStream extends AutoCloseable { public static EventStream openRepository(); public static EventStream openRepository(Path directory); public static EventStream openFile(Path file); void setStartTime(Instant startTime); void setEndTime(Instant endTime); void setOrdered(boolean ordered); void setReuse(boolean reuse); void onEvent(Consumer<RecordedEvent> handler); void onEvent(String eventName, Consumer<RecordedEvent handler); void onFlush(Runnable handler); void onClose(Runnable handler); void onError(Runnable handler); void remove(Object handler); void start(); void startAsync(); void awaitTermination(); void awaitTermination(Duration duration); void close(); } 创建流的方法有3种: EventStream::openRepository(Path)从磁盘存储库中构造一个流。这是一种可以直接通过文件系统监视其他进程的方法。磁盘存储库的位置存储在系统属性jdk.jfr.repository中,可以使用API读取到。 EventStream::openRepository()方法执行进程内监控。与RecordingStream不同,它不会开始录制。相反,仅当通过外部方式(例如,使用JCMD或JMX)启动记录时,流才接收事件。 EventStream::openFile(Path)从记录文件中创建流,扩充了已经存在的RecordingFile类。 该接口还可用于设置缓冲的数据量,以及是否应按时间顺序对事件进行排序。为了最大程度地降低分配压力,还可以选择控制是否应为每个事件分配新的事件对象,或者是否可以重用以前的对象。我们可以在当前线程中启动流,也可以异步启动流。 JVM每秒一次将线程本地缓冲区中存储的事件定期刷新到磁盘存储库。 一个单独的线程解析最近的文件,直到写入数据为止,然后将事件推送给订阅者。 为了保持较低的开销,仅从文件中读取活动订阅的事件。 要在刷新完成后收到通知,可以使用EventStream::onFlush(Runnable)方法注册处理程序。 这是在JVM准备下一组事件时将数据聚合或推送到外部系统的机会。 JEP 352: 非易失性映射字节缓冲区 添加新的特定于JDK的文件映射模式,以便可以使用FileChannel API创建引用非易失性内存(non-volatile memory,NVM)的MappedByteBuffer实例。 动机 NVM为应用程序程序员提供了在程序运行过程中创建和更新程序状态的机会,而减少了输出到持久性介质或从持久性介质输入时的成本。这对于事务程序特别重要,在事务程序中,需要定期保持不确定状态以启用崩溃恢复。 现有的C库(例如Intel的libpmem)为C程序提供了对基层NVM的高效访问。他们还以此为基础来支持对各种持久性数据类型的简单管理。当前,由于频繁需要进行系统调用或JNI调用来调用原始操作,从而确保内存更改是持久的,因此即使仅使用Java中的基础库也很昂贵。同样的问题限制了高级库的使用,并且由于C中提供的持久数据类型分配在无法从Java直接访问的内存中这一事实而加剧了这一问题。与C或可以低成本链接到C库的语言相比,这使Java应用程序和中间件(例如Java事务管理器)处于严重的劣势。 该特性试图通过允许映射到ByteBuffer的NVM的有效写回来解决第一个问题。由于Java可以直接访问ByteBuffer映射的内存,因此这可以通过实现与C语言中提供的客户端库等效的客户端库来解决第二个问题,以管理不同持久数据类型的存储。 描述 1. 初步变更 该JEP使用了Java SE API的两个增强功能: 支持implementation-defined的映射模式 MppedByteBuffer::force方法以指定范围 2. 特定于JDK的API更改 通过新模块中的公共API公开新的MapMode枚举值 一个公共扩展枚举ExtendedMapMode将添加到jdk.nio.mapmode程序包: package jdk.nio.mapmode; . . . public class ExtendedMapMode { private ExtendedMapMode() { } public static final MapMode READ_ONLY_SYNC = . . . public static final MapMode READ_WRITE_SYNC = . . . } 在调用FileChannel::map方法创建映射到NVM设备文件上的只读或读写MappedByteBuffer时,可以使用上述的枚举值。如果这些标志在不支持NVM设备文件的平台上传递,程序会抛出UnsupportedOperationException异常。在受支持的平台上,仅当目标FileChannel实例是从通过NVM设备打开的派生文件时,才能传递这些参数。在任何其他情况下,都会抛出IOException异常。 发布BufferPoolMXBean,用于跟踪MappedByteBuffer统计信息 JEP 358: 友好的空指针异常 精确描述哪个变量为null,提高JVM生成的NullPointerException的可用性。 动机 每个Java开发人员都遇到过NullPointerException(NPE)问题。NPE几乎可以出现在程序的任意位置,因此尝试捕获和修复它们是不可能的。下面的代码: a.i = 99; JVM会打印出方法名、文件名和NPE异常的行数: Exception in thread "main" java.lang.NullPointerException at Prog.main(Prog.java:5) 使用这个错误报告,开发人员可以定位到a.i = 99;并推断对象a是null。但是对于更复杂的代码,不使用调试器就无法确定哪个变量为空。假设下面的代码中出现了一个NPE: a.b.c.i = 99; 仅仅使用文件名和行数,并不能精确定位到哪个变量为null,是a、b还是c? 访问数组也会发生类似的问题。假设此代码中出现一个NPE: a[i][j][k] = 99; 文件名和行号不能精确指出哪个数组组件为空。是a还是a[i]或a[i][j]? 一行代码可能包含多个访问路径,每个访问路径都可能是NPE的来源。假设此代码中出现一个NPE: a.i = b.j; 文件名和行号并不能确定哪个对象为空,是a还是b? NPE也可能在方法调用中传递,看下面的代码: x().y().i = 99; 文件名和行号不能指出哪个方法调用返回null。是x()还是y()? 描述 JVM在程序调用空引用的位置抛出NPE异常,通过分析程序的字节码指令,JVM可以精确判断哪个变量为空,并在NPE中描述详细信息(根据源代码)。包含方法名、文件名和行号的null-detail消息将显示在JVM的消息中。 例如a.i = 99;的NPE异常可能是如下格式: Exception in thread "main" java.lang.NullPointerException: Cannot assign field "i" because "a" is null at Prog.main(Prog.java:5) 在更复杂的a.b.c.i = 99;语句中,NPE消息会包含导致空值的完整访问路径: Exception in thread "main" java.lang.NullPointerException: Cannot read field "c" because "a.b" is null at Prog.main(Prog.java:5) 同样,如果数组访问和赋值语句a[i][j][k] = 99;引发NPE: Exception in thread "main" java.lang.NullPointerException: Cannot load from object array because "a[i][j]" is null at Prog.main(Prog.java:5) 类似地,a.i = b.j;会引发NPE: Exception in thread "main" java.lang.NullPointerException: Cannot read field "j" because "b" is null at Prog.main(Prog.java:5) JEP 359: record(预览) 通过record增强Java编程语言。record提供了一种紧凑的语法来声明类,这些类是浅层不可变数据的透明持有者。 动机 我们经常听到这样的抱怨:“Java太冗长”、“Java规则过多”。首当其冲的就是充当简单集合的“数据载体”的类。为了写一个数据类,开发人员必须编写许多低价值、重复且容易出错的代码:构造函数、访问器、equals()、hashCode()和toString()等等。 尽管IDE可以帮助开发人员编写数据载体类的绝大多数编码,但是这些代码仍然冗长。 从表面上看,将Record是为了简化模板编码而生的,但是它还有“远大”的目标:modeling data as data(将数据建模为数据)。record应该更简单、简洁、数据不可变。 描述 record是Java的一种新的类型。同枚举一样,record也是对类的一种限制。record放弃了类通常享有的特性:将API和表示解耦。但是作为回报,record使数据类变得非常简洁。 一个record具有名称和状态描述。状态描述声明了record的组成部分。例如: record Point(int x, int y) { } 因为record在语义上是数据的简单透明持有者,所以记录会自动获取很多标准成员: 状态声明中的每个成员,都有一个 private final的字段; 状态声明中的每个组件的公共读取访问方法,该方法和组件具有相同的名字; 一个公共的构造函数,其签名与状态声明相同; equals和hashCode的实现; toString的实现。 限制 records不能扩展任何类,并且不能声明私有字段以外的实例字段。声明的任何其他字段都必须是静态的。 records类都是隐含的final类,并且不能是抽象类。这些限制使得records的API仅由其状态描述定义,并且以后不能被其他类实现或继承。 在record中额外声明变量 也可以显式声明从状态描述自动派生的任何成员。可以在没有正式参数列表的情况下声明构造函数(这种情况下,假定与状态描述相同),并且在正常构造函数主体正常完成时调用隐式初始化(this.x=x)。这样就可以在显式构造函数中仅执行其参数的验证等逻辑,并省略字段的初始化,例如: record Range(int lo, int hi) { public Range { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); } } JEP 361: Switch Expressions (标准) 扩展switch可以使其应用于语句或表达式。扩展switch使其可以用作语句或表达式,以便两种形式都可以使用传统的“case ... :”标签(带有贯穿)或“... ->”标签(不带有贯穿),还有另一个新语句,用于从switch表达式产生值。这些更改将简化日常编码,并为在交换机中使用模式匹配提供了方法。这些功能在JDK 12和JDK 13中是属于预览语言功能,在JDK 14中正式称为标准。 动机 当我们准备增强Java编程语言以支持模式匹配(JEP 305)时,现有switch语句的一些不规则性(长期以来一直困扰着用户)成为了障碍。下面的代码中,众多的break语句使代码变得冗长,这种“视觉噪声”通常掩盖了更多的错误。 switch (day) { case MONDAY: case FRIDAY: case SUNDAY: System.out.println(6); break; case TUESDAY: System.out.println(7); break; case THURSDAY: case SATURDAY: System.out.println(8); break; case WEDNESDAY: System.out.println(9); break; } 我们建议引入一种新形式的switch标签“case L ->”,以表示如果匹配标签,则只执行标签右边的代码。switch标签允许在每种情况下使用逗号分隔多个常量。现在可以这样编写以前的代码: switch (day) { case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); case TUESDAY -> System.out.println(7); case THURSDAY, SATURDAY -> System.out.println(8); case WEDNESDAY -> System.out.println(9); } switch标签“case L ->”右侧的代码被限制为表达式、代码块或throw语句。这样局部变量的范围在本块之内,而传统的switch语句局部变量的作用域是整个模块! switch (day) { case MONDAY: case TUESDAY: int temp = ... // The scope of 'temp' continues to the } break; case WEDNESDAY: case THURSDAY: int temp2 = ... // Can't call this variable 'temp' break; default: int temp3 = ... // Can't call this variable 'temp' } 许多现有的switch语句实质上是对switch表达式的模拟,其中每个分支要么分配给一个公共目标变量,要么返回一个值: int numLetters; switch (day) { case MONDAY: case FRIDAY: case SUNDAY: numLetters = 6; break; case TUESDAY: numLetters = 7; break; case THURSDAY: case SATURDAY: numLetters = 8; break; case WEDNESDAY: numLetters = 9; break; default: throw new IllegalStateException("Wat: " + day); } 上面的表述是复杂、重复且容易出错的。代码设计者的意图是为每天计算numLetters。这段代码可以改写成下面这段形式,更加清晰和安全: int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; }; 描述 1. “case L ->”标签 除了传统的“case L :”标签外,还定义了一种更简洁的形式:“case L ->”标签。如果表达式匹配了某个标签,则仅执行箭头右侧的表达式或语句;否则将不执行任何操作。 static void howMany(int k) { switch (k) { case 1 -> System.out.println("one"); case 2 -> System.out.println("two"); default -> System.out.println("many"); } } 下面的代码: howMany(1); howMany(2); howMany(3); 将会打印: one two many 2. Switch表达式 JDK 14扩展了switch语句,使其可以应用于表达式中。例如上述的howMany方法可以重写为如下形式: static void howMany(int k) { System.out.println( switch (k) { case 1 -> "one"; case 2 -> "two"; default -> "many"; } ); } 在通常情况下,switch表达式如下所示: T result = switch (arg) { case L1 -> e1; case L2 -> e2; default -> e3; }; 3. 通过yield产生值 大多数switch表达式在“case L->”标签的右侧都有一个表达式。如果需要一个完整的块,JDK 14引入了一个新的yield语句来产生一个值,该值成为封闭的switch表达式的值。 int j = switch (day) { case MONDAY -> 0; case TUESDAY -> 1; default -> { int k = day.toString().length(); int result = f(k); yield result; } }; JEP 362: 弃用Solaris和SPARC端口 不建议使用Solaris/SPARC,Solaris/x64和Linux/SPARC端口,以在将来的发行版中删除它们。 动机 放弃对这些端口的支持将使OpenJDK社区中的贡献者能够加速新功能的开发,这些新功能将推动平台向前发展。 JEP 363: 移除CMS垃圾收集器 移除CMS(Concurrent Mark Sweep)垃圾收集器。 动机 在两年多以前的JEP 291中,就已经弃用了CMS收集器,并说明会在以后的发行版中删除,以加快其他垃圾收集器的发展。在这段时间里,我们看到了2个新的垃圾收集器ZGC和Shenandoah的诞生,同时对G1的进一步改进。G1自JDK 6开始便成为CMS的继任者。我们希望以后现有的收集器进一步减少对CMS的需求。 描述 此更改将禁用CMS的编译,删除源代码中gc/cms目录的内容,并删除仅与CMS有关的选项。尝试使用命令-XX:+UseConcMarkSweepGC开启CMS会收到以下警告: Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option UseConcMarkSweepGC; \ support was removed in <version> VM将使用默认收集器继续执行。 JEP 364: macOS系统上的ZGC(实验) 将ZGC垃圾收集器移植到macOS。 动机 尽管我们希望需要ZGC可伸缩性的用户使用基于Linux的环境,但是在部署应用程序之前,开发人员通常会使用Mac进行本地开发和测试。 还有一些用户希望运行桌面应用程序,例如带有ZGC的IDE。 描述 ZGC的macOS实现由两部分组成: 支持macOS上的多映射内存。 ZGC设计大量使用彩色指针,因此在macOS上我们需要一种将多个虚拟地址(在算法中包含不同颜色)映射到同一物理内存的方法。我们将为此使用mach microkernel mach_vm_remap API。堆的物理内存在单独的地址视图中维护,在概念上类似于文件描述符,但位于(主要是)连续的虚拟地址中。该内存被重新映射到内存的各种ZGC视图中,代表了算法的不同指针颜色。 ZGC支持不连续的内存保留。在Linux上,我们在初始化期间保留16TB的虚拟地址空间。我们假设没有共享库将映射到所需的地址空间。在默认的Linux配置上,这是一个安全的假设。但是在macOS上,ASLR机制会侵入我们的地址空间,因此ZGC必须允许堆保留不连续。假设VM实现使用单个连续的内存预留,则共享的VM代码也必须停止。如此一来,is_in_reserved(),reserved_region()和base()之类的GC API将从CollectedHeap中删除。 JEP 365: Windows系统上的ZGC(实验) 将ZGC垃圾收集器移植到Windows系统上。 描述 ZGC的大多数代码库都是平台无关的,不需要Windows特定的更改。现有的x64负载屏障支持与操作系统无关,也可以在Windows上使用。需要移植的特定于平台的代码与如何保留地址空间以及如何将物理内存映射到保留的地址空间有关。用于内存管理的Windows API与POSIX API不同,并且在某些方面不太灵活。 Windows实现的ZGC需要进行以下工作: 支持多映射内存。 ZGC使用彩色指针需要支持堆多重映射,以便可以从进程地址空间中的多个不同位置访问同一物理内存。在Windows上,分页文件支持的内存为物理内存提供了一个标识(句柄),该标识与映射它的虚拟地址无关。使用此标识,ZGC可以将同一物理内存映射到多个位置。 支持将分页文件支持的内存映射到保留的地址空间。 Windows内存管理API不如POSIX的mmap/munmap灵活,尤其是在将文件支持的内存映射到以前保留的地址空间区域中时。为此,ZGC将使用Windows概念的地址空间占位符。 Windows 10和Windows Server版本1803中引入了占位符概念。不会实现对Windows较早版本的ZGC支持。 支持映射和取消映射堆的任意部分。 ZGC的堆布局与其动态调整堆页面大小(以及重新调整大小)相结合,需要支持映射和取消映射任意堆粒子。此要求与Windows地址空间占位符结合使用时,需要特别注意,因为占位符必须由程序显式拆分/合并,而不是由操作系统自动拆分/合并(如在Linux上)。 支持提交和取消提交堆的任意部分。 ZGC可以在Java程序运行时动态地提交和取消提交物理内存。为了支持这些操作,物理内存将被划分为多个分页文件段并由其支持。每个分页文件段都对应一个ZGC堆粒度,并且可以独立于其他段进行提交和取消提交。 JEP 366: 弃用Parallel Scavenge和Serial Old垃圾收集算法的组合 弃用Parallel Scavenge和Serial Old垃圾收集算法的组合。 动机 有一组GC算法的组合很少使用,但是维护起来却需要巨大的工作量:并行年轻代GC(ParallelScavenge)和串行老年代GC(SerialOld)的组合。用户必须使用-XX:+UseParallelGC -XX:-UseParallelOldGC来启用此组合。 这种组合是畸形的,因为它将并行的年轻代GC算法和串行的老年代GC算法组合在一起使用。我们认为这种组合仅在年轻代很多、老年代很少时才有效果。在这种情况下,由于老年代的体积较小,因此完整的收集暂停时间是可以接受的。但是在生产环境中,这种方式是非常冒险的:年轻代的对象容易导致OutOfMemoryException。此组合的唯一优势是总内存使用量略低。我们认为,这种较小的内存占用优势(最多是Java堆大小的约3%)不足以超过维护此GC组合的成本。 描述 除了弃用选项组合-XX:+UseParallelGC -XX:-UseParallelOldGC外,我们还将弃用选项-XX:UseParallelOldGC,因为它唯一的用途是取消选择并行的旧版GC,从而启用串行旧版GC。 因此,任何对UseParallelOldGC选项的明确使用都会显示弃用警告。 JEP 367: 移除Pack200工具和API 删除java.util.jar软件包中的pack200和unpack200工具以及Pack200 API。这些工具和API在Java SE 11中已经被注明为不推荐,并明确打算在将来的版本中删除它们。 动机 Pack200是JSR 200在Java SE 5.0中引入的一种JAR文件压缩方案。其目标是“减少Java应用程序打包,传输和交付的磁盘和带宽需求”。开发人员使用一对工具pack200和unpack200压缩和解压缩其JAR文件。在java.util.jar包中提供了一个API。 删除Pack200的三个原因: 从历史上看,通过56k调制解调器缓慢下载JDK阻碍了Java的采用。 JDK功能的不断增长导致下载量膨胀,进一步阻碍了采用。使用Pack200压缩JDK是缓解此问题的一种方法。但是,时间已经过去了:下载速度得到了提高,并且JDK 9为Java运行时(JEP 220)和用于构建运行时的模块(JMOD)引入了新的压缩方案。因此,JDK 9和更高版本不依赖Pack200。 JDK 8是在构建时用pack200压缩的最新版本,在安装时用unpack200压缩的最新版本。总之,Pack200的主要使用者(JDK本身)不再需要它。 除了JDK,Pack200还可以压缩客户端应用程序,尤其是applet。某些部署技术(例如Oracle的浏览器插件)会自动解压缩applet JAR。但是,客户端应用程序的格局已经改变,并且大多数浏览器都放弃了对插件的支持。因此,Pack200的主要消费者类别(在浏览器中运行的小程序)不再是将Pack200包含在JDK中的驱动程序。 Pack200是一项复杂而精致的技术。它的文件格式与类文件格式和JAR文件格式紧密相关,二者均以JSR 200所无法预料的方式发展。(例如,JEP 309向类文件格式添加了一种新的常量池条目,并且JEP 238在JAR文件格式中添加了版本控制元数据。)JDK中的实现是在Java和本机代码之间划分的,这使得维护变得很困难。 java.util.jar.Pack200中的API不利于Java SE平台的模块化,从而导致在Java SE 9中删除了其四种方法。总的来说,维护Pack200的成本是巨大的,并且超过了其收益。包括在Java SE和JDK中。 描述 JDK最终针对的JDK功能发行版中将删除以前用@Deprecated(forRemoval = true)注解的java.base模块中的三种类型: java.util.jar.Pack200 java.util.jar.Pack200.Packer java.util.jar.Pack200.Unpacker 包含pack200和unpack200工具的jdk.pack模块先前已使用@Deprecated(forRemoval = true)进行了注解,并且还将在此JEP最终针对的JDK功能版本中将其删除。 JEP 368: Text Blocks(二次预览) Java语言增加文本块功能。文本块是多行字符串文字,能避免大多数转义。 动机 在Java中,HTML, XML, SQL, JSON等字符串对象都很难阅读和维护。 1. HTML 使用one-dimensional的字符串语法: String html = "<html>\n" + " <body>\n" + " <p>Hello, world</p>\n" + " </body>\n" + "</html>\n"; 使用two-dimensional文本块语法: String html = """ <html> <body> <p>Hello, world</p> </body> </html> """; 2. SQL 使用one-dimensional的字符串语法: String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" + "WHERE `CITY` = 'INDIANAPOLIS'\n" + "ORDER BY `EMP_ID`, `LAST_NAME`;\n"; 使用two-dimensional文本块语法: String query = """ SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB` WHERE `CITY` = 'INDIANAPOLIS' ORDER BY `EMP_ID`, `LAST_NAME`; """; 3. 多语言示例 使用one-dimensional的字符串语法: ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); Object obj = engine.eval("function hello() {\n" + " print('\"Hello, world\"');\n" + "}\n" + "\n" + "hello();\n"); 使用two-dimensional文本块语法: ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); Object obj = engine.eval(""" function hello() { print('"Hello, world"'); } hello(); """); 描述 文本块是Java语言的新语法,可以用来表示任何字符串,具有更高的表达能力和更少的复杂度。 文本块的开头定界符是由三个双引号字符(""")组成的序列,后面跟0个或多个空格,最后跟一个行终止符。内容从开头定界符的行终止符之后的第一个字符开始。 结束定界符是三个双引号字符的序列。内容在结束定界符的第一个双引号之前的最后一个字符处结束。 与字符串文字中的字符不同,文本块的内容中可以直接包含双引号字符。允许在文本块中使用\",但不是必需的或不建议使用。 与字符串文字中的字符不同,内容可以直接包含行终止符。允许在文本块中使用\n,但不是必需或不建议使用。例如,文本块: """ line 1 line 2 line 3 """ 等效于字符串文字: "line 1\nline 2\nline 3\n" 或字符串文字的串联: "line 1\n" + "line 2\n" + "line 3\n" JEP 370: 外部存储器API(孵化) 引入一个API,以允许Java程序安全有效地访问Java堆之外的外部内存。 动机 许多Java的库都能访问外部存储,例如Ignite、mapDB、memcached及Netty的ByteBuf API。这样可以: 避免垃圾回收相关成本和不可预测性 跨多个进程共享内存 通过将文件映射到内存中来序列化、反序列化内存内容。 但是Java API却没有提供一个令人满意的访问外部内存的解决方案。 Java 1.4中引入的ByteBuffer API允许创建直接字节缓冲区,这些缓冲区是按堆外分配的,并允许用户直接从Java操作堆外内存。但是,直接缓冲区是有限的。 开发人员可以从Java代码访问外部内存的另一种常见途径是使用sun.misc.Unsafe API。Unsafe有许多公开的内存访问操作(例如Unsafe::getInt和putInt)。使用Unsafe访问内存非常高效:所有内存访问操作都定义为JVM内在函数,因此JIT会定期优化内存访问操作。然而根据定义,Unsafe API是不安全的——它允许访问任何内存位置(例如,Unsafe::getInt需要很长的地址)。如果Java程序了访问某些已释放的内存位置,可能会使JVM崩溃。最重要的是,Unsafe API不是受支持的Java API,并且强烈建议不要使用它。 虽然也可以使用JNI访问内存,但是与该解决方案相关的固有成本使其在实践中很少适用。整个开发流程很复杂,因为JNI要求开发人员编写和维护C代码段。 JNI本质上也很慢,因为每次访问都需要Java到native的转换。 在访问外部内存时,开发人员面临一个难题:应该使用安全但受限(可能效率较低)的方法(例如ByteBuffer),还是应该放弃安全保证并接受不受支持和危险的Unsafe API? 该JEP引入了受支持的,安全且有效的外部内存访问API。并且设计时就充分考虑了JIT优化。 描述 外部存储器访问API引入了三个主要的抽象:MemorySegment,MemoryAddress和MemoryLayout。 MemorySegment用于对具有给定空间和时间范围的连续内存区域进行建模。可以将MemoryAddress视为段内的偏移量,MemoryLayout是内存段内容的程序描述。 可以从多种来源创建内存段,例如本机内存缓冲区,Java数组和字节缓冲区(直接或基于堆)。例如,可以如下创建本机内存段: try (MemorySegment segment = MemorySegment.allocateNative(100)) { ... } 上述代码将创建大小为100字节的,与本机内存缓冲区关联的内存段。 内存段在空间上受限制;任何试图使用该段来访问这些界限之外的内存的尝试都会导致异常。正如使用try-with-resource构造所证明的那样,片段在时间上也是有界的。也就是说,它们已创建,使用并在不再使用时关闭。关闭段始终是一个显式操作,并且可能导致其他副作用,例如与该段关联的内存的重新分配。任何访问已关闭的内存段的尝试都将导致异常。空间和时间安全性检查对于确保内存访问API的安全性至关重要。 通过获取内存访问var句柄可以取消引用与段关联的内存。这些特殊的var句柄具有至少一个强制访问坐标,类型为MemoryAddress,即发生取消引用的地址。它们是使用MemoryHandles类中的工厂方法获得的。要设置本机段的元素,我们可以使用如下所示的内存访问var句柄: VarHandle intHandle = MemoryHandles.varHandle(int.class); try (MemorySegment segment = MemorySegment.allocateNative(100)) { MemoryAddress base = segment.baseAddress(); for (int i = 0 ; i < 25 ; i++) { intHandle.set(base.offset(i * 4), i); } } 参考引用 本文同步至: https://waylau.com/jdk-14-released/ https://openjdk.java.net/projects/jdk/14/

优秀的个人博客,低调大师

还在使用 SVN 的企业,如何快速迁移到 Gitee

前言 2000 年 CollabNet 创建了 Subversion 项目,一晃 SVN 已经诞生 20 年了,截至 r1873568 SVN 主分支共有 59674 次提交,32 个开发者,288 次发布,2005 年 Linus Torvalds 创建了 Git,截至 de93cc14ab7e8db7645d8dbe4fd2603f76d5851f,git 主分支共有 58209 次提交,1343 个贡献者,742 次发布,诸如 Google,Microsoft,Facebook 这样的巨无霸公司都在使用 Git,Git 主要开发者来自 Google 和 Microsoft。 人多力量大,众人拾柴火焰高,有钱能使鬼推磨,贡献越多码越好,我们可以看到 SVN 只是缓慢变好,而 Git 却在飞速增强,到了今天为什么还不从 SVN 迁移到 Git? Git 与 SVN 的比较 Git 是最流行的分布式版本控制系统,而 SVN 是集中式版本控制系统,顾名思义,SVN 的存储库将存储在中央服务器,而 Git 的存储库是存储在本地,当网络连接断开后,SVN 便无法进行提交,使用 Git 的开发者则可以先将代码提交到本地存储库,待网络恢复后再推送到远程服务器。 特征 Git Subversion 分类 分布式版本控制系统 集中式版本控制系统 许可协议 GPLv2 Apache 基金会 Software Freedom Conservancy Apache 交流会议 Git Merge 技术资讯 Git Rev News 分支 轻量级分支(引用) 复杂的分支模型,创建分支缓慢 访问控制 通常无细粒度权限控制 目录级别的权限控制 学习难度 及其简单 简单 Gitee 的功能 用户在使用 SVN 时,通常感到舒适的功能有部分检出,目录权限控制等等,并一直以此为理由否定 Git 的进步,随着 Git 的不断增强,Gitee 开发者的不断努力,Gitee 逐渐拥有了这些功能。 2019 年 5 月底,Gitee 新增只读目录支持:SVN 的文件和目录只读特性,能否在 Git 也实现? 并且,我写了一篇文章介绍如何实现 Git 目录权限控制。 2020 年 1 月 17 日,码云目前已经初步支持 Git 部分克隆,结合部分克隆和稀疏检出能够提供比 SVN 更好的部分检出体验。 Gitee 除了在 Git 功能上推陈出新,还在团队协作,企业管理上增加了很多功能,自定义权限管理更切合企业实际,任务,里程碑,成员周报能够让开发者异地完成诸多任务,并被考核。 2019 年度疫情爆发以来,各地交通管制,返程复工有诸多不便,使用 Gitee 远程工作正当其时,为什么还不从 SVN 迁移到 Gitee 呢? 将 SVN 存储库迁移到 Gitee 企业只需要在 Gitee 上创建空存储库,然后将 SVN 存储库转换成 Git 存储库推送到 Gitee,便完成了向 Gitee 的迁移。 使用 git svn 工具转换 将 SVN 存储库转换成 Git 存储库非常简单,使用 git 自带的命令便可以完成: # convert repo to git repo gitsvnclonehttps://example.io/path/svn/repo-Ttrunk-bbranches-ttags gitremoteaddgitee git@gitee.com:example/name.git gitpush-ugitee--all 如果你以后无需追踪原有的 SVN 存储库,可以在 Push 之前运行: git branch -m trunk master 当存储库越来越大时,git svn 的缺陷便很明显了,转换耗时比较长,这也是 GCC 从 SVN 转成 Git 反反复复花了好几年的原因。 使用 svn2git(ruby) 转换 在 Github 上有个实用工具 svn2git,这个工具主要是简化了转换流程: sudo gem install svn2git svn2git http://svn.example.com/path/to/repo 这个工具能够提供更好的提交日志,唯一遗憾的是,自 2016 年以来便不在更新。 使用 svn-all-fast-export/svn2git 转换 KDE 的开发者开发了 svn-all-fast-export/svn2git 这个工具在服务器上将 SVN 存储库转换成 git 存储库,由于省去网络传输和检出,速度要远胜于 git svn/svn2git(ruby)。 KDE 开发者撰写了使用示例:UsingSvn2Git,这一工具使用难度较高,需要创建规则文件,如果存储库较小,不建议使用此类工具。 create repository kdelibs match /trunk/KDE/kdelibs/ min revision 123453 max revision 456789 repository kdelibs branch master end match end repository 使用 git-svn-fast-import 转换 Gitee 还移植了一个 SVN to Git 的工具 git-svn-fast-import,这个转换又快又简单: $ mkdir -p repo.git && cd repo.git $ git init $ git-svn-fast-import --stdlayout -r 0:100000 /path/to/svnrepo progress Skipped revision 0 progress Imported revision 1 progress Imported revision 2 progress Imported revision 3 ... progress Imported revision 99999 progress Imported revision 100000 Gitee 开发者曾用此工具为某私有化客户将存储库从 SVN 转到 Git。 简易 Git 命令指南 Git 任务 说明 命令 配置用户名 在创建提交时需要配置用户名 git config --global user.email 'name@email.io'; git config --global user.name 'Your Name' 初始化存储库 git init 克隆存储库 本地存储库 git clone /path/to/local/repo SSH git clone git@gitee.com:example/example.git HTTPS git clone https://gitee.com/example/example.git 查看本地状态 git status 查看本地更改 git diff 添加修改 比如新增或者增加了文件 A.txt git add A.txt 提交更改 git commit -m "Add a new file 'A.txt'" 推送到 Gitee git push 创建分支 git checkout -b NewBranchName 更多的命令可以访问 ProGit 或者运行 git help command 查看特定子命令的帮助信息。 在 Gitee 上使用 SVN 功能 随着开发者投入的逐步减少,因此使用 SVN 接入 Gitee 并不被提倡。 但是,如果你仍然想要在迁移到 Gitee 后,使用落后的 SVN,你可以在项目设置页面打开 SVN,然后使用: svn co svn+ssh://gitee.com/example/repo 这将使用 SVN Over SSH 的方式访问远程存储库,只需要配置好 SSH 公钥,便可免密使用 SVN 协议访问远程 Git 存储库。 更多的 Gitee SVN 可以访问:码云 SVN 支持 总结 开发者为开发者,Gitee 不断改进 Git 的体验,愿更多的企业从 SVN 迁移到 Gitee,享受更好的体验。

优秀的个人博客,低调大师

不加班的秘诀:如何通过AOE快速集成NCNN?

直接集成NCNN的缺点 直接集成NCNN相对麻烦,为SqueezeNet接入NCNN,把相关的模型文件,NCNN的头文件和库,JNI调用,前处理和后处理相关业务逻辑等。把这些内容都放在SqueezeNet Sample工程里。这样简单直接的集成方法,问题也很明显,和业务耦合比较多,不具有通用性,前处理后处理都和SqueezeNcnn这个Sample有关,不能很方便地提供给其他业务组件使用。深入思考一下,如果我们把AI业务,作为一个一个单独的AI组件提供给业务的同学使用,会发生这样的情况: 每个组件都要依赖和包含NCNN的库,而且每个组件的开发同学,都要去熟悉NCNN的接口,写C的调用代码,写JNI。所以我们很自然地会想到要提取一个NCNN的组件出来,提取以后如图所示: AOE SDK里的NCNN组件 有了AOE SDK,就可以使操作更加简便。在AOE开源SDK里,我们提供了NCNN组件,下面我们从4个方面来讲一讲NCNN组件: 1.NCNN组件的设计2.对SqueezeNet Sample的改造3.应用如何接入NCNN组件4.对NCNN组件的一些思考 NCNN组件的设计 NCNN组件的设计理念是组件里不包含具体的业务逻辑,只包含对NCNN接口的封装和调用。具体的业务逻辑,由业务方在外部实现。在接口定义和设计上,我们参考了TF Lite的源码和接口设计。目前提供的对外调用接口,如下图: // 加载模型和param void loadModelAndParam(...) // 初始化是否成功 boolean isLoadModelSuccess() // 输入rgba数据 void inputRgba(...) // 进行推理 void run(...) // 多输入多输出推理 void runForMultipleInputsOutputs(...) // 得到推理结果 Tensor getOutputTensor(...) // 关闭和清理内存 void close() 而机智骚年本人,用的是这个: ├── AndroidManifest.xml ├── cpp │ └── ncnn │ ├── c_api_internal.h │ ├── include │ ├── interpreter.cpp │ ├── Interpreter.h │ ├── jni_util.cpp │ ├── jni_utils.h │ ├── nativeinterpreterwrapper_jni.cpp │ ├── nativeinterpreterwrapper_jni.h │ ├── tensor_jni.cpp │ └── tensor_jni.h ├── java │ └── com │ └── didi │ └── aoe │ └── runtime │ └── ncnn │ ├── Interpreter.java │ ├── NativeInterpreterWrapper.java │ └── Tensor.java └── jniLibs ├── arm64-v8a │ └── libncnn.a └── armeabi-v7a └── libncnn.a ●Interpreter,提供给外部调用,提供模型加载,推理这些方法。 ●NativeInterpreterWrapper是具体的实现类,里面对native进行调用。 ●Tensor,主要是一些数据和native层的交互。 AOE NCNN用的好,任务完成早,奥秘在此。 1.支持多输入多输出。 2.使用ByteBuffer来提升效率。 3.使用Object作为输入和输出(实际支持了Bytebuffer和多维数据组) 光说不练假把式,AOE NCNN的实现过程,且听我细细道来。 如何支持多输入多输出 为了支持多输入和多输出,我们在Native层创建了一个Tensor对象的列表,每个Tensor对象里保存了相关的输入和输出数据。Native层的Tensor对象,通过tensor_jni提供给java层调用,java层维护这个指向native层tensor的“指针”地址。这样在有多输入和多输出的时候,只要拿到这个列表里的对应的Tensor,就可以就行数据的操作了。 ByteBuffer的使用 ByteBuffer,字节缓存区处理子节的,比传统的数组的效率要高。DirectByteBuffer,使用的是堆外内存,省去了数据到内核的拷贝,因此效率比用ByteBuffer要高。 当然ByteBuffer的使用方法不是我们要说的重点,我们说说使用了ByteBuffer以后,给我们带来的好处: 1. 接口里的字节操作更加便捷,例如里面的putInt,getInt,putFloat,getFloat,flip等一系列接口,可以很方便的对数据进行操作。2. 和native层做交互,使用DirectByteBuffer,提升了效率。我们可以简单理解为java层和native层可以直接对一块“共享”内存进行操作,减少了中间的字节的拷贝过程。 如何使用Object作为输入和输出 目前我们只支持了ByteBuffer和MultiDimensionalArray。在实际的操作过程中,如果是ByteBuffer,我们会判断是否是direct buffer,来进行不同的读写操作。如果是MultiDimensionalArray,我们会根据不同的数据类型(例如int, float等),维度等,来对数据进行读写操作。 对SqueezeNet Sample的改造 集成AOE NCNN组件以后,让SqueezeNet依赖NCNN Module,SqueezeNet Sample里面只包含了模型文件,前处理和后处理相关的业务逻辑,前处理和后处理可以用java,也可以用c来实现,由具体的业务实现来决定。新的代码结构变得非常简洁,目录如下: ├── AndroidManifest.xml ├── assets │ └── squeeze │ ├── model.config │ ├── squeezenet_v1.1.bin │ ├── squeezenet_v1.1.id.h │ ├── squeezenet_v1.1.param.bin │ └── synset_words.txt └── java └── com └── didi └── aoe └── features └── squeeze └── SqueezeInterpreter.java ↑ Sample也适用于其他的AI业务组件对NCNN组件的调用。 应用如何接入NCNN组件对NCNN组件的接入,有两种方式 ●直接接入 ●通过AOE SDK接入 ▲两种接入方式比较: 不BATTLE了,我单方面宣布,AOE SDK完胜! 对NCNN组件的总结和思考 通过对NCNN组件的封装,现在业务集成NCNN更加快捷方便了。之前我们一个新的业务集成NCNN,可能需要半天到一天的时间。使用AOE NCNN组件以后,可能只需要1-2小时的时间。当然NCNN组件目前还存在很多不完善的地方,我们对NCNN还需要去加深学习和理解。后面会通过不断的学习,持续的对NCNN组件进行改造和优化。 - - - - - - - - - - - - - - - - - - - - - - - - - - - - A o E - - - - - - - - - - - - - - - - - - - - - - - - - - - -

优秀的个人博客,低调大师

CakePHP 4.0.0 RC1 发布,PHP 快速开发框架

CakePHP 4.0.0 的第一个候选版本发布了,更新内容如下: 新的route _path 格式已添加至Router::url() http 和控制台库已创建独立程序包 开发错误页面会在异常消息中保留内联代码高亮和换行符 已弃用 SecurityComponent,并由 FormProtectionComponent 代替 ConsoleErrorHandler 已移至 Error 包 Validation::time()现在接受微秒 为 PHP>7.3.0 添加了 SameSite cookie 支持 增加了用于 char,datetimefractional 和 timestampfractional 的新数据库类型 新增InstanceConfigTrait::getConfigOrFail() 新的 isLinkedTo 和 isNotLinkedTo 规则已添加到ORM\RulesChecker Date 和 FrozenDate 对象现在使用默认时区而不是 UTC 重构了 Controller action dispatching,使将来添加 DI 容器变得更加容易 Routing前缀现在是 PascalCased 而不是 under_scored 详情见更新说明: https://bakery.cakephp.org/2019/11/15/cakephp_400RC1_released.html

优秀的个人博客,低调大师

如何快速定位Android端GPU问题之工具介绍

GAPID GAPID是Google下的一个开源库,可用于记录发送给GPU的API调用及渲染状态检查,目前主要用于Android端,对OpenGL ES及Vulkan的支持最完善,使用此工具可以协助定位排查端上OpenGL ES的渲染效果、渲染状态及错误、渲染绑定的shader等资源,同时可以即时修改渲染每步中的变量值并查看新的渲染效果。 下载安装 下载地址:https://github.com/google/gapid/releases当前最新的稳定版本是1.6.1,也可以下载开发版1.7.0,注意开发版本更新比较频繁,也可能会出现运行过程中崩溃问题,建议使用稳定版本。下载后默认安装即可。 环境配置 启动界面如下图 选择Capture a new trace,如下图我的机器上的默认设置 注意:如果第一行Device无法显示,可以点击右侧图标刷

优秀的个人博客,低调大师

如何通过火焰图快速定位Cassandra性能瓶颈

运维大规模分布式系统的比较重要的一个挑战是可以有能力指出关键问题所在。在没有证据支持某种说法的情况下,当故障出现时,总是责怪组件(通常是数据库)的偶然性问题是很常见的一件事。我们已经讨论过监控工具、图形化输出、以及报警metric的重要性,并使用分布式tracing系统(比如zikin)去正确的辨别复杂系统的根源问题。 一旦你把问题缩小到一个单一的系统上面,你会怎么做?通常我们都会说这个会具体问题具体对待。有的问题是临时的,比如坏盘。有的问题却涉及到人为引入的变化,比如部署或者是错误的配置。这些都是有直接简单的解决办法:换盘或者是回滚部署。 但是如果出现的问题超出简单变化的范畴,那怎么办?到现在为止还没有提到的是规模增长造成的问题。规模可以成为另一个困难的问题,因为复现这个问题一般情况下是很微妙且复杂的。这些挑战有时是通过吞吐量(每秒

优秀的个人博客,低调大师

Jboot 2.2.2 发布,分布式开发更加简单快速

Jboot 是一个基于 JFinal、JFinal-Undertow、Dubbo 等开发的微服务框架,帮助开发者降低微服务开发门槛。同时完美支持在 idea、eclipse 下多 maven 模块,对java代码、html、css、js 等资源文件进行热加载,爽爽的开发。 Jboot v2.2.2 更新内容如下: 新增:新增 AopCache 工具类,方便直接操作 AOP 缓存 新增:Model 新增 findListByIds() 方法,用于更加多个ID查询多个Model 新增:CacheUtil 新增 use() 方法,方便通过 CacheUtil 对不同的缓存进行操作 优化:重构 Cache AOP 拦截器的包结构,使之更加合理 优化:优化 Cache 的相关工具类,删除多余的无用代码 优化:添加 Columns.EMPTY 变量,用于查询时在某些情况无需创建变量 优化:find 系列方法更好的答应 debug sql,同时删除 JbootModel 无需重写的某些方法 maven 依赖: <dependency> <groupId>io.jboot</groupId> <artifactId>jboot</artifactId> <version>2.2.2</version> </dependency> Hello World: @RequestMapping("/") public class HelloworldController extends JbootController { public void index(){ renderText("hello world"); } public static void main(String[] args){ JbootApplication.run(args); } }

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册