听说你没法在JRE中使用arthas?不,你可以

本文是《容器中的Java》系列文章之 5/n ,欢迎关注后续连载 :) 。

之前经常遇到的问题是,排查问题需要挂arthas,但客户用的是JRE,没法挂载arthas。就只能让客户更换成JDK,再重新部署、排查问题。

很多有用的现场,在这个过程中也会丢失,最终导致问题排查效率降低。于是就探索了下如何在JRE环境中,使用artahs。

复现问题

如果一个Bug 没法复现,研发大概率是无法修复的。 —— by 网友

我们写一个Java例子和Dockerfile:

./src/main/java/Main.java
1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) throws Exception {
while (true) {
System.out.println("hello!");
Thread.sleep(30 * 1000);
}
}
}
./Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM openjdk:8-jdk-alpine as builder
COPY ./ /app
WORKDIR /app/src/main/java/
# 编译java文件
RUN javac Main.java

# 运行时容器使用JRE
FROM openjdk:8-jre-alpine
RUN apk add bash curl busybox-extras
WORKDIR /app/src/main/java/
# 将arthas copy 到容器中
COPY --from=hengyunabc/arthas:latest /opt/arthas /opt/arthas
COPY --from=builder /app/src/main/java/ /app/src/main/java/
CMD ["java", "Main"]

构建并正常启动应用,并尝试用arthas attach,此处为了便于了解原理,我们使用as.sh来执行:

阅读更多

为什么在容器中1号进程挂不上arthas?

本文是《容器中的Java》系列文章之 4/n ,欢迎关注后续连载 :) 。

最近在容器环境中,发现在Java进程是1号进程的情况下,无法使用arthas,提示AttachNotSupportedException: Unable to get pid of LinuxThreads manager thread。具体操作和报错如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.5.6
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 1 com.alibabacloud.mse.demo.ZuulApplication
1
[INFO] arthas home: /home/admin/.opt/ArmsAgent/arthas
[INFO] Try to attach process 1
[ERROR] Start arthas failed, exception stack trace:
com.sun.tools.attach.AttachNotSupportedException: Unable to get pid of LinuxThreads manager thread
at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:86)
at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:78)
at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:250)
at com.taobao.arthas.core.Arthas.attachAgent(Arthas.java:117)
at com.taobao.arthas.core.Arthas.<init>(Arthas.java:27)
at com.taobao.arthas.core.Arthas.main(Arthas.java:166)
[INFO] Attach process 1 success.

之前也遇到过,总是调整了下镜像,让Java进程不是1号进程就可以了。但这个不是长久之计,还是要抽时间看下这个问题。

复现问题

我们创建如下项目,来复现这个问题:

./src/main/java/Main.java
1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) throws Exception {
while (true) {
System.out.println("hello!");
Thread.sleep(30 * 1000);
}
}
}
./Dockerfile
1
2
3
4
5
FROM openjdk:8u212-jdk-alpine
COPY ./ /app
WORKDIR /app/src/main/java/
RUN javac Main.java
CMD ["java", "Main"]

然后正常启动应用,并尝试用arthas,或者jstack:

阅读更多

Z-Library挂了?怎么办?

Z-Library

Z-Library 是一个免费下载电子书的网站。不过最近由于Z-Library的域名被查封,所以现在没法访问了。

Z-Library 刚开始的时候是 LibGen 的镜像站点,正因如此,Z-Library 上的大部分图书,都能在 LibGen找到;但有一些书是用户自己上传到 Z-Library 的,所以就没法在 LibGen 找到了。

这种情况,一般可以在维基百科上找下官方信息,就能发现 Z-Library 还提供了一个 onion 域名可以访问。

所以,我们可以下载一个 Tor 浏览器,或者 Brave 浏览器,然后访问 http://zlibrary​24tuxziyiyfr7​zd46ytefdqbqd2axkmxm​4o5374ptpc52fad.onion 来继续访问了:

当然, Tor 网络虽然链路是分布式的,但是其中的 server 却是集中式的,个人觉得不算是“分布式Web”

  • 网上也会有一些 Z-Library 镜像,也可以搜索下使用,比如 zlib.wikizlib.quest等。

Z-Library 存档

当然,这种网站一般也会有人收集离线存档,可以在 pilimi.org 找到 Z-Library 存档。

阅读更多

如何使用rust写内核模块

近年来,Rust语言以内存安全、高可靠性、零抽象等能力获得大量开发者关注,而这些特性恰好是内核编程中所需要的,所以我们来尝试下如何用rust来写Linux内核模块。

Rust与内核模块

虽然Rust支持已经在Linux Kernel 6.1版本合并到主线了,所以理论上来说,开发者可以使用Rust来为Linux 6.1写内核模块。
但实际开发工作中,内核版本不是最新的,比如Debian 11的内核就是5.10版本的,那么在这种情况下,该如何用Rust写内核模块呢?

