【JVM】JVM 字节码指令集

前言

曾经:源代码 -> 经过编译 -> 本地机器码

Java:源代码 -> 经过编译 -> 字节码 -> 解释器 -> 本地机器码

image-20210508090007130

字节码:与操作系统和机器指令集无关的,平台中立的程序编译后的存储格式

字节码是无关性的基石

平台无关性的基石:

  • 所有平台都统一支持字节码
  • 不同的Java虚拟机都可以执行平台无关的字节码

因此实现了一次编译,到处运行

语言无关性的基石:

  • Java虚拟机
  • 字节码

Java虚拟机不是只可以执行Java源代码编译而成的字节码,只要符合要求(安全…)的字节码,它都可以执行。因此Kotlin等语言也可以运行在Java虚拟机上。

Class 字节码文件结构

文件格式存取数据的类型

  • 无符号数 : u1,u2,u4,u8代表1,2,4,8个字节的无符号数(可以表示数字,UTF-8的字符串,索引引用…)
  • 表: 由n个无符号数或n个表组成(命名以_info结尾)

初识 Class 文件格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
private int m;
private final int CONSTANT=111;

public int inc() throws Exception {
int x;
try {
x = 1;
return x;
}catch (Exception e){
x = 2;
return x;
}finally{
x = 3;
}
}
}

使用可视化工具classpy查看反编译的结果

image-20201107172118033

每个集合前都有一个计数器来统计集合中元素的数量

阅读全文

【数据结构】栈与队列

栈和队列的转换

使用两个栈实现队列结构

思路:A 栈用来添加数据,B 栈用来弹出数据

  • append 时直接加入到 A 栈中
  • delete 时,先判断 B 栈是否为空:
    • 如果为空,A 栈都弹出到 B 里,然后弹出 B 顶
    • 如果不为空,则直接弹出 B 顶

代码:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
class CQueue {
// 两个栈,
// 1. append 时直接加入到 A 栈
// 2. delete 时,先判断 B 栈是否为空,
// 2.1 如果为空,A 栈都弹出到 B 里,然后弹出 B 顶。
// 2.2 如果不为空,则直接弹出 B 顶
// 相当于 A 栈专门 appen。 B 栈专门 delete

private Stack<Integer> queueA;
private Stack<Integer> queueB;

public CQueue() {
queueA = new Stack<>();
queueB = new Stack<>();
}

public void appendTail(int value) {
// 向 A 栈中添加元素
queueA.push(value);
}

public int deleteHead() {
// 先判断 B 栈是否为空
// 1. 如果为空,则将 A 栈的都弹出到 B 中
if (queueB.isEmpty()) {
while (!queueA.isEmpty()) {
queueB.push(queueA.pop());
}
// 然后判断 B 是否仍为空,如果还为空,说明压根没元素
if (queueB.isEmpty()) {
return -1;
} else {
return queueB.pop();
}
} else {
// 1. 如果不为空,则直接弹出 B 的栈顶
return queueB.pop();
}
}
}

使用两个队列实现栈结构

思路:准备两个队列 queuehelp

  • 添加时,加入到 queue
  • 删除时,将 queue 中的数据都存到 help 中,只剩下一个弹出。弹出后再交换 queuehelp 的引用,使得 queue 始终指向存放数据的队列。help 始终存放空队列

代码:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static class TwoQueuesStack {
private Queue<Integer> queue;
private Queue<Integer> help;

public TwoQueuesStack() {
queue = new LinkedList<Integer>();
help = new LinkedList<Integer>();
}

public void push(int pushInt) {
queue.add(pushInt);
}

public int peek() {
if (queue.isEmpty()) {
throw new RuntimeException("Stack is empty!");
}
while (queue.size() != 1) {
help.add(queue.poll());
}
int res = queue.poll();
help.add(res);
swap();
return res;
}

public int pop() {
if (queue.isEmpty()) {
throw new RuntimeException("Stack is empty!");
}
while (queue.size() > 1) {
help.add(queue.poll());
}
int res = queue.poll();
swap();
return res;
}

private void swap() {
Queue<Integer> tmp = help;
help = queue;
queue = tmp;
}
}
阅读全文

