# 用Java实现JVM第九章《本地方法调用》

作者:小傅哥
博客:https://bugstack.cn (opens new window)

沉淀、分享、成长,让自己和他人都能有所收获!😄

# 案例介绍

本章主要介绍用java实现一些本地方法类库,并初始化本地方法,之后通过反射命令来调用本地方法。

Java虚拟机和Java类库一起构成了Java运行时环境。Java类库主要用Java语言编写,一些无法用Java语言实现的方法则使用本地语言编写,这额方法叫作本地方法。 OpenJDK类库中的本地方法是用JNI(Java Native Interface)编写的,但是要让虚拟机支持JNI规范还需要大量工作。

# 环境准备

  1. jdk 1.8.0
  2. IntelliJ IDEA Community Edition 2018.3.1 x64

# 配置信息

  1. 调试配置
    1. 配置位置:Run/Debug Configurations -> program arguments
    2. 配置内容:-Xjre "C:\Program Files\Java\jdk1.8.0_161\jre" E:\itstack\git\istack-demo\itstack-demo-jvm\itstack-demo-jvm-09\target\test-classes\org\itstack\demo\test\HelloWorld

# 代码示例

itstack-demo-jvm-09
├── pom.xml
└── src
    └── main
    │    └── java
    │        └── org.itstack.demo.jvm
    │             ├── _native
    │             │   ├── java
    │             │   │   ├── _Class.java
    │             │   │   ├── _Double.java
    │             │   │   ├── _Float.java
    │             │   │   ├── _Object.java
    │             │   │   ├── _String.java
    │             │   │   └── _System.java	
    │             │   └── sun	
    │             ├── NativeMethod.java
    │             └── Registry.java	
    │             ├── classfile
    │             │   ├── attributes   
    │             │   ├── constantpool 
    │             │   ├── ClassFile.java
    │             │   ├── ClassReader.java
    │             │   └── MemberInfo.java   
    │             ├── classpath
    │             │   ├── impl
    │             │   │   ├── CompositeEntry.java
    │             │   │   ├── DirEntry.java 
    │             │   │   ├── WildcardEntry.java 
    │             │   │   └── ZipEntry.java    
    │             │   ├── Classpath.java
    │             │   └── Entry.java   
    │             ├── classpath
    │             │   ├── base
    │             │   │   ├── BytecodeReader.java
    │             │   │   ├── ClassInitLogic.java
    │             │   │   ├── Instruction.java
    │             │   │   ├── InstructionBranch.java
    │             │   │   ├── InstructionIndex8.java
    │             │   │   ├── InstructionIndex16.java
    │             │   │   ├── InstructionNoOperands.java	
    │             │   │   └── MethodInvokeLogic.java
    │             │   ├── comparisons
    │             │   ├── constants
    │             │   ├── control
    │             │   ├── conversions
    │             │   ├── extended
    │             │   ├── loads
    │             │   ├── math
    │             │   ├── references
    │             │   │   ├── ANEW_ARRAY.java
    │             │   │   ├── ARRAY_LENGTH.java
    │             │   │   ├── CHECK_CAST.java
    │             │   │   ├── GET_FIELD.java
    │             │   │   ├── GET_STATIC.java
    │             │   │   ├── INSTANCE_OF.java
    │             │   │   ├── INVOKE_INTERFACE.java
    │             │   │   ├── INVOKE_SPECIAL.java
    │             │   │   ├── INVOKE_STATIC.java
    │             │   │   ├── INVOKE_VIRTUAL.java
    │             │   │   ├── MULTI_ANEW_ARRAY.java
    │             │   │   ├── NEW.java
    │             │   │   ├── NEW_ARRAY.java
    │             │   │   ├── PUT_FIELD.java
    │             │   │   └── PUT_STATIC.java
    │             │   ├── reserved
    │             │   │   └── INVOKE_NATIVE.java	
    │             │   ├── stack
    │             │   ├── store
    │             │   │   └── xastore
    │             │   │       ├── AASTORE.java	
    │             │   │       ├── BASTORE.java	
    │             │   │       ├── CASTORE.java	
    │             │   │       ├── DASTORE.java
    │             │   │       ├── FASTORE.java
    │             │   │       ├── IASTORE.java
    │             │   │       ├── LASTORE.java	
    │             │   │       └── SASTORE.java		
    │             │   └── Factory   
    │             ├── rtda
    │             │   ├── heap
    │             │   │   ├── constantpool
    │             │   │   ├── methodarea
    │             │   │   │   ├── Class.java    
    │             │   │   │   ├── ClassMember.java  
    │             │   │   │   ├── Field.java    
    │             │   │   │   ├── Method.java 
    │             │   │   │   ├── MethodDescriptor.java 
    │             │   │   │   ├── MethodDescriptorParser.java 
    │             │   │   │   ├── MethodLookup.java 	
    │             │   │   │   ├── Object.java   
    │             │   │   │   ├── Slots.java   
    │             │   │   │   └── StringPool.java	
    │             │   │   └── ClassLoader.java  
    │             │   ├── Frame.java
    │             │   ├── JvmStack.java
    │             │   ├── LocalVars.java
    │             │   ├── OperandStack.java
    │             │   ├── Slot.java 
    │             │   └── Thread.java
    │             ├── Cmd.java
    │             ├── Interpret.java    
    │             └── Main.java
    └── test
         └── java
             └── org.itstack.demo.test
                 └── HelloWorld.java
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

