阿里妹导读
任何复杂的系统都可能因为一个小小的疏漏而无法运转,本文记录了一个困扰作者一个月的 bug 最终拨云见日的过程。
假设你是一个 Java 程序员,但你早已厌倦了什么 Java 8 什么 CMS GC 什么 SSM。
某天你心血来潮,在自己的小破开发机里装了最新版的 JDK,用上了潮到没边的 Shenandoah GC,抄起键盘起手就是 hello world 一把梭,结果发现你写的程序居然跑不了——甚至还把 Java 搞崩溃了,现场只剩下 log、coredump 和一地鸡毛。
你会怀疑是不是自己太长时间没接触 Java 新特性,居然写不出一个能跑的程序……
还是说,你会觉得是 Java 本身,甚至是编译 JVM 的那个编译器、OS、CPU 里出现了更离谱的 bug 呢?
我的日常工作之一是给 OpenJDK 上游修 bug。某天我百无聊赖,用最新 build 出来的 JDK 在 RISC-V 平台跑测试,发现 tier1 测试居然挂了一大片。
给不了解 OpenJDK 的朋友们解释一下:JDK 仓库公开的测试按重要程度分为 tier1 到 tier4 四个级别[1],tier1 是其中优先级最高的。如果 tier1 中有测试挂掉,说明目前的 JDK 即使是跑一些很常规的 Java 程序,也有很大概率出问题。像这种 tier1 一挂一大片的情况,基本说明目前这个 JDK 在某些功能上已经是个残废了。我之前还从来没见到过这样的现象,只觉得这背后必有蹊跷。当然,既然我们已经发现了这个问题,不如就地把它解决掉,顺便在等编译的时候带薪摸鱼。0x01 缩圈
考虑到 OpenJDK 的代码量相当之大,涵盖的内容相当之丰富,构建流程横跨 C、C++、Java 等多种语言,试图通过干瞪眼发现问题基本是不现实的。因此,我们需要从挂掉的测试出发,尝试更多触发错误的方式,通过进一步分析,尽可能缩小问题的范围。
首先,从测试结果来看,所有挂掉的测试都和 Shenandoah GC [2]有关。这其中,最简单的测试是 TestSmallHeap.java[3],它的实现只有短短五行:
public class TestSmallHeap {
public static void main(String[] args) throws Exception {
System.out.println("Hello World!");
}
}
这……这对吗?我没在开玩笑,在某些情况下,目前的这个 JDK 还真就连 hello world 都跑不起来。
当然,这个测试实际上检查的还是 GC,而不是 hello world。测试会把堆内存改到一个很小的值,比如 4MB,从而触发 GC——是的,Java 连输出 hello world 都需要 GC。如果这个测试崩掉,说明 Shenandoah GC 的实现很大概率是有问题的。为了验证这个猜想,我们可以挑几个挂掉的测试,加些参数重跑一下:
这基本确定了出问题的地方就在 Shenandoah GC 相关的实现里。其次,这个问题在除 RISC-V 之外的其他架构上也存在吗?我尝试在 x86-64 和 aarch64 上使用同样的源码构建了 JDK,并没有复现这个问题,这表明这个问题很可能是 RISC-V 实现特有的。此外,用来跑测试的是 release mode 构建的 JDK。经过一些尝试,我发现如果换到 fastdebug mode,问题就会消失。
这说明可能是 release mode 相比 fastdebug mode 在编译选项上的差异导致生成的 JDK 出现了问题。那么 fastdebug 和 release 的编译选项有哪些不同呢?直接看 JDK 的编译脚本基本就别想了,因为这个巨型项目里光 Makefile 就有几万行代码。一个最简单的办法是,使用 make -n 直接输出编译命令行,然后检查编译 JVM 核心组件的编译命令之间的差异:
--- release.log 2024-02-14 09:31:04
+++ fastdebug.log 2024-02-14 09:36:25
...
@@ -11,13 +11,13 @@
-fno-lifetime-dse
-Wno-format-zero-length -Wtype-limits -Wuninitialized
--DNDEBUG
--DPRODUCT
+-DASSERT
+-DCHECK_UNHANDLED_OOPS
-DTARGET_ARCH_riscv
-DINCLUDE_SUFFIX_OS=_linux
-DINCLUDE_SUFFIX_CPU=_riscv
...
对比发现,fastdebug 除了比 release 多调整了一些诸如 PRODUCT、ASSERT 这类控制调试的宏定义开关之外,还额外定义了 CHECK_UNHANDLED_OOPS 宏。后续测试表明,如果我们给 release 也定义这个宏,之前的问题就会消失。
那么问题来了,这个宏会影响哪些实现呢?在代码中搜索,我们发现它主要控制了 oopsHierarchy.hpp [4]中和 oop 相关的实现。在 HotSpot JVM 中,oop 指的是 “ordinary object pointer”[5],实际上就是一个指向 Java 对象的指针——更确切地说,oop 指向的是 Java 对象头部的元数据,其中包含了 mark word、类的描述信息等内容,之后才是对象本身的内容。这其中,mark word 里的某些位会被 GC 用来标记对象,所以这部分实现确实也能和 GC 扯上关系,很合理。默认情况下,oop 的定义就是简单的 C++ 指针:
typedef class oopDesc* oop;
而开启 CHECK_UNHANDLED_OOPS 之后,原本的指针定义会被换成一个重载了 ->、* 运算符的类:
class oop {
oopDesc* _o;
void check_oop() const { if (check_oop_function != nullptr && _o != nullptr) check_oop_function(_o); }
void on_usage() const { check_oop(); }
public:
oopDesc* obj() const { on_usage(); return _o; }
oopDesc* operator->() const { return obj(); }
operator oopDesc* () const { return obj(); }
...
};
这么做是为了在解引用指针时,插入额外的检查操作。然而,除非通过参数手动开启,这个检查永远不会执行。
那就奇怪了,这岂不是相当于,我们只要在每次 oop 解引用之前,都插入一个条件判断,这个问题就会消失?我们可以分别验证:- 开启 CHECK_UNHANDLED_OOPS**,但删除其中的所有检查:问题出现。**
我去,还真是,这也太怪了。那会是什么导致了这个问题呢?由于 include oopsHierarchy.hpp 的文件少说也有一百多个,每个文件里更是出现了不止一次解引用操作,此后的一段时间里,我始终一头雾水,同时一无所获。好在,虽然还是没能找到问题的原因,但通过之前的尝试,我们已经可以确定,这个问题:
除了分析测试结果和源码实现,还有一个更重要的工具我们目前没有用到——coredump。JVM 在崩溃时,会输出错误日志,同时生成问题现场的 coredump。通过分析 coredump,我们可以检查程序里每个线程的调用栈,以及程序执行时的寄存器、内存状态。
一般来说,对于简单的问题,只需要看一眼 coredump,一切都会迎刃而解。但坏就坏在这个问题不简单。从错误日志来看,导致 JVM 崩溃的直接原因几乎全是 SIGSEGV,即段错误[6],也就是说 JVM 此时访问了一个无效的内存地址。几个 coredump 都可以印证这一点,比如其中的一个:
#22 <signal handler called>
#23 Klass::is_instance_klass (this=0xb8547061) at src/hotspot/share/oops/klass.hpp:643
#24 oopDesc::is_instance (this=0xffcc0c08) at src/hotspot/share/oops/oop.inline.hpp:205
#25 ShenandoahMark::do_task<ShenandoahMarkRefsClosure, (StringDedupMode)0> ...
at src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp:73
...
backtrace 表明 JVM 在 Klass::is_instance_klass 里崩掉了,尝试看一下发生了什么:
(gdb) x/i $pc
=> 0x3fa0ac1fc4 <_ZN14ShenandoahMark7do_task...+108>: lw a4,12(a2)
(gdb) x/g $a2 + 12
0xb854706d: Cannot access memory at address 0xb854706d
看起来确实是段错误,出问题的内存地址也和错误日志记录的地址相符。但很奇怪的是:这明明是一条 lw 指令,内存地址的结尾为什么是 d?
在 RISC-V 中,lw 指令表示从内存中读出一个 4 字节的数据,而地址 0xb854706d 显然没有对齐 4 字节的边界。虽然 RISC-V 的 ISA 手册提到[7],处理器在实现上可以选择支持非对齐访存,但通常为了提升性能和兼容性,编译器(包括 JVM)不会生成非对齐的访存。而在另一个 coredump 中,出问题的位置却和上一个完全不同。
#23 <signal handler called>
#24 0x0000003f87bdce7c in ?? ()
通常,如果 JVM 的 coredump 里出现了问号,这说明程序正在执行 Java 方法在运行时生成的代码,而不是 C/C++ 代码。错误日志里的 stack trace 表明确实如此:
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
j java.util.ArrayList.add(Ljava/lang/Object;[Ljava/lang/Object;I)V+0 java.base@23
j java.util.ArrayList.add(Ljava/lang/Object;)Z+20 java.base@23
j java.lang.invoke.InvokerBytecodeGenerator.classData(Ljava/lang/Object;)Ljava/lang/String;+165 java.base@23
...
检查当前指令所做的操作:
(gdb) x/i $pc
=> 0x3f87bdce7c: ld a0,208(a0)
(gdb) x/g $a0 + 208
0x866e623c: Cannot access memory at address 0x866e623c
和前一个 coredump 类似,程序使用 ld 访问了一个无效的内存地址,同时这个地址也没有对齐 8 字节的边界。
如果说前两个 coredump 中,程序访问的地址看起来还像那么回事(虽然我们一眼就能看出这些地址显然不符合 64-bit Linux 通常的虚拟地址格式),那第三个 coredump 中,程序访问的地址就非常奇怪了:
(gdb) x/i $pc
=> 0x3fb7385926 <_ZN7oopDesc4sizeEv+118>: ld a5,256(a5)
(gdb) x/g $a5 + 256
0x100: Cannot access memory at address 0x100
居然是 0x100。黑色幽默的是,这次的地址倒是按照 8 字节对齐了。
不过,我们还是能发现一些端倪。这个 coredump 的 backtrace 如下:
#8 <signal handler called>
#9 0x0000003fb7385926 in oopDesc::size_given_klass ... at src/hotspot/share/oops/oop.inline.hpp:196
#10 oopDesc::size_given_klass ... at src/hotspot/share/oops/oop.inline.hpp:156
...
#13 0x0000003fb78c3ac0 in ShenandoahMark::mark_loop_work<ShenandoahMarkRefsClosure, true, (StringDedupMode)0> ...
at src/hotspot/share/gc/shenandoah/shenandoahMark.cpp:178
...
虽然出问题的地方依然和前两个 coredump 完全不同,但我们注意到,其中出现了对 ShenandoahMark 类方法的调用,而第一个 coredump 的调用栈里也出现了类似的内容——这说明,JVM 正在执行 Shenandoah GC 的 concurrent marking 阶段。
作为一种低暂停时间的 GC,Shenandoah GC 工作流程里的大部分阶段都可以并行于 Java 应用执行,concurrent marking 阶段也不例外:在这个阶段中,GC 会从根集合(root set)出发,并行地扫描堆内存,标记可达的对象,为后续的内存清理工作收集信息。
因此,从 coredump 里,我们只能得出以下的结论:
以上种种迹象表明,我们遇到了一个比较奇怪的多线程同步问题。江湖上有这样一个传说:左转的红灯最难等,多线程的问题最难调。不过,江湖上还流传着这样的故事:一个 bug,只要你能稳定复现,你就已经成功 de 出了一半。脑海中不断回想这两个似乎有些矛盾的金句,我却站在原地,不知从何下手。
至此,我们彻底走进了一个死胡同。
分析到这里,我隐约觉得,这个问题除了和 JVM 相关,还很有可能和编译 JVM 的 GCC、运行 JVM 的操作系统,甚至于运行所有软件的那个 RISC-V CPU 有关系。为什么呢?从前两节我们可以得知:
这些猜想,有的很好验证,有的则相对比较难。比如 OS 和 CPU 的问题,我们只需要找一些其他的 RISC-V CPU 实现,再跑一些其他版本的 Linux,看能否复现同样的问题即可。
不过说实话,硬件实现不是那么好找,主要是还得多花钱,于是我把目光投向了 QEMU[9]。
作为一个老牌 CPU 模拟器,QEMU 的维护者数以千计,RISC-V target 更是备受关注,所以它的实现质量应该不会太差。参考网上随处可见的教程[10],我们很快就可以在最新的 QEMU 上跑起一个相对较新 RISC-V Debian:
debian@debian:~$ uname -a
Linux debian 6.6.13-riscv64 #1 SMP Debian 6.6.13-1 (2024-01-20) riscv64 GNU/Linux
然后,只要在这个环境里用 JDK 跑之前挂掉的测试即可。需要注意的是,QEMU 模拟执行的速度非常慢,跑个测试还凑合,但千万别在里面构建 JDK。
最后,果然不出所料:之前挂掉的测试,在 Debian + QEMU 里依然会挂。
虽然从逻辑上讲,不同的 OS + CPU 的组合如果不能复现问题,则一定可以说明这个问题和特定 OS + CPU 有关;反之,如果可以复现,则不能说明这个问题和特定实现一定无关,只能说无关的概率比较大。即便如此,这个现象依然给我们提供了一个参考方向:相比于 OS 和 CPU 出问题的可能,此后我们应该更专注于检查 JVM 和 GCC 的问题。Man, what can I say? OS and CPU out!
接下来,如何确定 JVM 和 GCC 到底谁出了问题呢?在 HotSpot JVM 的几百万行 C++ 代码里一行一行找,无异于大海捞针。当我们不知道问题到底出在什么地方的时候,我们可以用一个手段快速缩小问题的范围——二分,前提是我们假设问题只和某一个关键点相关。
在这个问题上,二分有两种思路:
反正现在也没什么别的好办法,不如先这么做试试看。二分提交记录很简单,git 为我们提供了 git bisect 命令,直接用即可。
一通操作后,我们发现,引入问题的提交是 287b243[11],这个提交从 JVM 的编译选项中删掉了 -fno-delete-null-pointer-checks。尝试在最新的 JDK 中添加这个编译选项,问题消失。好起来了,看起来确实很像 undefined behavior。
但我们不能通过简单地把这个选项加回去来修复这个 bug,因为我们还没有找到这个 bug 的具体原因——到底是哪段代码导致了这个问题?
这就需要二分确定出问题的文件。首先,我们知道,开启 CHECK_UNHANDLED_OOPS 宏时,问题消失,关闭宏,问题出现。而通过编译选项定义宏,会同步影响所有依赖 oopsHierarchy.hpp 的实现,进而影响依赖这些实现的其他实现……以此类推。
这不是我们希望看到的。因此,我们可以手动修改代码,只在依赖 oopsHierarchy.hpp 的文件中定义 CHECK_UNHANDLED_OOPS 宏,二分确定唯一的那个开了会导致问题的文件(并祈祷真的只有一个文件会导致问题)。然后,二分所有依赖这个文件的其他文件……直到找到最后的那个文件。这个过程比较机械,也比较折磨。
不过好在每次都可以排除一半的选项,JDK 也可以快速地增量编译,最主要的是每次你都会离真相更进一步,以及等编译的时候可以刷一会手机。终于,经过一通乱刨,我们发现,出问题的文件是 shenandoahHeap.cpp。太好了,胜利的曙光就在前方……吗?看着眼前这个 2000 多行代码的 C++ 文件,我陷入了沉默。
之前提到的二分定位法,只能确定出问题的文件,而没办法精确到文件里的特定函数,甚至特定的行。当然,你可以说,把这个文件里每个函数都分别拆分到不同的文件里,然后继续二分,不就能定位到函数级别了吗?
确实,但这有点麻烦,而且你也不好说你这么改之后会不会出别的问题,搞不好这样还需要修改编译脚本。
不过,还有另一种思路:既然我们已经能把问题的影响范围限制在一个文件里,那我们干脆直接对比修改这个文件前后生成的汇编代码,看哪些指令发生了变化,不就能一步到位,确定到出问题的行了吗?反正变化的地方只有这个文件,理论上不会影响太多东西。
说干就干,我们可以写一个脚本(或者偷懒让 AI 写):
最后的最后,我们发现,问题出在 ShenandoahHeap::conc_update_with_forwarded [12]方法中。有问题的实现是这样的:
0000000000ab3200 <_ZN14ShenandoahHeap26conc_update_with_forwardedI9narrowOopEEvPT_>:
...
ab3254: 0ff0000f fence
ab3258: 00c7d7b3 srl a5,a5,a2
ab325c: 00c75733 srl a4,a4,a2
ab3260: 1005a6af lr.w a3,(a1)
ab3264: 00f69563 bne a3,a5,ab326e
ab3268: 18e5a62f sc.w a2,a4,(a1)
ab326c: fa75 bnez a2,ab3260
ab326e: 0ff0000f fence
ab3272: 6422 ld s0,8(sp)
...
而正确的实现是这样的:
0000000000ab2ec0 <_ZN14ShenandoahHeap26conc_update_with_forwardedI9narrowOopEEvPT_>:
...
ab2d08: 00c7d7b3 srl a5,a5,a2
ab2d0c: 00c75733 srl a4,a4,a2
ab2d10: 2781 sext.w a5,a5
ab2d12: 2701 sext.w a4,a4
ab2d14: 0ff0000f fence
ab2d18: 1005a6af lr.w a3,(a1)
ab2d1c: 00f69563 bne a3,a5,ab2d26
ab2d20: 18e5a62f sc.w a2,a4,(a1)
ab2d24: fa75 bnez a2,ab2d18
ab2d26: 0ff0000f fence
ab2d2a: 6422 ld s0,8(sp)
...
发现了吗?正确实现里多了两条 sext.w 指令!如果我们用 16 进制编辑器修改正确的 libjvm.so,把这两个 sext.w 替换成空操作,然后再跑测试,就会出现相同的问题!
从这段代码要做的事情来看,少两条 sext.w 确实会出问题。这个循环实际上要做的是一个典型的 CAS(Compare-and-Swap)操作[13],也就是读出内存里的值,和一个给定值进行比较,如果一致,则将另一个新值写入内存,整个过程要原子地完成,不能被其他线程打断。事实上,以上那个错误的实现和 RISC-V ISA 手册里提供的示例[14]如出一辙,但其中忽略了一个重要的步骤:
ab3258: 00c7d7b3 srl a5,a5,a2
ab325c: 00c75733 srl a4,a4,a2
ab3260: 1005a6af lr.w a3,(a1)
ab3264: 00f69563 bne a3,a5,ab326e
根据 ISA 手册,lr.w 返回的结果,即 a3 里的内容,是符号扩展的;之后的 bne 指令将 a3 与 a5 进行了比较;而 a5 的值又来自于之前的 srl 指令,即逻辑右移——众所周知,逻辑右移会将移动后多出来的高位全部写成 0。
也就是说,如果很不幸,内存里数据的第 31 位是 1,则 a3 里的高 32 位全为 1。而在 a2 不为 0 的情况下,a5 的最高几位永远不可能为 1,此时,bne 指令的条件一定会成立,这和 CAS 操作的初衷相悖。当 CAS 操作实现错误时,线程之间自然会出现同步问题,这与之前我们观测到的现象相符。
找到了问题所在的位置,我们就可以尝试在 GCC 上复现问题了。HotSpot JVM 中,出问题的代码简化后如这个链接[15]所示。或者,我们还可以进一步简化:
void foo(uint32_t *p) {
uintptr_t x = *(uintptr_t *)p;
uint32_t e = !p ? 0 : (uintptr_t)p >> 1;
uint32_t d = x;
__atomic_compare_exchange(p, &e, &d, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
}
对应的 Compiler Explorer 链接请看这里[16]。
令人意外的是,这段代码中并没有出现 undefined behavior,但 GCC 生成的结果却出现了问题。与之相比,Clang 生成的结果看起来非常正常,并没有出现少一条 sext.w 的情况。
此外,根据我们之前的排查,在添加 -fno-delete-null-pointer-checks 选项之后,GCC 也可以生成正确的代码。因此,这个问题很明显是 GCC 编译器中的 bug 导致的,卧底居然是 GCC!想不到你个浓眉大眼的也叛变了。
至于具体是什么原因导致 GCC 出现了这样的 bug,因为我对 GCC 的代码并不熟悉,所以没法给出一个确切的解释,估计是后端代码生成的逻辑出了什么问题。专业的事情就要交给专业的人来解决,于是,我火速给 GCC 提交了一个 bug,见 Bug 114130[17]。GCC 这边也很给力,转眼就把 bug 修好了,前后只用了一天多点。皆大欢喜,接下来我们看看 JDK 这边的问题要怎么修。
在 HotSpot 的代码中,CAS 操作的实现和操作系统与 CPU 都相关;具体到 Linux + RISC-V 的情况,这部分实现位于 atomic_linux_riscv.hpp[18] 文件的 142-166 行。这个实现基本就是 GCC 内置函数 __atomic_compare_exchange[19] 的封装,篇幅所限,具体代码这里就不贴了。在 GCC 的 __atomic_compare_exchange 出 bug 的情况下,我们需要用内联汇编重新写一个相同的实现,核心代码如下:
__asm__ __volatile__ (
"1: lr.w %0, %2 \n\t"
" bne %0, %3, 2f \n\t"
" sc.w %1, %4, %2 \n\t"
" bnez %1, 1b \n\t"
"2: \n\t"
: /*%0*/"=&r" (old_value), /*%1*/"=&r" (rc_temp), /*%2*/"+A" (*dest)
: /*%3*/"r" ((int64_t)(int32_t)compare_value), /*%4*/"r" (exchange_value)
: "memory" );
可以看到,核心的四条汇编代码和之前我们反汇编得到的结果一致,但这里的重点在于对 compare_value 的处理:我们首先把它强制转换到 int32_t,接着立即将其转换到 int64_t。这样处理之后,GCC 会自行生成一个符号扩展操作,对应到最终的汇编代码里,就是多了一个 sext.w。此后,bne 将其与同样进行了符号扩展的 lr.w 的结果进行比较时,就不会出问题了。修改完成,编译测试通过,愉快地给 OpenJDK 提交一个 pull request:JDK-8326936/PR18039[20]。和 reviewer 进行简短而友好的交流之后,这个 patch 就被成功 merge 到了 JDK 中。完美谢幕!0x08 复盘
在知道了问题的原因后,我们可以用最开始观察到的现象一一验证,发现一切都是能说得通的:
而我们能在迷雾中一步步前行,最终发掘问题的真相,则少不了科学的方法论。我面对这个问题,采取了如下思路:
当然,上述思路仅供参考,如果你有更好的想法,欢迎在评论区讨论。经此一役,我们明白了,任何复杂的系统都可能因为一个小小的疏漏而无法运转,任何复杂的问题都可以通过抽丝剥茧进而各个击破,任何我们常规认知里总是正确的软件也都难免在一些犄角旮旯里存在致命问题。然而,关关难过关关过,步步难行步步行,至少从软件工程的角度出发,任何问题,经过一通昏天黑地的 debug 之后,最终几乎都能被妥善解决。最后,祝大家每次遇到奇怪的 bug 时,都能一眼看穿事情的真相——我们通常把这种人叫做,一眼盯真。
参考链接:
本文由微信公众号阿里云开发者原创,哈喽比特收录。
文章来源:https://mp.weixin.qq.com/s/_h4a7TbzVPN0PDJfAp3Olg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。