【Redis】Redis 分布式锁

image-20210913131720145

分布式锁简介

随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。

分布式锁可以用来解决缓存击穿的问题,在大量请求访问某个可能会过期的 key 前,先加上分布式锁,这样就能保证数据库只会被访问一次,从而减轻了数据库的压力。

分布式锁主流的实现方案:

  • 基于数据库实现分布式锁
  • 基于缓存(Redis等,将key存储在缓存中)
  • 基于 ZooKeeper(将key存储在ZooKeeper中)

每一种分布式锁解决方案都有各自的优缺点:

  • 高性能:Redis(AP),但其无法保证主从机器上的缓存数据是一致的,可能主机刚保存了某个锁,还未同步给从机,自己就宕机了。在哨兵机制选举出了另一台主机后,其内并不存在该锁,故此前加的分布式锁失效,但其能保证高性能,而不像ZooKeeper一样主从同步时服务无法访问。
  • 可靠性:ZooKeeper(CP),能够保证数据的一致性,主机收到的加锁消息会在同步给所有从机后再一起添加到缓存中,此时即可以保证分布式锁数据高度一致,但是缺点是同步期间服务无法访问,性能降低。

本文将介绍基于Redis的分布式锁实现方式。

阅读全文

【Redis】Redisson

image-20210913131720145

Redisson 简介

官网文档上详细说明了不推荐使用 setnx 来实现分布式锁,应该参考 the Redlock algorithm 的实现

image-20201101050725534

the Redlock algorithm:https://redis.io/topics/distlock

在 Java 语言环境下使用 Redisson,即 Redisson 是 Redlock 在 Java 中的实现

image-20201101050924914

官方文档:https://github.com/redisson/redisson/wiki/目录

Redisson是一个在 Redis 的基础上实现的 Java 驻内存数据网格(In-Memory Data Grid),提供了分布式和可扩展的 Java 数据结构。其特点:

  • 基于 Netty 实现,采用非阻塞 IO,性能高
  • 支持异步请求
  • 支持连接池、pipeline、LUA Scripting、Redis Sentinel、Redis Cluster 不支持事务,官方建议以 LUA Scripting 代替事务
  • 主从、哨兵、集群都支持。Spring 也可以配置和注入 RedissonClient。
  • 实现分布式锁:在 Redisson 里面提供了更加简单的分布式锁的实现。
阅读全文

【JUC】JUC 常用锁

JUC 简介

JUC 就是 java.util .concurrent 工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的。

可重入锁

synchronizedLockReentrantLock)都是可重入锁(又称为递归锁)。synchronized是隐式锁,不用手工上锁与解锁,而 LockReentrantLock)为显式锁,需要手工上锁与解锁。

可重入锁作用:避免某一对象递归调用同一方法时产生死锁,一个线程拿到了外层的锁就可以无视内部的锁。前提:必须是同一把锁

有了可重入锁之后,破解第一把锁之后就可以一直进入到内层结构,直接无视内部的其他锁。也就是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块(只能进入同一把锁的同步代码块)。

可重入的原理:AQS 里每次获取锁的时候,看下当前维护的那个线程和当前请求的线程是否一样,一样就可重入了

阅读全文

【JVM】JVM 概述

JVM 与 Java 体系结构

前言

image-20200704111417472

计算机系统体系对我们来说越来越远,在不了解底层实现方式的前提下,通过高级语言很容易编写程序代码。但事实上计算机并不认识高级语言。

image-20200704112119729

阅读全文

【MyBatis】Spring Boot 整合 MyBatis

img

整合 Druid 数据源

导入 JDBC 场景

在Maven中导入JDBC场景spring-boot-starter-data-jdbc

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