原理

  1. Rust如何向内核注册回调、如何调用内核代码。Rust和C的互操作性
  2. Rust如何编译到目标平台上。Rust的target配置
  3. Rust如何申明内核模块入口、并添加特殊section。Rust内核模块的二进制约定

Rust和C的互操作性

第一个问题基本上就是C和Rust的互操作性了。
得益于Rust的抽象层次,C语言和Rust的互相调用都是比较容易的。rust官方也提供了bindgen这样,根据.h文件生成.rs文件的库。
这样一来,貌似直接使用bindgen将内核头文件翻译成.rs就可以了?
但还有一个问题,如何获取内核头文件路径呢?
可以使用一个dummy内核模块,在编译过程中把编译参数导出来,其中包含了头文件路径,编译参数等,用于bindgen生成代码。

Rust的target配置

内核模块和普通的程序相比,主要的不同在于:

  1. 内核模块是freestanding的,没有libc、内存分配也比较原始
  2. 内核模块对于异常处理等有特殊约定
阅读更多

如果java虚拟线程稳定了,是不是有一大批框架和工具要重写?

知乎上有一个提问,“如果java虚拟线程稳定了,是不是有一大批框架和工具要重写?”,我整理了下我的知乎回答:

首先,抛开虚拟线程、协程,我们看看现在的架构。

在顶层,开辟出很多调度单位(线程、协程);在底层,有很多的IO原语(accept、read、write);在中间,有很多的组织逻辑。

协程主要做的就是,在底层执行IO原语的时候,暂时保存上下文,等执行结束后,再恢复上下文继续执行。

那么在现有的协程实现方案上,就出现了两种模式:

有栈协程,就是协程上下文包括了callstack,当IO完成、恢复上下的时候,连带着callstack恢复。那么对于callstack中的各个caller(调用者)和callee(被调用者),都感知不到整个协程的调用过程,自然代码就不用修改了。

有栈协程的优点就是不需要现有的代码改动太多,只需要调度单位创建+调度器+IO操作方面改动即可。但开发者的把控就比较弱,不能干涉调度过程。

Go和Java的Loom,都是有栈协程。

无栈协程,就是协程上下问不包含callstack,恢复上下文的时候,也只是通知上下文完成了。接下来干什么事情,还是得重新构建callstack。

比如 A -> B -> C -> IO函数,当出现IO调用,需要保留上下文,那么就需要C->B->A逐个返回上下文,恢复的时候也是 A -> B ->C去恢复执行。

阅读更多

让 Java Agent 在 Dragonwell 上更好用

本文是《容器中的Java》系列文章之 3/n ,欢迎关注后续连载 :) 。

背景

Java Agent 技术能够动态修改 Java 应用程序行为,而不用重新修改代码。

正是因为这些特点,很多中间件团队、云厂商团队、开源产品,开始使用 Java Agent 技术来提供一些基础能力,比如 Apache Skywalking、OpenTelemetry 都提供了 Java Agent。

在早前,中间件团队通过SDK提供能力(比如可观测、微服务治理能力等);但中间件团队每次新增特性、修复缺陷,都需要各个业务方更新SDK版本、重新发布。

随着公司架构越来越复杂,随着云厂商开始提供中间件能力,这种逐个推动SDK使用方更新的方式越来越麻烦。

而用了 Java Agent 之后,业务同学只需要写业务代码;中间件能力通过设置环境变量来动态注入Java Agent来实现。Java Agent的更新,也只需要重启应用即可。

问题

我们以一个微服务demo为例。先在一个Kubernetes集群中部署demo,然后通过JAVA_TOOL_OPTIONS使用用Java Agent:

1
2
$ echo $JAVA_TOOL_OPTIONS
-javaagent:/home/admin/.opt/ArmsAgent/arms-bootstrap-1.7.0-SNAPSHOT.jar ...
阅读更多

LeetCode 周赛 311

第一题 2413. Smallest Even Multiple

给你一个正整数 n ,返回 2 和 n 的最小公倍数(正整数)。

显而易见的:

1
2
3
4
5
6
7
func smallestEvenMultiple(n int) int {
if n%2==1{
return n*2
}else{
return n
}
}

第二题 2414. Length of the Longest Alphabetical Continuous Substring

最近恰好做过dp的题目,所以立马dp:

  • 状态定义为 下标i -> 以i结尾的string中,字母序连续的字符串最大长度
  • 状态转移 s[i]和s[i-1]连续,那么在前面基础上+1,如果不连续,则置为1

最后,遍历i,拿最大长度即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func longestContinuousSubstring(s string) int {
dp := make([]int, len(s))
dp[0] = 1
for i := 1; i < len(s); i++ {
if s[i] == s[i-1]+1 {
dp[i] = dp[i-1] + 1
} else {
dp[i] = 1
}
}
res := 1
for i := 0; i < len(dp); i++ {
if res < dp[i] {
res = dp[i]
}
}
return res
}
阅读更多

Java Agent踩坑之appendToSystemClassLoaderSearch问题

