Skip to content

2019 05 10 用Java实现JVM第九章《本地方法调用》

fuzhengwei edited this page May 16, 2020 · 1 revision

作者:小傅哥
博客:https://bugstack.cn - 原创系列专题

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

案例介绍

本章主要介绍用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

代码片段

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

}

_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;

    }

}

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

}

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

}

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

    }

}

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


}

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

}

测试结果

虚拟机本地方法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;

上一篇:用Java实现JVM第八章《数组和字符串》

下一篇:用Java实现JVM第十章《异常处理》

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

📝 首页

🌏 知识星球码农会锁

实战项目:「DDD+RPC分布式抽奖系统」、专属小册、问题解答、简历指导、架构图稿、视频课程

🐲 头条

⛳ 目录

  1. 源码 - :octocat: 公众号:bugstack虫洞栈 文章所涉及到的全部开源代码
  2. Java
  3. Spring
  4. 面向对象
  5. 中间件
  6. Netty 4.x
  7. 字节码编程
  8. 💯实战项目
  9. 部署 Dev-Ops
  10. 📚PDF 下载
  11. 关于

💋 精选

🐾 友链

建立本开源项目的初衷是基于个人学习与工作中对 Java 相关技术栈的总结记录,在这里也希望能帮助一些在学习 Java 过程中遇到问题的小伙伴,如果您需要转载本仓库的一些文章到自己的博客,请按照以下格式注明出处,谢谢合作。

作者:小傅哥
链接:https://bugstack.cn
来源:bugstack虫洞栈

2021年10月24日,小傅哥 的文章全部开源到代码库 CodeGuide 中,与同好同行,一起进步,共同维护。

这里我提供 3 种方式:

  1. 提出 Issue :在 Issue 中指出你觉得需要改进/完善的地方(能够独立解决的话,可以在提出 Issue 后再提交 PR )。
  2. 处理 Issue : 帮忙处理一些待处理的 Issue
  3. 提交 PR: 对于错别字/笔误这类问题可以直接提交PR,无需提交Issue 确认。

详细参考:CodeGuide 贡献指南 - 非常感谢你的支持,这里会留下你的足迹

  • 加群交流 本群的宗旨是给大家提供一个良好的技术学习交流平台,所以杜绝一切广告!由于微信群人满 100 之后无法加入,请扫描下方二维码先添加作者 “小傅哥” 微信(fustack),备注:加群。
微信:fustack

  • 公众号(bugstack虫洞栈) - 沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。
公众号:bugstack虫洞栈

感谢以下人员对本仓库做出的贡献或者对小傅哥的赞赏,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR,请与我联系。

Clone this wiki locally