代码片段

_Class.java

package org.itstack.demo.jvm._native.java;

import org.itstack.demo.jvm._native.NativeMethod;
import org.itstack.demo.jvm._native.Registry;
import org.itstack.demo.jvm.rtda.Frame;
import org.itstack.demo.jvm.rtda.LocalVars;
import org.itstack.demo.jvm.rtda.OperandStack;
import org.itstack.demo.jvm.rtda.heap.ClassLoader;
import org.itstack.demo.jvm.rtda.heap.methodarea.Class;
import org.itstack.demo.jvm.rtda.heap.methodarea.Object;
import org.itstack.demo.jvm.rtda.heap.methodarea.StringPool;

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/30
 */
public class _Class {

    private final String jlClass = "java/lang/Class";

    public _Class() {
        Registry.register(jlClass, "getPrimitiveClass", "(Ljava/lang/String;)Ljava/lang/Class;", new NativeMethod(this, "getPrimitiveClass"));
        Registry.register(jlClass, "getName0", "()Ljava/lang/String;", new NativeMethod(this, "getName0"));
        Registry.register(jlClass, "desiredAssertionStatus0", "(Ljava/lang/Class;)Z", new NativeMethod(this, "desiredAssertionStatus0"));
        Registry.register(jlClass, "registerNatives", "()V", new NativeMethod(this, "registerNatives"));
    }

    public void registerNatives(Frame frame) {
        // do nothing
    }

    public void getPrimitiveClass(Frame frame) {
        Object nameObj = frame.localVars().getRef(0);
        String name = StringPool.goString(nameObj);

        ClassLoader loader = frame.method().clazz().loader();
        Object jClass = loader.loadClass(name).jClass();

        frame.operandStack().pushRef(jClass);
    }

    public void getName0(Frame frame) {
        Object thiz = frame.localVars().getThis();
        Class clazz = (Class) thiz.extra();

        String name = "虚拟机本地方法getName0获取类名:" + clazz.javaName();
        Object nameObj = StringPool.jString(clazz.loader(), name);

        frame.operandStack().pushRef(nameObj);
    }

    public void desiredAssertionStatus0(Frame frame) {
        frame.operandStack().pushBoolean(false);
    }

    public void isInterface(Frame frame) {
        LocalVars vars = frame.localVars();
        Object thiz = vars.getThis();
        Class clazz = (Class) thiz.extra();

        OperandStack stack = frame.operandStack();
        stack.pushBoolean(clazz.isInterface());
    }

    public void isPrimitive(Frame frame) {
        LocalVars vars = frame.localVars();
        Object thiz = vars.getThis();
        Class clazz = (Class) thiz.extra();

        OperandStack stack = frame.operandStack();
        stack.pushBoolean(clazz.IsPrimitive());
    }

}
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

_System.java

package org.itstack.demo.jvm._native.java;

import org.itstack.demo.jvm._native.NativeMethod;
import org.itstack.demo.jvm._native.Registry;
import org.itstack.demo.jvm.rtda.Frame;
import org.itstack.demo.jvm.rtda.LocalVars;
import org.itstack.demo.jvm.rtda.heap.methodarea.Class;
import org.itstack.demo.jvm.rtda.heap.methodarea.Object;

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/30
 */