本文是《容器中的Java》系列文章之 2/n ,欢迎关注后续连载 :) 。

从Java Agent报错开始,到JVM原理,到glibc线程安全,再到pthread tls,逐步探究Java Agent诡异报错。

背景

由于阿里云多个产品都提供了Java Agent给用户使用,在多个Java Agent一起使用的场景下,造成了总体Java Agent耗时增加,各个Agent各自存储,导致内存占用、资源消耗增加。

所以我们发起了one-java-agent项目,能够协同各个Java Agent;同时也支持更加高效、方便的字节码注入。

其中,各个Java Agent作为one-java-agent的plugin,在premain阶段是通过多线程启动的方式来加载,从而将启动速度由O(n)降低到O(1),降低了整体Java Agent整体的加载时间。

问题

但最近在新版Agent验证过程中,one-java-agent的premain阶段,发现有如下报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2022-06-16 09:51:09 [oneagent plugin a-java-agent start] ERROR c.a.o.plugin.PluginManagerImpl -start plugin error, name: a-java-agent
com.alibaba.oneagent.plugin.PluginException: start error, agent jar::/path/to/one-java-agent/plugins/a-java-agent/a-java-agent-1.7.0-SNAPSHOT.jar
at com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:113)
at com.alibaba.oneagent.plugin.PluginManagerImpl.startOnePlugin(PluginManagerImpl.java:294)
at com.alibaba.oneagent.plugin.PluginManagerImpl.access$200(PluginManagerImpl.java:22)
at com.alibaba.oneagent.plugin.PluginManagerImpl$2.run(PluginManagerImpl.java:325)
at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.InternalError: null
at sun.instrument.InstrumentationImpl.appendToClassLoaderSearch0(Native Method)
at sun.instrument.InstrumentationImpl.appendToSystemClassLoaderSearch(InstrumentationImpl.java:200)
at com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:100)
... 4 common frames omitted
2022-06-16 09:51:09 [oneagent plugin b-java-agent start] ERROR c.a.o.plugin.PluginManagerImpl -start plugin error, name: b-java-agent
com.alibaba.oneagent.plugin.PluginException: start error, agent jar::/path/to/one-java-agent/plugins/b-java-agent/b-java-agent.jar
at com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:113)
at com.alibaba.oneagent.plugin.PluginManagerImpl.startOnePlugin(PluginManagerImpl.java:294)
at com.alibaba.oneagent.plugin.PluginManagerImpl.access$200(PluginManagerImpl.java:22)
at com.alibaba.oneagent.plugin.PluginManagerImpl$2.run(PluginManagerImpl.java:325)
at java.lang.Thread.run(Thread.java:855)
Caused by: java.lang.IllegalArgumentException: null
at sun.instrument.InstrumentationImpl.appendToClassLoaderSearch0(Native Method)
at sun.instrument.InstrumentationImpl.appendToSystemClassLoaderSearch(InstrumentationImpl.java:200)
at com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:100)
... 4 common frames omitted

熟悉Java Agent的同学可能能注意到,这是调用Instrumentation.appendToSystemClassLoaderSearch报错了。

阅读更多

kill-port 清理占用端口的进程

背景

在日常开发中,经常出现端口莫名被占用的情况。比如要启动一个java服务,报错8080端口被占用,这时候就想着快速清理掉占用该端口的进程。

在Linux下,我们可以用熟悉的ss命令来找出进程并删除,但是macOS下,netstat我用的比较少,而且速度也很慢。

最近正好在学习Rust,所以就用Rust来写一个kill-port小工具,也算是入门系统编程了吧。

安装

1
2
3
$ git clone https://github.com/robberphex/kill-port.git
$ cargo build --release
# 将 ./target/release/kill-port 添加到PATH路径中。

使用方法

1
2
3
4
5
6
7
kill-port 0.1.0

USAGE:
kill-port [PORT]...

ARGS:
<PORT>... ports to find

例子:

1
2
# kill掉占用8080端口的进程
$ kill-port 8080
阅读更多

用 Pygments 在 Word 中实现代码高亮

虽然日常都是在写代码,但有的时候,还是需要写一些Word文档。这时候在Word文档中贴代码的时候,就希望能够做到代码高亮。

可以看一下rtf formatter的说明pygmentize -H formatter rtf:

1
2
3
4
5
6
7
Format tokens as RTF markup. This formatter automatically outputs full RTF
documents with color information and other useful stuff. Perfect for Copy and
Paste into Microsoft(R) Word(R) documents.

Please note that ``encoding`` and ``outencoding`` options are ignored.
The RTF format is ASCII natively, but handles unicode characters correctly
thanks to escape sequences.

可以看到,复制出来的是RTF格式的,能够复制、粘贴到Word文档中,并带上语法高亮等特性。

使用时只需要执行如下命令,

1
pygmentize ~/tmp/a-pod.yaml -f rtf | pbcopy

然后在Word等支持RTF格式的软件中粘贴即可。

阅读更多