一个正则表达式导致 CPU 高的问题排查过程

这篇文章记录一个正则表达是导致 CPU 高的问题排查。由于无法直接使用线上的代码测试,所以我自己把代码整理了下来,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class AppMain {
public static void main(String[] args) throws InterruptedException {
final String regex="^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";
final String email="blog.laofu.online.fuweilao@vip.qq.com#";
for (int i = 0; i < 1000000; i++) {
Matcher matcher = RegexUtils.matcher(regex, email);
matcher.find();
Thread.sleep(10);
// matcher.group();
}
}
}

当运行程序的时候,我们可以看到 java 的进程占用了 CPU 了 82.1%,由于我使用的服务器是 1核+2G, 所以 load avg 占用也很高。

阅读更多

字节码判断方法参数的个数

Jvm 如何确定方法的参数的个数

找到 Method 的 DescriptionIndex 的属性,找到对应的描述,例如:

1
2
3
4
5
6
7
8
9
10
11
public class AddMain {
public static void main(String[] args) {

int c = add(100,200);
System.out.println(c);
}

private static int add(int a, int b) {
return a + b;
}
}

这个例子中的 java 代码,add 方法对应的代码是 (II)I,最后一个 I 代表返回值,这个代表两个整型的参数.

1
2
3
private static int add(int a, int b,String c,boolean d) {
return a + b;
}

​ 同样,(IILjava/lang/String;Z)I 代表有4个参数,字符串的表示是:Ljava/lang/String;,解析比较特殊。

阅读更多

手写一个简单的JVM--01. 解析Class文件

java的运行过程

在运行一段 java 代码的时候需要经过编译,验证,加载运行,具体如下图:

运行过程

对于 Java 源码变成字节码的编译过程,我们暂且跳过不讨论。

想弄清楚 java 代码的运行原理,其实本质就是 java 字节码如何被 jvm 执行。

阅读更多

JVM指令的速记

在学习的JVM的时候,最重要的是认识JVM的指令,JVM指令很多,为了方便记忆,可以根据前缀和功能进行分类:

例如:nop指令代表是一个空指令,JVM收到指令后,什么都不用做,等待下一个指令。

阅读更多

Java如何实现零拷贝

什么是零拷贝

在操作系统中,从内核的形态区分,可以分为内核态(Kernel Space)和用户态(User Space)。

在传统的IO中,如果把数据通过网络发送到指定端的时候,数据需要经历下面的几个过程:

IO

  1. 当调用系统函数的时候,CPU执行一系列准备工作,然后把请求发送给DMA处理(DMA可以理解为专门处理IO的组件),DMA将硬盘数据通过总线传输到内存中。
  2. 当程序需要读取内存的时候,这个时候会执行CPU Copy,内存会有内核态写入用户的缓存区。
  3. 系统调用write()方法时,数据从用户态缓冲区写入到网络缓冲区(Socket Buffer), 由用户态编程内核态。
  4. 最后由DMA写入网卡驱动中,传输到网卡的驱动。
阅读更多

Java中实现顺序IO

顺序IO和随机IO


对于磁盘的读写分为两种模式,顺序IO和随机IO。 随机IO存在一个寻址的过程,所以效率比较低。而顺序IO,相当于有一个物理索引,在读取的时候不需要寻找地址,效率很高。

网上盗了一个图(侵权删)
IO

阅读更多

ServiceLoader的使用

获得接口的实现类有点困难

在Java中,由于反射的局限性,无法直接获取一个接口的所有实现子类,所以为了能够实现一个接口动态的注入实现的子类对象,需要借助ServiceLoader

简单的Demo使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

public interface IService {
void doSomeThing();
}

public class DefalutService implements IService{
@Override
public void doSomeThing() {
RzLogger.info("默认服务");
}
}

public class LogService implements IService {
@Override
public void doSomeThing() {
RzLogger.info("日志服务");
}
}


public static void main(String[] args) {
ServiceLoader<IService> loader = ServiceLoader.load(IService.class);
for (IService service : loader) {
service.doSomeThing();
}
}

阅读更多

一个有效的收拾程序运行残局的方法--ShutdownHook

发现addShutdownHook

在阅读QMQ的源码的时候,在Server端启动的时候,注册了一个shutdown的代码,具体的代码如下:

1
2
Runtime.getRuntime().addShutdownHook(new Thread(wrapper::destroy));

addShutdownHook作用

addShutdownHook方法可以添加一个指定的线程来在Java程序退出的时候做一些事情,在以下几个场景会被调用:

  1. 程序运行完成后退出
  2. 使用Ctrl+C时终端退出
  3. 调用系统退出的方法, System.exit(0)
阅读更多

CAS的性能问题

昨天写了一个计数器的类,性能高于JDK,思考了很久,后来被同学点破。

 public void increase() {
    long before = unsafe.getLongVolatile(this, offset);
    while (!unsafe.compareAndSwapLong(this, offset, before, before + 1))
    {
        before = unsafe.getLongVolatile(this, offset);
        Thread.yield();
    }
}

有人怀疑是测试的代码问题,后来发现并不是,真正的原因是:在高并发的环境下,CAS修改旧值时经常被其他线程中断,就会进行重试,不断的重试的代价就很高。yield操作能够缓解这个情况,但是也会带来多次上下文切换,在并发没那么高的情况下,反而更浪费资源

所以JDK在写的Atomic的类型的时候,应该是考虑到重试一次的代价,小于线程的上下文切换,所以并没有采用yield操作。

阅读更多