程序如何装载

Main\.java, Minor\.java -> jar包.java Main\.main(): 编译打包
jar包.java Main\.main() -> 验证: 加载
jar包.java Main\.main() -> Minor\.class: 使用
Minor\.class -> JVM: 加载
验证 -> 准备 -> 解析 -> 初始化 -> JVM

加载:从磁盘加载到内存。(懒加载,用到类才加载,如main方法或new对象)
验证:验证字节码是否正确、是否可识别。
准备:初始化静态(static,不包括常量)变量、赋初值(默认值)。
解析:符号引用 -> 直接引用。静态方法(如main) -> 指向数据所在内存的指针。这是静态链接,在类加载期间完成;而动态链接在程序运行期间完成。
初始化:为静态变量赋值,执行静态代码块。

类加载器

加载过程由类加载器实现,有几种类加载器:

  1. 引导类加载器(C++):JRE核心lib的jar类包
  2. 扩展类加载器:JRE拓展lib(ext)jar类包
  3. 应用程序类加载器:ClassPath路径下的类包(自己编写的类)
  4. 其他加载器:加载自定义路径下的类包
java com\.site\.jvm\.Math\.class -> java\.exe调用底层jvm\.dll创建Java虚拟机 -> 创建引导类加载器实例
创建引导类加载器实例 -> sum\.misc\.Launcher\.getLauncher(): C++调用Java代码,创建JVM启动器实例,这个实例负责创建其他类加载器
sum\.misc\.Launcher\.getLauncher() -> launcher\.getClassLoader(): 获取运行类自己的加载器ClassLoader(AppClassLoader实例)
launcher\.getClassLoader() -> classLoader\.loadClass("com\.site\.jvm\.Math"):调用loadClass加载即将要运行的类
classLoader\.loadClass("com\.site\.jvm\.Math") -> Math\.main(): 加载完成后,JVM执行Math.main()
创建引导类加载器实例 -> Math\.main(): C++发起调用
Math\.main()-> JVM销毁: Java程序运行结束
阅读全文 »

基本配置

  1. sudo权限: 修改/etc/sudoers,或者rootadduser <username> sudo并重启
  2. apt软件包管理系统换源:/etc/apt/sources.list修改软件发布源
    deb http://站点/目录名/stretch版本名 main contrib non-free三类软件包
    Debian官方软件源:官网/mirror/list
  3. /usr/share/doc 有安装软件的信息

1.0 命令篇

基本命令