public class _System {

    private final String jlSystem = "java/lang/System";

    public _System() {
        Registry.register(jlSystem, "arraycopy", "()Ljava/lang/String;", new NativeMethod(this, "arraycopy"));
        Registry.register(jlSystem,"registerNatives", "()V",new NativeMethod(this,"registerNatives"));
    }

    public void registerNatives(Frame frame) {
        // do nothing
    }
    
    public void arraycopy(Frame frame) {
        LocalVars vars = frame.localVars();
        Object src = vars.getRef(0);
        int srcPos = vars.getInt(1);
        Object dest = vars.getRef(2);
        int destPos = vars.getInt(4);
        int length = vars.getInt(4);

        if (null == src || dest == null) {
            throw new NullPointerException();
        }

        if (!checkArrayCopy(src, dest)) {
            throw new ArrayStoreException();
        }

        if (srcPos < 0 || destPos < 0 || length < 0 ||
                srcPos + length > src.arrayLength() ||
                destPos + length > dest.arrayLength()) {
            throw new IndexOutOfBoundsException();
        }

        System.arraycopy(src, srcPos, dest, destPos, length);

        //todo 待完善

    }

    public boolean checkArrayCopy(Object src, Object dest) {
        Class srcClass = src.clazz();
        Class destClass = dest.clazz();

        if (!srcClass.isArray() || !destClass.isArray()) {
            return false;
        }

        if (srcClass.componentClass().IsPrimitive() || destClass.componentClass().IsPrimitive()) {
            return srcClass == destClass;
        }

        return true;

    }

}
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

NativeMethod.java

package org.itstack.demo.jvm._native;

import org.itstack.demo.jvm.rtda.Frame;

import java.lang.reflect.Method;

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/30
 */
public class NativeMethod {

    private String methodName;
    private Object obj;

    public NativeMethod(Object obj, String methodName) {
        this.methodName = methodName;
        this.obj = obj;
    }

