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

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写入网卡驱动中,传输到网卡的驱动。

可以看到,传统的IO的读写,数据会经历4次内存的拷贝,这种拷贝拷贝会带来资源的浪费和效率的底下。


如何实现零拷贝


内存映射方式I/O

阅读更多

Java中实现顺序IO

顺序IO和随机IO


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

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


Java中的随机读写


在Java中读写文件的方式有很多种,先总结以下3种方法:

  1. FileWriter和FileReader

    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
    public static void fileWrite(String filePath, String content) {
    File file = new File(filePath);
    //创建FileWriter对象
    FileWriter writer = null;
    try {
    //如果文件不存在,创建文件
    if (!file.exists())
    file.createNewFile();
    writer = new FileWriter(file);
    writer.write(content);//写入内容
    writer.flush();
    writer.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    public static void fileRead(String filePath) {
    File file = new File(filePath);
    if (file.exists()) {
    try {
    //创建FileReader对象,读取文件中的内容
    FileReader reader = new FileReader(file);
    char[] ch = new char[1];
    while (reader.read(ch) != -1) {
    System.out.print(ch);
    }
    reader.close();
    } catch (IOException ex) {
    ex.printStackTrace();
    }

    }
    }

阅读更多

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();
}
}

如果直接运行,可以发现没有任何结果,需要在META-INF\services创建一个文件xxx.xxx.IService(是接口类的全类名) ,内容是两个子类的全类名:

1
2
learnJava.rz08.DefalutService
learnJava.rz08.LogService

再次运行结果:

1
2
21:55:48,873  INFO [main] (RzLogger.java:12) - 默认服务
21:55:48,877 INFO [main] (RzLogger.java:12) - 日志服务
阅读更多

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

发现addShutdownHook

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

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
 Runtime.getRuntime().addShutdownHook(new Thread(wrapper::destroy));

```

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

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

具体的使用Demo如下:

``` java
public class AppMain {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit01));
Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit02));
Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit03));
while (true){
System.out.println(".....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}
public static void exit01(){
System.out.println("exit01");
}
public static void exit02(){
System.out.println("exit02");
}
public static void exit03(){
System.out.println("exit03");
}
}

```

执行当执行退出的时候:
![程序退出](/img/assets/67/01.png)




#### addShutdownHook的使用场景

addShutdownHook的使用场景很多, 尤其是在通信的模块功能,比如Netty为例,当服务端或者客户端异常断开的时候,需要告诉对方释放资源,从而保证不会触发异常机制。

例如分布式队列,RPC框架都需要在异常后,释放相关资源。


#### addShutdownHook使用注意几点

1. 在多次使用addShutdownHook增加退出的方法是,由于是开一个线程,所以不能保证方法的执行是按顺序的。 所以尽量保证方法在一个方法里面完成。

2. 在释放的时候不应该增加阻塞的方法,否者会导致推出时阻塞,导致无法退出

``` java
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
while (true) {
//
}
}));

hooks相关源码

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
 public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}

public boolean removeShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
return ApplicationShutdownHooks.remove(hook);
}

```

``` java

class ApplicationShutdownHooks {
/* The set of registered hooks */
private static IdentityHashMap<Thread, Thread> hooks;
static {
try {
Shutdown.add(1 /* shutdown hook invocation order */,
false /* not registered if shutdown in progress */,
new Runnable() {
public void run() {
runHooks();
}
}
);
hooks = new IdentityHashMap<>();
} catch (IllegalStateException e) {
// application shutdown hooks cannot be added if
// shutdown is in progress.
hooks = null;
}
}


private ApplicationShutdownHooks() {}

/* Add a new shutdown hook. Checks the shutdown state and the hook itself,
* but does not do any security checks.
*/
static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");

if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");

if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");

hooks.put(hook, hook);
}

/* Remove a previously-registered hook. Like the add method, this method
* does not do any security checks.
*/
static synchronized boolean remove(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");

if (hook == null)
throw new NullPointerException();

return hooks.remove(hook) != null;
}

/* Iterates over all application hooks creating a new thread for each
* to run in. Hooks are run concurrently and this method waits for
* them to finish.
*/
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}

for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
while (true) {
try {
hook.join();
break;
} catch (InterruptedException ignored) {
}
}
}
}
}


```

#### Hooks管理问题

在很多开源的框架中,有的Hooks反而会起到反作用,我们怎么样能够移除我们不需要的Hooks呢?

在看了上面的代码,我们发现`ApplicationShutdownHooks` 不是一个`public`的类,JDK也没有提供对外管理的方法,只有`add`和`remove`方法。

通过分析`ApplicationShutdownHooks`类源码的结构,我们可以发现,用来存放hooks的文件的是一个`IdentityHashMap`容器,而且是一个静态的属性。

**从反射入手**

既然是一个变量,可以通过反射来获得对应的Hooks的值,代码如下:

``` java
String className = "java.lang.ApplicationShutdownHooks";
Class<?> clazz = Class.forName(className);
Field field = clazz.getDeclaredField("hooks");
field.setAccessible(true);
IdentityHashMap<Thread, Thread> map = (IdentityHashMap<Thread, Thread>) field.get(clazz);
for (Thread thread : map.keySet()) {
RzLogger.info("found shutdownHook: " + thread.getName());
}
```

增加三个Hook验证:

``` java

Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit01));
Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit02));
Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit03));


```

``` cte

23:33:11,102 INFO [main] (RzLogger.java:12) - found shutdownHook: Thread-2
23:33:11,106 INFO [main] (RzLogger.java:12) - found shutdownHook: Thread-0
23:33:11,106 INFO [main] (RzLogger.java:12) - found shutdownHook: Thread-1
exit03
exit01
exit02