参考资料 https://missing-semester-cn.github.io/2020/course-shell/
  • 关机、重启
    shutdown
    -h now halt,挂起,相当于 halt
    -r now reboot,重启
    poweroff
    reboot

  • 手册
    man 命令

  • 导航
    pwd 显示当前所在目录
    cd 进入文件夹 '..' 上级目录 '.'当前目录 '/'开头的是绝对路径`

  • 查看文件
    ls 列出所有文件
    ls -l 查看文件权限信息

  • 创建文件夹
    mkdir 文件夹名
    rmdir 删除文件夹

  • 没有vim的时候如何创建、编辑、查看文件
    touch 文件 创建文件
    echo 文本 > 文件 echo+重定向输入文件(会把原来内容覆盖)
    echo 文本 >> 文件 追加输入(在原来内容的结尾另起一行输入)
    cat 文件 查看文件
    除了使用cat看文件,还有tac(从最后一行开始显示),more, less(可以翻页,好用)

  • 压缩
    压缩一整个目录,使用 tar
    压缩单个文件 bzip2 gzip(-d解压)
    tar -cvf 目标名 文件名 压缩 , tar -xvf解压

  • 查找
    grep 用法 grep "word" filename

grep "string" * # 在所有文件中搜索string
grep -r "string" # 递归搜索
`find`用法 `find filename`
阅读全文 »

ssh登录

ssh <user_name>@<remote_ip> -p <remote_port> -i <your_key>

ssh端口映射

可以用于不保留端口的情况下,远程连接数据库等。

ssh -N -L <local_port>:localhost:<remote_port> <user_name>@<remote_ip> -p <remote_port> -i <your_key> 

脚本批量映射

需要注意,Nacos有gRPC,除了8848端口外,9848端口也要一起开放。

# Port Mapping
PORTS=(
"ulocalport:localhost:uremoteport"
# MySQL
"53306:localhost:3306"
# Nacos
"58848:localhost:8848"
"59848:localhost:9848"
# Redis
"56379:localhost:6379"
# RocketMQ namesrv
"59876:localhost:9876"
# RocketMQ broker
"510911:localhost:10911"
)

ARGS=()
for port in "${PORTS[@]}"; do
ARGS+=(-L "$port")
done

ssh -o ServerAliveInterval=60 -N "${ARGS[@]}" <username>@<remote_ip>

密钥登录

  1. 首先在本地生成一份密钥,然后将公钥上传到remote的~/.ssh/authorized_keys
  2. 修改remote/etc/ssh/sshd_config
# 新端口
Port 22
# 启用密钥认证
PubkeyAuthentication yes
# 禁用密码登录
PasswordAuthentication no
# 允许Root登录但禁止密码验证
PermitRootLogin prohibit-password
  1. 重启ssh
# Ubuntu/Debian
sudo systemctl restart ssh

# CentOS/RHEL
sudo systemctl restart sshd

线程资源通过线程池提供

线程池可以减少创建、销毁线程的开销,解决资源不足问题。
如果手动创建线程,容易造成系统存在大量同类线程而导致内存耗尽、过度切换问题。

不使用Executors

Executors.newFixedThreadPool() 固定大小线程池
Executors.newSingleThreadExecutor() 单线程池
Executors.newCachedThreadPool() 动态线程池

Executors底层仍然使用new来创建线程,容易造成OOM。

自己调用ThreadPoolExecutor

推荐的方法使,直接自己调用new ThreadPoolExecutor来创建线程池,设置自己的参数

public ThreadPoolExecutor(
@Range(from = 0, to = Integer.MAX_VALUE) int corePoolSize,
@Range(from = 1, to = Integer.MAX_VALUE) int maximumPoolSize,
@Range(from = 0, to = Long.MAX_VALUE) long keepAliveTime,
@NotNull TimeUnit unit,
@NotNull BlockingQueue<Runnable> workQueue,
@NotNull ThreadFactory threadFactory,
@NotNull RejectedExecutionHandler handler
) { ... }

// Example
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
5,
10,
0L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10), // 10为容量
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);

corePoolSize 核心线程数量。
maximumPoolSize 最大线程数量(核心 + 临时)。只有核心线程满了,而且阻塞队列也满了,才会创建临时线程。
keepAliveTime 临时线程的空闲存活时间。
threadFactory 线程工厂,即以什么方式创建线程。
handler 核心线程、阻塞队列、临时线程都满了,触发拒绝策略。

Executors的问题在于,它指定的阻塞队列大小是Integer.MAX_VALUE,会导致内存溢出;而Executors.newCachedThreadPool()更是指定了maximumPoolSizeInteger.MAX_VALUE

拒绝策略,如何保证线程不丢

使用直接拒绝AbortPolicy策略,线程会丢失。此时可以使用

  • CallerRunsPolicy 直接在主线程同步执行(可能会阻塞主线程)
  • DiscardOldestPolicy 将队列头部删除,新线程尾部入队(保证新任务优先级)
  • DiscardPolicy 可以自己拓展,例如将任务放到redis、rocketmq中

ThreadPool工作流程

当前线程数没有达到corePoolSize之前,每个新任务都会触发创建新线程;
达到以后,才会放到阻塞队列里,等待线程任务执行完成,分发任务给核心线程。
阻塞队列满了,才会创建临时线程执行任务。

ThreadPool如何初始化?

供参考的经验值:

  • CPU密集任务(如数据统计、排序):
    核心线程 = 最大线程 = 核心数 + 1
    减少上下文切换开销
  • IO密集任务:
    核心线程 = 核心数 * 2;最大线程 = 核心数 * 4

动态线程池

参考教程: https://www.ruanyifeng.com/blog/2019/10/tmux.html
  • tmux使会话与窗口解绑,一个窗口source .bashrc更新了,另一个窗口可能没有
  • ctrl+b 前缀键
    % 分成左右两栏
    " 分成上下两栏
    up 选择上边的窗口
    [ 查看历史记录

窗口

  • <C-d> 删除窗口
  • <C-b> z 最大化/最小化一个窗口
  • <C-b> c 创建一个新的窗口,使用 <C-d>关闭
  • <C-b> N 跳转到第 N 个窗口,注意每个窗口都是有编号的
  • <C-b> p 切换到前一个窗口
  • <C-b> n 切换到下一个窗口
  • <C-b> w 列出当前所有窗口

Tmux-Path 双向绑定

使用tmux会存在一个问题:在1个窗口设置了环境变量,在其他窗口并不会生效。
下面的脚本会帮助我们解决问题,它将tmux白名单内的全局变量值自动同步到Shell环境变量。

阅读全文 »

Shortcuts 快捷键

  • CTRL+N|P 下、上一条命令
  • CTRL+A|E 跳到行首、行尾
  • ALT+F|BCTRL+←|→ 下、上一个单词(右ALT开始使用~)
  • CTRL+W 删除前面的单词
  • CTRL+D|H 删除一个字符(D向后删除=Delete,H向前删除=Backspace)
  • CTRL+U 删除整行
  • CTRL+R 搜索整行,Esc退出

Proxy

export https_proxy=http://host:port;
export http_proxy=http://host:port;
export all_proxy=socks5://host:port;

Bash prompt string配置

使用Windows CMD比使用MinTTY更快!

  • PS(prompt string): 是命令行的默认显示文本,如:
username@hostname /work_directory
$
  • 可以在~/.bash_profile.bashrc中修改默认显示(Windows Git Bash是git-prompt.sh
# Windows Git Bash默认配置,其中\007前面的$TITLEPREFIX和$PWD是标签栏的内容,git_ps1会显示git工作分支
export PS1='\[\e]0;$TITLEPREFIX:$PWD\007\]\n\[\e[32m\]\u@\h \[\e[35m\]$MSYSTEM \[\e[33m\]\w\[\e[36m\]`__git_ps1`\[\e[0m\]\n$ '
# 显示效果
username@hostname MINGW /work_directory
$

# 简洁配置,将\h换成了指定文本,保留了命令行前的换行,将标签名改为当前目录
# 注意,使用单引号才有动态解析效果
export PS1='\[\e]0;\W\007\] \n\[\e[32m\]\u@Host \[\e[33m\]\w\[\e[36m\]`__git_ps1`\[\e[0m\]\n$ '
# 显示效果
username@Host /work_directory
$
  • Windows虚拟环境后不会换行:设置venv/Scripts/activate
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1-}"
PS1="\n(${VIRTUAL_ENV_PROMPT}) ${PS1-}" # 添加换行符
export PS1
fi
  • MSYS2显示git prompt
# etc/profile.d/git-prompt.sh
if test -f /etc/profile.d/git-sdk.sh
then
TITLEPREFIX=SDK-${MSYSTEM#MINGW}
else
TITLEPREFIX=$MSYSTEM
fi

if test -f ~/.config/git/git-prompt.sh
then
. ~/.config/git/git-prompt.sh
else
PS1='\[\033]0;$TITLEPREFIX:$PWD\007\]' # set window title
PS1="$PS1"'\n' # new line
PS1="$PS1"'\[\033[32m\]' # change to green
PS1="$PS1"'\u@\h ' # user@host<space>
PS1="$PS1"'\[\033[35m\]' # change to purple
PS1="$PS1"'$MSYSTEM ' # show MSYSTEM
PS1="$PS1"'\[\033[33m\]' # change to brownish yellow
PS1="$PS1"'\w' # current working directory
if test -z "$WINELOADERNOEXEC"
then
GIT_EXEC_PATH="$(git --exec-path 2>/dev/null)"
COMPLETION_PATH="${GIT_EXEC_PATH%/libexec/git-core}"
COMPLETION_PATH="${COMPLETION_PATH%/lib/git-core}"
COMPLETION_PATH="$COMPLETION_PATH/share/git/completion"
if test -f "$COMPLETION_PATH/git-prompt.sh"
then
. "$COMPLETION_PATH/git-completion.bash"
. "$COMPLETION_PATH/git-prompt.sh"
PS1="$PS1"'\[\033[36m\]' # change color to cyan
PS1="$PS1"'`__git_ps1`' # bash function
fi
fi
PS1="$PS1"'\[\033[0m\]' # change color
PS1="$PS1"'\n' # new line
PS1="$PS1"'$ ' # prompt: always $
fi

MSYS2_PS1="$PS1" # for detection by MSYS2 SDK's bash.basrc

# Evaluate all user-specific Bash completion scripts (if any)
if test -z "$WINELOADERNOEXEC"
then
for c in "$HOME"/bash_completion.d/*.bash
do
# Handle absence of any scripts (or the folder) gracefully
test ! -f "$c" ||
. "$c"
done
fi

# ~/.bashrc || ~/.bash_profile
shopt -q login_shell || . /etc/profile.d/git-prompt.sh
syntax description
\u username
\h hostname
\e set colors
\w work directory
\W current diretory
\n line feed
阅读全文 »

微服务

非单体项目,可以用下面的脚本启动微服务。

#!/bin/bash

# 获取服务名称和额外参数
SERVICE_NAME=$1
shift # 移除第一个参数(服务名),将剩余参数保存到 $@
EXTRA_ARGS="$@"

# 检查是否输入服务名称
if [ -z "$SERVICE_NAME" ]; then
echo "Usage: ./run.sh <servicename|all> [additional_maven_args]"
exit 1
fi

# 定义运行单个服务的函数
run_service() {
local service=$1
local args=$2
echo "Building and running $service with args: $args..."
mvn clean install -pl $service -am
if [ "$service" == "gateway/" ]; then
echo "Gateway starting..."
mvn spring-boot:run -pl $service -Dreactor.netty.http.server.accessLogEnabled=true $args
else
mvn spring-boot:run -pl $service $args
fi
}

# 如果输入 "all",运行所有服务(默认不传参)
if [ "$SERVICE_NAME" == "all" ]; then
echo "Building and running all services..."
mvn clean install -pl "!generator"
for module in $(mvn help:evaluate -Dexpression=project.modules -q -DforceStdout | sed -e 's/<[^>]*>//g' -e 's/\s*//g' | tr ',' '\n'); do
if [ "$module" != "generator" ]; then
echo "Running $module..."
mvn spring-boot:run -pl $module
fi
done
else
# 运行指定的单个服务,并传递额外参数
run_service $SERVICE_NAME "$EXTRA_ARGS"
fi

核心命令是这一条:

mvn spring-boot:run -pl $your_service

想要增加JVM参数,指定端口可以加上

-Dspring-boot.run.arguments=--server.port=$your_port

全局换源

找到 settings.xml 文件:

  • 全局配置:位于 Maven 安装目录的 conf 文件夹下(例如/usr/local/maven/conf/settings.xml)。
  • 用户配置:位于用户主目录下的 .m2 文件夹中(例如~/.m2/settings.xml)。
<settings>
<!-- 其他配置 -->
<mirrors>
<!-- 阿里云 Maven 镜像 -->
<mirror>
<id>aliyun-maven</id>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
<mirrorOf>central</mirrorOf> <!-- 指定替换中央仓库 -->
</mirror>
<!-- 华为云 Maven 镜像 -->
<mirror>
<id>huaweicloud</id>
<name>华为云 Maven</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
<!-- 其他配置 -->
</settings>

上传文件

scp ./upload/path/file.postfix user@host.com:/path/to/file
scp -P <port> -i <key>
# 文件夹
scp -r ./upload/path/folder user@host.com:/path/to/folder

下载文件

scp user@host.com:/path/to/file.postfix ./download/path
# 文件夹
scp -r user@host.com:/path/to/folader ./download/path

基本操作

  • gg=G:代码格式化
  • GX, GF:打开链接/文件
  • :%y *:复制全部到系统剪贴板
  • <C-o>, <C-i>:回到前一个/后一个位置(例如,打开文件默认在第一行,<C-o>回到上次编辑位置)。注意这个操作是跨文件的
  • `0:返回上次位置
  • 词:w(下一个词),b(词初),e(词尾)
  • 行:0(行初),^(第一个非空格字符),$(行尾)
  • 文件:gg(文件头),G(文件尾)
  • 搜索:/{正则表达式},n/N用于导航匹配
  • x删除字符(等同于dl
  • :s(substitute)替换字符(等同于xi
    • 替换命令:{作用范围}s/{目标文本}/{替换文本}/{替换标志}
    • :%s/s_content/o_content/gc 全局替换,附带确认提示
Sign Range
% 整个文件
. 当前行
$ 最后一行
,n 当前行到n行
n, n行到当前行
+n 当前行后n行
  • 可视化模式 + 操作
    • 选中文字,d删除(剪切) 或者c改变
  • u撤销,<C-r>重做
  • y复制 / “yank” (其他一些命令比如d也会复制)
  • p粘贴
    • +p 粘贴系统剪贴板
  • 更多值得学习的:
    • :<line> 跳到line行,相当于<line>G
    • ~改变字符的大小写
    • 3w向前移动三个词
    • A(大写)可以迅速定位到行尾进行修改
    • :!python prog.py 使用!直接运行shell命令
  • %匹配括号

Change

ce 替换一个单词
cs"' 把当前词块的"全部替换成’

多行操作

<C-v> 选中多行,Shift + i输入后Esc,即可多行同步输入

Esc Map

Vim和NeoVim内置了<C-[>作为<Esc>的映射;
还可以通过<A->Alt加上任何键(Meta键)的方式触发<Esc>-

录制宏

命令模式下,

  1. q<marcoName>进行录制,<marcoName>是宏的名字,例如qa
  2. 执行一系列操作后,再次按q结束录制

使用宏

命令模式下,使用@<marcoName>即可执行;使用5@<marcoName>可以重复执行宏

文件操作

  • :e filename切换到filename文件
  • :bn/bp切换到下/上个文件
  • <C-x><C-f> 自动补全路径
阅读全文 »

Git配置

远程仓库 - 廖雪峰的官方网站 (liaoxuefeng.com)

  1. 创建ssh key,在c盘用户目录.git文件夹中
    ssh-keygen -t ed25519 -C "youremail@example.com"
    ssh-keygen -l -f ~/.ssh/id_rsa 可以查看秘钥的配置信息,包括邮箱
  2. 在GitHub账号设置页面,添加ssh key,复制.ssh/id_rsa.pub的信息,点击创建即可
  3. 测试是否成功:ssh -T git@github.com
    注意:如果测试不成功,可能是反向代理的问题

Github 远程仓库

  1. 在github上新建一个仓库
  2. git remote add origin git@github.com:github账号名称/仓库名称.git 关联仓库,origin是远程库的名字
  3. git push -u origin master把本地库内容(master分支)推送到远程库(oringin),-u 参数表示会把本地master分支和远程master分支关联起来,方便后面简化命令
  • git remote set-url origin <URL>更改仓库地址

github trending 热门软件

  • 项目含金量 stars 1k+
  • fork 拷贝项目到自己的仓库
  • pull request 合并分支

NJU

  • 学习编程语言如C、Rust
  • 精选精读论文
  • STFW:比百度更高效的办法
0%