分类 Java 下的文章

基于缓存的两个局部性,可以充分利用这两个局部性写出缓存友好的代码。

0 时间局部性

时间局部性最常见的例子就是循环中访问同一变量了。

比如下面求min(inclusive)到max(exclusive)之间的整数之和:

public static int sum(int min, int max) {
    int sum = 0;
    for (int i = min; i < max; i++) {
        sum += i;
    }
    return sum;
}

sum这一变量在循环外声明,在循环内使用。这样就利用了sum的时间局部性。

实践应用提示

  1. 循环处理时,将每次都要处理的数据声明放在循环外,即用数据的时间局部性
  2. 批量处理数据时,将相同的操作放在一起执行,利用指令的时间局部性,比如读数据-处理数据-写数据,再读数据-处理数据-写数据,可以转变成读数据-读数据-处理数据-处理数据-写数据-写数据

1 空间局部性

空间局部性的例子就是数组了。

比如下面求数据之和:

public static int sum(int[] arr) {
    int sum = 0;
    for (int i = 0; i < arr.length; i++) {
        sum += arr[i];
    }
    return sum;
}

上面的例子中,arr中的每个成员在内存中的地址是相邻的,这样访问第一个元素的时候,会把相邻的第二个、第三个等元素都放在缓存中,这样后续再访问这些元素的时候就快了。

实践应用提示

  1. 经常一并访问的对象尽量放在内存地址相近的地方

本周三(2020-09-23)我进行了disruptor的分享,其中disruptor的一个优化就是缓存友好。

我就上网搜了资料,分享下在实际工作中,如何写出缓存友好的代码。

0 缓存简介

wiki链接:Cache_(computing))

计算机缓存的产生是一下两种情况的权衡:

  1. 大小和速度(缓存越大,意味着物理距离上越远,这样就会影响处理速度)
  2. 前进但昂贵的的存储技术和便宜但容易制造的存储技术(前者比如SRAM,后者比如DRAM或硬盘)

缓存有两个优势:

  1. 延迟
  2. 吞吐量

现在的计算机缓存有如下两个局部性原理:

  • 时间局部性,基于假设:最近引用的对象在不久的将来大概率会再次引用

Temporal Locality: If an address gets accessed, then it is very likely that the exact same address will be accessed once again in the near future.

int a = 1;
//  'a' will hopefully be used again soon
  • 空间局部性,基于假设:需要引用的对象周围的对象大概率会被引用

Spatial Locality: If an address gets accessed, then it is very likely that nearby addresses will be accessed in the near future.

int[] arr = new int[10];
arr[5] = 10;
//  'A[4]' or 'A[6]' will hopefully be used soon

参考资料:

  1. Writing Cache-Friendly Code

在官方github中的issue-482中介绍了使用tt命令获取执行现场,然后用ognl获取spring的ApplicationContext从而为所欲为的示例。

在spring的生态中,如果可以获取到ApplicationContext,无疑真的可以为所欲为了。

但是在实际使用的,我发现这个获取途径有如下两个问题:

  1. 需要触发才能获取到执行现场,从而进行后续的步骤。这意味着你的应用必须提供一个类似于http接口的形式。
  2. 在多classloader的应用中,使用tt命令执行ognl表达式的时候,会出现ClassNotFound的错误,是因为classloader不对的问题,但是tt命令无法指定classloader

为了解决上述两个问题,只能使用一个类静态变量保存这个ApplicationContext,然后直接使用ongl命令去操作,ognl命令是可以使用-c参数指定classloader的。当然这个方法的缺陷就是侵入了应用。

下面附一个我用的保存ApplicationContext的示例:

package com.chengjf.example.context;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@Component("contextHolder")
public class ContextHolder {

    public static ApplicationContext context;

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }

}

这样,就可以直接使用ognl表达式为所欲为了。

第一步,获取合适的classloader,使用sc命令:

sc -d com.chengjf.example.context.ContextHolder

结果中可能有多个classloader,根据class-loader选择合适的classloader,记住classLoaderHash

第二部,直接使用ognl表达式,获取ApplicationContext,然后获取你想要的bean,为所欲为:

ognl -c 1fe72c5c '#[email protected]@context,#o=#c.getBean("roomService"),#o.queryById(0L)'

0、无法连接jvm

错误信息如下:

com.sun.tools.attach.AttachNotSupportedException: Unable to open socket file: target process not responding or HotSpot VM not loaded
    at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:106)
    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:85)
    at com.taobao.arthas.core.Arthas.<init>(Arthas.java:28)
    at com.taobao.arthas.core.Arthas.main(Arthas.java:123)
[ERROR] attach fail, targetPid: 6958

排查思路如下:

  1. 目标pid是否正确,可以通过jps或ps进行确认
  2. 观察pid的执行用户是否和执行arthas的用户一致,比如常见的跑jvm的是tomcat用户,但是登录用户并不是

- 阅读剩余部分 -

重新定义七点半,不知道你有没有听过这个梗。
我不知道这个梗最初是来源于哪里,我第一次见是在老罗的发布会,因为老罗的发布会总会有各种各样的问题,说是七点半开,从来没有准时过,所以说,“重新定义了七点半”。

前面几篇都讲了怎么去获取数据(参数、静态字段、返回值、执行时间、执行链路),或者怎么去执行方法(静态方法、获取实例调用方法)。

这篇就说一个大招,怎么在运行时去修改代码。

0、前置工作

有下面这样一个接口,获取开始时间:


package com.chengjf.snippet.spring.mvc.service;

public interface HelloService {
    
    /**
     * 获取开始时间
     *
     * @return
     */
    String getStartTime();
}

实现是直接返回开始时间“七点半”:

package com.chengjf.snippet.spring.mvc.service.impl;

@Service
public class HelloServiceImpl implements HelloService {
    @Override
    public String getStartTime() {
        return "七点半";
    }
}

然后整个controller调用这个getStartTime方法:

@RequestMapping("startTime")
public Object getStartTime() {
    HashMap<Object, Object> objectObjectHashMap = Maps.newHashMap();
    String result = helloService.getStartTime();
    objectObjectHashMap.put("startTime", result);
    return objectObjectHashMap;
}

返回就是说好的“七点半”:

{
    "startTime": "七点半"
}

- 阅读剩余部分 -