0%

基本概念

JDK:包括编译器(javac.exe)、开发工具(javadoc.exe、jar.exe、keytool.exe、jconsole.exe)和更多的类库(如tools.jar)等。总结就是Java语言、Java虚拟机、Java API类库
JRE: 支持java运行的基本环境

image

JIT:just in time

运行时数据区:
image

JVM的内存结构

程序计数器:

当前代码行号指示器。各个线程计数器互不影响,独立存储。如果是执行Java方法,是虚拟机字节码地址。如果是native方法,计数器为空。唯一一个没有OOM的区域。是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。

程序计数器是指CPU中的寄存器,它保存的是程序当前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址),当CPU需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令。 

在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。

Java虚拟机栈:

描述Java方法执行的内存模型:每个执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等。每个方法调用到执行完成过程,对应一个栈帧在虚拟机栈中入栈到出栈的过程。线程私有

  • 局部变量表:

    存放编译期可知的各种基本数据类型、对象引用类型(一个指向对象起始地址的引用指针)、returnAddress类型(一个字节码指令地址)。其中64位长度long和double会占用2个局部变量空间,其余占一个。局部变量表所需内存大小是在编译期间完成分配的,方法运行期间不会改变。如果栈深度大于虚拟机允许的深度,将抛出StackOverflowError异常;如果虚拟机栈动态扩张时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

Read more »

Filter解析

通过Filter接口我们可以轻松地实现服务提供方和消费方的拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@SPI
public interface Filter {

/**
* do invoke filter.
* <p>
* <code>
* // before filter
* Result result = invoker.invoke(invocation);
* // after filter
* return result;
* </code>
*
* @param invoker service
* @param invocation invocation.
* @return invoke result.
* @throws RpcException
* @see com.alibaba.dubbo.rpc.Invoker#invoke(Invocation)
*/
Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;

}

我们注意到ReferenceConfig和ServiceConfig的共同父类AbstractInterfaceConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// filter
protected String filter;
//key为reference.filter
@Parameter(key = Constants.REFERENCE_FILTER_KEY, append = true)
public String getFilter() {
return filter;
}

//Parameter作用是将filter解析并放入key为service.filter的url
@Parameter(key = Constants.SERVICE_FILTER_KEY, append = true)
public String getFilter() {
return super.getFilter();
}

//xxx,yyy
public void setFilter(String filter) {
checkMultiExtension(Filter.class, "filter", filter);
this.filter = filter;
}
Read more »

new Thread()不会启动一个新线程,而是调用init的这个方法设置一些东西,比如设置线程组,栈大小,拿到线程名,tid。创建一个ThreadLocalMap。
而start方法才会启动一个线程。

其实start方法的核心就是调用了

1
private native void start0();

start0 -> JVM_StartThread

虾面是openjdk 的jvm源码http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/75d40493551f/src/share/vm/prims/jvm.cpp

Read more »

  • HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。学过数据结构的同学都知道,解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。
  • JDK1.6中HashMap采用的是位桶+链表的方式,即我们常说的散列链表的方式,而JDK1.8中采用的是位桶+链表/红黑树的方式,也是非线程安全的。当某个位桶的链表的长度达到某个阀值的时候,这个链表就将转换成红黑树。也就是说原来如果hash不理想,所有都落入同一个桶就变成的单链表,现在只要大于8个在同一个桶里面,就会转化为红黑树,提升效率。
  • 原来jdk1.7中resize是通过链表头插入的,jdk1.8是通过链表尾插入。可能有人会觉得1.7这样会更快,但是这样容易产生hash碰撞。如果它知道我们用的是哈希算法,它可能会发送大量的请求,导致产生严重的哈希碰撞。然后不停的访问这些 key就能显著的影响服务器的性能,这样就形成了一次拒绝服务攻击(DoS)
Read more »

BeanPostProcessor的执行顺序
1、如果使用BeanFactory实现,非ApplicationContext实现,BeanPostProcessor执行顺序就是添加顺序。

2、如果使用的是AbstractApplicationContext(实现了ApplicationContext)的实现,则通过如下规则指定顺序。
2.1、PriorityOrdered(继承了Ordered),实现了该接口的BeanPostProcessor会在第一个顺序注册,标识高优先级顺序,即比实现Ordered的具有更高的优先级;
2.2、Ordered,实现了该接口的BeanPostProcessor会第二个顺序注册;

int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;//最高优先级
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;//最低优先级

即数字越小优先级越高,数字越大优先级越低,如0(高优先级)——1000(低优先级)

2.3、无序的,没有实现Ordered/ PriorityOrdered的会在第三个顺序注册;
2.4、内部Bean后处理器,实现了MergedBeanDefinitionPostProcessor接口的是内部Bean PostProcessor,将在最后且无序注册。

Read more »

LoadBalance层

配置

服务端服务级别

1
<dubbo:service interface="..." loadbalance="roundrobin" />

客户端服务级别

1
<dubbo:reference interface="..." loadbalance="roundrobin" />

服务端方法级别

1
2
3
<dubbo:service interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>

客户端方法级别

1
2
3
<dubbo:reference interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {

/**
* select one invoker in list.
*
* @param invokers invokers.
* @param url refer url
* @param invocation invocation.
* @return selected invoker.
*/
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;

}
Read more »

在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。

image

各节点关系:

  • 这里的 InvokerProvider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息

  • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更

  • ClusterDirectory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个

  • Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等

  • LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选

    Read more »

当service被调用时,通过代理最后调用的是FailoverClusterInvoker的invoke

sequencediagram111

proxy 服务代理层

1
2
3
4
5
6
public class JavassistProxyFactory extends AbstractProxyFactory {
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
}

真正的service通过Javassist代理被代理成代理子类

InvokerInvocationHandler

Read more »

registry层

通过dubbo解析一我们分析了整个ServiceConfig,留下了RegistryProtocol这个没有分析,接下来我们来分析分析RegistryProtocol

reg333321

在ServiceConfig的loadRegistries方法里面,当spring容器初始化,服务提供方(消费方也差不多)暴露过程,就会拼接生成registryURL

registryURL:

registry://localhost:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&pid=9966&qos.port=22222&registry=zookeeper&timestamp=1522650082230

Read more »