导入该场景后,将出现数据源Hikari、JDBC和事务等依赖:

image-20210804152401123

阅读全文

【Redis】Spring Boot 整合 Redis

image-20210913131720145

导入依赖

导入Redis的starter场景依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- spring2.X 集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

<!--导入jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

Redis相关的组件都由 RedisAutoConfiguration 完成配置和注入:

image-20210914141335348

其绑定了 spring.redis 属性,并且向容器中注入了:

  • RedisTemplate<Object, Object>
  • StringRedisTemplate,其 key-value 都是String类型

提供了两种 Redis 客户端:

  • Lettuce:默认,基于Netty框架的客户端,线程安全,性能较好。
  • Jedis
阅读全文

【Docker】Docker 基础

Docker 简介

Docker 理念

Docker 是基于Go语言实现的云开源项目。Docker 的主要目标是“Build, Ship[ and Run Any App,Anywhere",也就是通过对应用组件的封装、分发、部署、运行等生命期的管理,使用户的APP (可以是一个WEB应用或数据库应用等等)及其运行环境能够做到“一次封装,到处运行”。

Linux容器技术的出现就解决了这样一一个问题,而Docker就是在它的基础上发展过来的。将应用运行在Docker容器上面,而Docker容器在任何操作系统上都是一致的,这就实现了跨平台、跨服务器。只需要一次配置好环境,换到别的机器上就可以一键部署好,大大简化了操作

一句话:Docker 是解决了运行环境和配置问题的软件容器,方便做持续集成并有助于整体发布的容器虚拟化技术

之前的虚拟机技术

虚拟机(virtual machine)就是带环境安装的一种解决方案。

它可以在一种操作系统里面运行另一种作系统,比如在Windows系统里面运行Linux系统。应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。这类虚拟机完美的运行了另一套系统,能够使应用程序,操作系统和硬件三者之间的逻辑不变。

虚拟机的缺点:

  • 资源占用多
  • 冗余步骤多
  • 启动慢

容器虚拟化技术

由于前面虛拟机存在这些缺点,Linux 发展出了另一种虚拟化技术: Linux 容器(Linux Containers,缩为LXC)。

Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。有了容器,就可以将软件运行所的所有资源打包到一个隔离的容器中。容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源和设置。系统因此而变得高效轻量并保证部署在任何环境中的软件都能始终如一地运行。

比较Docker和传统虚拟化方式的不同之处:

  • 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程。
  • 而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机为轻便。
  • 每个容器之间互相隔离,每个容器有自己的文件系统,容器之间进程不会相互影响,能区分计算资源。
阅读全文

【Redis】Redis 常见问题

image-20210913131720145

Redis 介绍

说说什么是 Redis?

Redis 是一个开源(BSD 许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,HyperLogLogs 等数据类型。内置复制、Lua 脚本、LRU 收回、事务,以及不同级别磁盘持久化功能,同时通过 Redis Sentinel 提供高可用,通过 Redis Cluster 提供自动分区。根据月度排行网站 DB-Engines 的数据,Redis 是最流行的键值对存储数据库。

Redis 全称为:Remote Dictionary Server(远程数据服务),是一个基于内存且支持持久化的高性能 key-value 数据库。具备以下三个基本特征:

  • 多数据类型
  • 持久化机制
  • 主从同步

Redis 有什么优点和缺点?

Redis 优点

  • 读写性能优异, Redis 能读的速度是 110000 次 /s,写的速度是 81000 次 /s。
  • 支持数据持久化,支持 AOF 和 RDB 两种持久化方式。
  • 支持事务,Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作合并后的原子性执行。
  • 数据结构丰富,除了支持 string 类型的 value 外还支持 hash、set、zset、list 等数据结构。
  • 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。

Redis 缺点

  • 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。
  • Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的 IP 才能恢复。
  • 主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP 后还会引入数据不一致的问题,降低了系统的可用性。
  • Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
阅读全文