    public void invoke(Frame frame) {
        try {
            Method method = obj.getClass().getMethod(methodName, frame.getClass());
            method.invoke(obj, frame);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
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

Registry.java

package org.itstack.demo.jvm._native;

import org.itstack.demo.jvm._native.java.*;

import java.util.HashMap;
import java.util.Map;

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/30
 */
public class Registry {

    private static Map<String, NativeMethod> registry = new HashMap<>();

    //初始化本地方法
    public static void initNative() {
        new _Class();
        new _Double();
        new _Float();
        new _Object();
        new _String();
        new _System();
    }

    public static void register(String className, String methodName, String methodDescriptor, NativeMethod method) {
        String key = className + "~" + methodName + "~" + methodDescriptor;
        registry.put(key, method);
    }

    public static NativeMethod findNativeMethod(String className, String methodName, String methodDescriptor) {
        String key = className + "~" + methodName + "~" + methodDescriptor;
        return registry.get(key);
    }

}
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

INVOKE_NATIVE.java

package org.itstack.demo.jvm.instructions.reserved;

import org.itstack.demo.jvm._native.NativeMethod;
import org.itstack.demo.jvm._native.Registry;
import org.itstack.demo.jvm.instructions.base.InstructionNoOperands;
import org.itstack.demo.jvm.rtda.Frame;
import org.itstack.demo.jvm.rtda.heap.methodarea.Method;

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/5/2
 */
public class INVOKE_NATIVE extends InstructionNoOperands {

    @Override
    public void execute(Frame frame) {
        Method method = frame.method();
        String className = method.clazz().name();
        String methodName = method.name();
        String methodDescriptor = method.descriptor();

        NativeMethod nativeMethod = Registry.findNativeMethod(className, methodName, methodDescriptor);
        if (null == nativeMethod) {
            String methodInfo = className + "." + methodName + methodDescriptor;
            throw new UnsatisfiedLinkError(methodInfo);
        }

        nativeMethod.invoke(frame);

    }

}
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

ClassLoader.java

package org.itstack.demo.jvm.rtda.heap;

import org.itstack.demo.jvm.classfile.ClassFile;
import org.itstack.demo.jvm.classpath.Classpath;
import org.itstack.demo.jvm.rtda.heap.constantpool.AccessFlags;
import org.itstack.demo.jvm.rtda.heap.methodarea.*;
import org.itstack.demo.jvm.rtda.heap.constantpool.RunTimeConstantPool;
import org.itstack.demo.jvm.rtda.heap.methodarea.Class;
import org.itstack.demo.jvm.rtda.heap.methodarea.Object;

import java.util.HashMap;
import java.util.Map;

/*
class names:
    - primitive types: boolean, byte, int ...
    - primitive arrays: [Z, [B, [I ...
    - non-array classes: java/lang/Object ...
    - array classes: [Ljava/lang/Object; ...
*/
public class ClassLoader {

    private Classpath classpath;
    private Map<String, Class> classMap;

    public ClassLoader(Classpath classpath) {
        this.classpath = classpath;
        this.classMap = new HashMap<>();

        this.loadBasicClasses();
        this.loadPrimitiveClasses();
    }

    private void loadBasicClasses() {
        Class jlClassClass = this.loadClass("java/lang/Class");
        for (Map.Entry<String, Class> entry : this.classMap.entrySet()) {
            Class clazz = entry.getValue();
            if (clazz.jClass == null) {
                clazz.jClass = jlClassClass.newObject();
                clazz.jClass.extra = clazz;
            }
        }
    }

    private void loadPrimitiveClasses() {
        for (Map.Entry<String, String> entry : ClassNameHelper.primitiveTypes.entrySet()) {
            loadPrimitiveClass(entry.getKey());
        }
    }

    private void loadPrimitiveClass(String className) {
        Class clazz = new Class(AccessFlags.ACC_PUBLIC,
                className,
                this,
                true);
        clazz.jClass = this.classMap.get("java/lang/Class").newObject();
        clazz.jClass.extra = clazz;
        this.classMap.put(className, clazz);
    }

    public Class loadClass(String className) {
        Class clazz = classMap.get(className);
        if (null != clazz) return clazz;

        //'['数组标识
        if (className.getBytes()[0] == '[') {
            clazz = loadArrayClass(className);
        } else {
            clazz = loadNonArrayClass(className);
        }

        Class jlClazz = this.classMap.get("java/lang/Class");
        if (null != jlClazz && null != clazz) {
            clazz.jClass = jlClazz.newObject();
            clazz.jClass.extra = clazz;
        }

        return clazz;
    }

    private Class loadArrayClass(String className) {
        Class clazz = new Class(AccessFlags.ACC_PUBLIC,
                className,
                this,
                true,
                this.loadClass("java/lang/Object"),
                new Class[]{
                        this.loadClass("java/lang/Cloneable"),
                        this.loadClass("java/io/Serializable")});
        this.classMap.put(className, clazz);
        return clazz;
    }

    private Class loadNonArrayClass(String className) {
        try {
            byte[] data = this.classpath.readClass(className);
            if (null == data) {
                throw new ClassNotFoundException(className);
            }
            Class clazz = defineClass(data);
            link(clazz);
            return clazz;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private void link(Class clazz) {
        verify(clazz);
        prepare(clazz);
    }

    private void prepare(Class clazz) {
        calcInstanceFieldSlotIds(clazz);
        calcStaticFieldSlotIds(clazz);
        allocAndInitStaticVars(clazz);
    }

    private void allocAndInitStaticVars(Class clazz) {
        clazz.staticVars = new Slots(clazz.staticSlotCount);
        for (Field field : clazz.fields) {
            if (field.isStatic() && field.isFinal()) {
                initStaticFinalVar(clazz, field);
            }
        }
    }

    private void initStaticFinalVar(Class clazz, Field field) {
        Slots staticVars = clazz.staticVars;
        RunTimeConstantPool constantPool = clazz.runTimeConstantPool;
        int cpIdx = field.constValueIndex();
        int slotId = field.slotId();

        if (cpIdx > 0) {
            switch (field.descriptor()) {
                case "Z":
                case "B":
                case "C":
                case "S":
                case "I":
                    java.lang.Object val = constantPool.getConstants(cpIdx);
                    staticVars.setInt(slotId, (Integer) val);
                    break;
                case "J":
                    staticVars.setLong(slotId, (Long) constantPool.getConstants(cpIdx));
                    break;
                case "F":
                    staticVars.setFloat(slotId, (Float) constantPool.getConstants(cpIdx));
                    break;
                case "D":
                    staticVars.setDouble(slotId, (Double) constantPool.getConstants(cpIdx));
                    break;
                case "Ljava/lang/String;":
                    String goStr = (String) constantPool.getConstants(cpIdx);
                    Object jStr = StringPool.jString(clazz.loader(), goStr);
                    staticVars.setRef(slotId, jStr);
                    break;
            }
        }

    }

    private void calcStaticFieldSlotIds(Class clazz) {
        int slotId = 0;
        for (Field field : clazz.fields) {
            if (field.isStatic()) {
                field.slotId = slotId;
                slotId++;
                if (field.isLongOrDouble()) {
                    slotId++;
                }
            }
        }
        clazz.staticSlotCount = slotId;
    }

    private void calcInstanceFieldSlotIds(Class clazz) {
        int slotId = 0;
        if (clazz.superClass != null) {
            slotId = clazz.superClass.instanceSlotCount;
        }
        for (Field field : clazz.fields) {
            if (!field.isStatic()) {
                field.slotId = slotId;
                slotId++;
                if (field.isLongOrDouble()) {
                    slotId++;
                }
            }
        }
        clazz.instanceSlotCount = slotId;
    }

    private void verify(Class clazz) {
        // 校验字节码,尚未实现
    }

    private Class defineClass(byte[] data) throws Exception {
        Class clazz = parseClass(data);
        clazz.loader = this;
        resolveSuperClass(clazz);
        resolveInterfaces(clazz);
        this.classMap.put(clazz.name, clazz);
        return clazz;
    }

    private void resolveInterfaces(Class clazz) throws Exception {
        int interfaceCount = clazz.interfaceNames.length;
        if (interfaceCount > 0) {
            clazz.interfaces = new Class[interfaceCount];
            for (int i = 0; i < interfaceCount; i++) {
                clazz.interfaces[i] = clazz.loader.loadClass(clazz.interfaceNames[i]);
            }
        }
    }

    private void resolveSuperClass(Class clazz) throws Exception {
        if (!clazz.name.equals("java/lang/Object")) {
            clazz.superClass = clazz.loader.loadClass(clazz.superClassName);
        }
    }

    private Class parseClass(byte[] data) {
        ClassFile classFile = new ClassFile(data);
        return new Class(classFile);
    }


}
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230

HelloWorld.java

package org.itstack.demo.test;

/**
 * -Xjre "C:\Program Files\Java\jdk1.8.0_161\jre" E:\itstack\git\istack-demo\itstack-demo-jvm\itstack-demo-jvm-09\target\test-classes\org\itstack\demo\test\HelloWorld -verbose true -args 你好,java版虚拟机v1.0,欢迎你的到来。
 */
public class HelloWorld {

    public static void main(String[] args) {
        System.out.println(byte.class.getName()); // byte
        System.out.println(void.class.getName()); // void
        System.out.println(boolean.class.getName()); // boolean
        System.out.println(char.class.getName()); // char
        System.out.println(short.class.getName()); // short
        System.out.println(int.class.getName()); // int
        System.out.println(long.class.getName()); // long
        System.out.println(float.class.getName()); // float
        System.out.println(double.class.getName()); // double
        System.out.println(Object.class.getName()); // java.lang.Object
        System.out.println(int[].class.getName()); // [I
        System.out.println(int[][].class.getName()); // [[I
        System.out.println(Object[].class.getName()); // [Ljava.lang.Object;
        System.out.println(Object[][].class.getName()); // [[Ljava.lang.Object;
    }

}
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

# 测试结果

虚拟机本地方法getName0获取类名:byte
虚拟机本地方法getName0获取类名:void
虚拟机本地方法getName0获取类名:boolean
虚拟机本地方法getName0获取类名:char
虚拟机本地方法getName0获取类名:short
虚拟机本地方法getName0获取类名:int
虚拟机本地方法getName0获取类名:long
虚拟机本地方法getName0获取类名:float
虚拟机本地方法getName0获取类名:double
虚拟机本地方法getName0获取类名:java.lang.Object
虚拟机本地方法getName0获取类名:[I
虚拟机本地方法getName0获取类名:[[I
虚拟机本地方法getName0获取类名:[Ljava.lang.Object;
虚拟机本地方法getName0获取类名:[[Ljava.lang.Object;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

微信搜索「bugstack虫洞栈」公众号,关注后回复「用Java实现jvm源码」获取本文源码&更多原创专题案例!