Spring 核心知识

注解源码解析

  • @Configuration 如何扫描注解,并且构建 bean
  • @ComponentScan 定义 filter, 源码查看如何扫描注释,使用过滤规则
  • @Bean 源码查看如何 parser bean 注解信息,如何 实例化,并调用自定义的 init 和 destroy。
  • 从 IOC 容器中获取 Bean 的过程
  • @Import 注解与 bean 注解类似,不过使用起来更灵活些
  • @PropertySource 如何解析自定义文件,并加入到 spring 环境中

Mysql基础知识学习

Innodb 索引结构

B+tree 相比红黑树可以用更少的磁盘 IO B+tree 相比 Hash 结构和 B-tree,支持范围查询

索引分为聚簇索引和二级索引。 聚簇索引就是包含所有的内容,表数据存于叶子节点上。

通过二级索引一般需要回表查询,但是如果是二级索引是覆盖索引,也就是包含了所有需要的字段,那么就可以不回表了。

联合索引并不是多个索引,而是一个索引结构,按书写顺序来的。

MVCC 原理

MVCC 依赖 undolog + readView + 数据库隐藏字段

readView 是开始读时候的快照,维护了开始时刻活跃事务 id

可见性就是依赖这个活跃事务 idList,如果当前事务(readView 中的相关事务 id) id 比最小的还小,那么可见,比最大的还大,那么不可见,如果在之间,那么就看在不在列表里,如果在,那么就不可见,不在就可见。

如果不可见,那么就会去找 undolog 中的版本链,找到合适的事务 id。比较方法应该还是如上所述。

Explain/ DESC 优化查询

extra 中 filesort 并不是文件排序,加了 order by 之后一般都有。

java.lang.OutOfMemoryError: unable to create native thread

背景

用户反馈系统运行一段时间后就出现了如下错误。

java.lang.RuntimeException: java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached

用户执行了几次查询后,就发现线程疯狂飙升,而且jvm 内存释放后,线程却没释放,内存还是不够。

这说明,jvm 虽然内存不高,但是由于线程太多,java 线程和操作系统线程有对应关系,相当于操作系统线程占了pod 内的内存,造成内存不够。

开始我以为是监控不准,后来进入到 pod 后,发现 jvm 内存确实不高。那么就是线程泄漏。

这是用户的 stack 信息,发现 ss-0,ss-1,ss-2 线程个有 400 +

"ss-0" #721 daemon prio=5 os_prio=0 cpu=57.90ms elapsed=155563.07s tid=0x00007fc79a22dbd0 nid=0x316 waiting on condition  [0x00007fc5329c8000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@17.0.2/Native Method)
	- parking to wait for  <0x000000040492a820> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(java.base@17.0.2/LockSupport.java:341)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.2/AbstractQueuedSynchronizer.java:506)
	at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.2/ForkJoinPool.java:3463)
	at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.2/ForkJoinPool.java:3434)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.2/AbstractQueuedSynchronizer.java:1623)
	at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.2/LinkedBlockingQueue.java:435)
	at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.2/ThreadPoolExecutor.java:1062)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.2/ThreadPoolExecutor.java:1122)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.2/ThreadPoolExecutor.java:635)
	at java.lang.Thread.run(java.base@17.0.2/Thread.java:833)

   Locked ownable synchronizers:
	- None

"ss-1" #722 daemon prio=5 os_prio=0 cpu=42.84ms elapsed=155563.07s tid=0x00007fc79a22ff30 nid=0x317 waiting on condition  [0x00007fc5328c7000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@17.0.2/Native Method)
	- parking to wait for  <0x000000040492a820> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(java.base@17.0.2/LockSupport.java:341)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.2/AbstractQueuedSynchronizer.java:506)
	at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.2/ForkJoinPool.java:3463)
	at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.2/ForkJoinPool.java:3434)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.2/AbstractQueuedSynchronizer.java:1623)
	at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.2/LinkedBlockingQueue.java:435)
	at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.2/ThreadPoolExecutor.java:1062)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.2/ThreadPoolExecutor.java:1122)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.2/ThreadPoolExecutor.java:635)
	at java.lang.Thread.run(java.base@17.0.2/Thread.java:833)

   Locked ownable synchronizers:
	- None

"ss-2" #723 daemon prio=5 os_prio=0 cpu=47.49ms elapsed=155563.07s tid=0x00007fc79a230ea0 nid=0x318 waiting on condition  [0x00007fc5327c6000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@17.0.2/Native Method)
	- parking to wait for  <0x000000040492a820> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(java.base@17.0.2/LockSupport.java:341)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.2/AbstractQueuedSynchronizer.java:506)
	at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.2/ForkJoinPool.java:3463)
	at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.2/ForkJoinPool.java:3434)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.2/AbstractQueuedSynchronizer.java:1623)
	at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.2/LinkedBlockingQueue.java:435)
	at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.2/ThreadPoolExecutor.java:1062)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.2/ThreadPoolExecutor.java:1122)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.2/ThreadPoolExecutor.java:635)
	at java.lang.Thread.run(java.base@17.0.2/Thread.java:833)

   Locked ownable synchronizers:
	- None

其实这里就可以推测出来是创建了 400 + 个线程池,但是没有回收。

远程编译

今天同事用局域网内闲置电脑搞了个远程编译环境。本地脚本如下,效果很好,编译再也不用卡了

#!/usr/bin/env zsh
usage() { echo "Usage: ./rb.sh [-t] [-rf <model>]" 1>&2; exit 1; }
arg="-Dmaven.javadoc.skip=true -Dcheckstyle.skip=true -Dspotbugs.skip=true -Drat.skip=true -Djacoco.skip=true -DskipITs -DskipTests -Prelease"
while getopts "tr:" o; do
    case "${o}" in
        t) arg="-Drat.skip=true -Dcheckstyle.skip=false -T1" ;;
        r) rf="-rf ${OPTARG}" ;;
        *) usage ;;
    esac
done
shift $((OPTIND-1))
rsync -azh --progress --delete --exclude={'**/target','.git','.idea'} "$(pwd)" chuxin@llt-mbp.local:~/dev
rsync -azh --progress ~/.m2/ chuxin@llt-mbp.local:~/.m2/
# shellcheck disable=SC2087
ssh -tt -i /Users/chenchuxin/.ssh/id_rsa chuxin@llt-mbp.local << EOF
cd ~/dev/${PWD##*/}
./mvnw clean install $arg $rf
exit 0
EOF
rsync -azh --progress --include='**/target' chuxin@llt-mbp.local:~/dev/"${PWD##*/}"/ "$(pwd)"
rsync -azh --progress chuxin@llt-mbp.local:~/.m2/ ~/.m2

原理其实也很简单,就是使用 rsync 来同步,先将依赖同步过去,编译完后再将文件同步回来

读深入理解高并发编程后相关总结

线程池

线程池构造方法

Executors 类可以方便构造常用的线程池。

底层真正调用的就两个

ThreadPoolExecutor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

ForkJoinPool