收获

  1. ShutDown的使用,在程序退出后清理现场
  2. 一步一步去理解代码的原理和特性,细节很重要
阅读更多

Unsage类的使用

Java是以安全著称,但在Java中有一个类是一个Bug级别的存在,那就是Unsafe. 前面已经说过Unsafe在java中的使用,此处我们直接说用法:

避免初始化

当你想跳过对象初始化的阶段,或者绕过构造函数的检查,去实例化没有任何公共构造函数的类,可以使用allocateInstance:

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

class A {
private long a; // not initialized value

public A() {
this.a = 1; // initialization
}

public long a() { return this.a; }
}

```
使用构造函数、反射和unsafe初始化它,将得到不同的结果。

``` java
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
A o1 = new A(); // constructor
System.out.println(o1.a()); // prints 1

A o2 = A.class.newInstance(); // reflection
System.out.println(o2.a()); // prints 1

A o3 = (A) UnsafeUtils.getUnsafe().allocateInstance(A.class); // unsafe
System.out.println(o3.a()); // prints 0
}

内存崩溃(Memory corruption)

我们可以使用Unsafe去做一些绕过安全的技术:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   class Guard {
private int ACCESS_ALLOWED = 1;

public boolean giveAccess() {
return 42 == ACCESS_ALLOWED;
}
}
```


当客户端调用`giveAccess`代码是,始终返回的都是`false`.使用`Unsafe`可以绕过权限:

``` java
Guard guard=new Guard();
System.out.println("修改前:"+guard.giveAccess());
Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption
System.out.println("修改后:"+guard.giveAccess());

计算对象的大小

sizeOf是返回对象的自身内存大小。

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
 public static long sizeOf(Object o) {
Unsafe u = UnsafeUtils.getUnsafe();
HashSet<Field> fields = new HashSet<Field>();
Class c = o.getClass();
while (c != Object.class) {
for (Field f : c.getDeclaredFields()) {
if ((f.getModifiers() & Modifier.STATIC) == 0) {
fields.add(f);
}
}
c = c.getSuperclass();
}
// get offset
long maxSize = 0;
for (Field f : fields) {
long offset = u.objectFieldOffset(f);
if (offset > maxSize) {
maxSize = offset;
}
}

return ((maxSize/8) + 1) * 8; // padding
}

```

如果只是对象类的结构大小,那么可以更简单的实现:

```java
public static long sizeOf(Object object){
return UnsafeUtils.getUnsafe().getAddress(
normalize(UnsafeUtils.getUnsafe().getInt(object, 4L)) + 12L);
}
private static long normalize(int value) {
if(value >= 0) return value;
return (~0L >>> 32) & value;
}

阅读更多

手写JDK动态代理

反射动态代理

为了能够更好实现AOP的思想,在Java中有了动态代理概念,与动态代理相对应的就是静态代理,首先看下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface IWoker {
String sayHello(String name,String code) ;
}

public class Worker implements IWoker{
@Override
public String sayHello(String name,String code) {
System.out.println("hello");
}
}

```

如果我们想在`sayHello`的方法前后分别都打印一条日志,实现AOP的思想,那使用静态代理的方法如下:

``` java
public class WorkerProxy {
public void aopWorker(IWoker doWoker){
System.out.println("before");
doWoker.sayHello("Rz","Mz");
System.out.println("end");
}
}

定义一个WorkProxy的代理类,在执行方法的时候,使用代理类来执行:

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
Worker worker = new Worker();
new WorkerProxy().aopWorker(worker);
```

静态代理很好立即,即是使用组合类的方式来实现代理模式。这种形式的就是过于单一,代理的接口多的时候,就对应会产生很多代理类。

而java的动态代理就是为了解决这个问题,动态代理实现的代码如下:


``` java
//动态代理类
public class DynaminProxy implements InvocationHandler {
private Object subject;
public DynaminProxy(Object subject)
{
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object obj = method.invoke(subject, args);
System.out.println("end");
return obj;
}
}

```

代码调用:

``` java

IWoker proxy = (IWoker) Proxy.newProxyInstance(
IWoker.class.getClassLoader(),
new Class[]{IWoker.class},
new DynaminProxy(worker));
proxy.sayHello();

```

从上面的代码可以看出,动态代理返回来的`proxy`对象很奇怪,通过调试可以看到对应的类型为:


![Proxy对象](/img/assets/62/01.jpg)



对于`Proxy`对象可以通过java语法分析如下:

1. `Proxy`对象的类一定是实现了`IWorker`接口
2. 执行方法的时候,是通过调用`DynaminProxy`中`invoke`方法来调用真正的方法

根据上面的推断,`proxy`的对象的代码应该是:

``` java
public class $Proxy implements IWoker {
private DynaminProxy h;
public $Proxy(DynaminProxy dynaminProxy) {
this.h = dynaminProxy;
}
@Override
public String sayHello(String name,String code) {
try {
Method m = IWoker.class.getDeclaredMethod("sayHello", name.getClass(),code.getClass);
Object invoke = this.h.invoke(null, m, new Object[]{name,code});
} catch (Throwable ex) {
ex.printStackTrace();
}
}
}
```

使用代码进行的调用的时候,运行结果一致。在刚刚的代码中,如何实现的这个功能的呢?

``` java
Proxy.newProxyInstance(IWoker.class.getClassLoader(), new Class[]{IWoker.class}, new DynaminProxy(worker));

再观察动态代理的创建方式,有三个参数ClassLoader,Class的数组和最终的是执行的代码。

根据上面的代码,可以先确定代码实现的思路:

  1. 动态创建动态代理类$Proxy.java
  2. 编译成$Proxy.class
  3. 使用ClassLoader加载对应的类,创建对象返回。

根据上面的思路,可以编写代码如下:

创建$Proxy.java类,实现动态生成类

阅读更多