实战|服务器调优案例

背景:在服务上线前夕,对我负责的一个查询接口进行压力测试,测试时发现,服务器的 cpu 占用率 很高, 4c16g 的机器,cpu 占用率达到 70%,这个查询接口是对第三方的 webservice 协议接口进行封装的。webservice接口部分使用的是生成的客户端代理类。

记一次服务器 CPU 飙高案例

为了找到为什么在压测期间 cpu 占用率飙升,我在测试期间上服务器 dump 线程堆栈信息。使用的是 jdk 自带的 jstack 工具

使用方法是:jstack -l pid > stack.txt 将终端打印的堆栈信息重定向到 stack.txt 文本中,方便后续分析。

将堆栈信息文本上传到 fastThread 网站进行分析。

022249

从分析结果发现 java.util.zip.ZipFile$ZipFileInputStream.read 方法被大量调用,此方法是 jdk 中的方法,我也没有手动调用过,查看该该部分源码发现该该方法还加了同步锁。

022350

因此,必须找到为何会频繁调用此方法,在本地 debug 发现,在调用 webservice 接口前,要创建代理对象,创建代理对象的过程中会调用这个方法,而且每请求一次,就会重新创建一个代理对象。至此找到了 cpu 占用率飙升的原因。

解决方案:

使用单例模式创建代理对象,使得代理对象只需要创建一次。

022436

验证:修改代码后,重新进行压测。发现 cpu 占用率维持在30%左右,并对压测时的线程堆栈信息进行分析。

022510

记一次 JAVA StackOverFlowError 异常

背景:昨天同事的一段程序出现了 StackOverFlowError 异常,问我怎么处理,我看了下代码,在这段代码里有递归,递归写的正确,有出口,但是递归的层级有点深,一共有 2600 级,递归到 2200 左右是抛出 StackOverFlowError 异常。

我分析了一下,出现该异常是由于递归的调用是这个一个线程中的,JVM 默认给一个线程分配的内存空间是 1M 大小,加上由于递归调用比较深,压栈比较多,导致分配给线程的内存空间耗尽,出现 StackOverFlowError 异常。

对此我给出 3 个解决方案

1、不使用递归

2、使用多线程的并发递归

3、设置 JVM 启动参数 -Xss2M 将分配给线程的默认内存空间调大

java -Xss2M -jar test.jar

记一次 被 static 修饰的、引用类型的成员变量被修改记录

背景:同事咨询我一个 bug,说是有一个 domian 类的一个属性 是 List 类型的,被 static 关键字修饰了,这个属性只在创建对象的时候初始化了值,并且只提供了一个 getter,按理说这个属性的值是永远不变的,但是随着项目的运行,这个 List 的 size 在不断的增长。

public class Order {
    private static List<Integer> orderIds = new ArrayList<>(Arrays.asList(, 2, 3, 4, 5));

    public List<Integer> getOrderIds() {
        return orderIds;
    }
}

随着时间的推移,getOrderIds 得到的 List 的长度越来越大。

分析:

orderIds 是引用类型的变量,即使没有 setter 可以修改值,但是依然可以通过 getter 获取到其引用,然后再修改值,那么这个属性值就成功的被修改了。并在这个变量是被 static 修饰,表示这个属性属于类的,任何一个对象将其修改了,对其他对象都生效。

    public static void main(String[]args){
        Order order=new Order();
        List<Integer> orderIds=order.getOrderIds();
        orderIds.add(6);
        Order orderNew=new Order();
        System.out.println(orderNew.getOrderIds());
        //[1, 2, 3, 4, 5, 6]
        }

但是我查看项目的源码,并没有发现这个 getter 被调用过。我猜测是在通过 POST 接口新增一条记录时,用 Order 对象接收的,并且参数中是包含这个属性的。

    @PostMapping("/add-order")
    public Object createOrder(@RequestBody Order order){
        System.out.println(order.getOrderIds());
        //[1, 2, 3, 4, 5, 1, 2, 3, 4, 5,]
        return order;
    }

结果证实了我的猜想。