0.Java程序是如何运行的
Java是一款跨平台的开发语言,如下图所示,Java程序在运行时经历了编译
->解释
->运行
这三大过程。编译
过程中使用的也就是javac
,而解释和运行则依赖于JVM
(Java虚拟机)。正是因为有了不同平台下的JVM,因而java程序才能够跨平台运行。
当.java
文件被编译成.class
文件后,JVM
负责对Class文件进行解释和运行。.class
文件中的内容是一种介于用户语言和机器语言之间的被称为Java bytecode
(Java字节码)的内容。编写如下LearnJVM.java
文件:
public class LearnJVM { public static void main(String argvs[]){ System.out.println("Hello JVM!"); } }
使用javac工具对上述代码进行编译便可生成包含Java字节码的class文件。JDK中提供了反汇编器javap
,javap产生的结果是Java汇编语言。
Java汇编指令由操作码
(OpCode)和操作数
(Operand)组成,JVM在执行的其实就是上面生成的这些java字节码。JVM执行class文件的过程如下图所示:
如上图所示,JVM中由CLass Loader
负责将class文件读入内存中,并为运行前进行相应的处理。CLass Loader
完成的工作主要包含以下三步:
- Loading :读取class文件并加载到JVM的内存中
- Linking:链接过程包含以下三个子步骤
-
- Verifying :检查读取的class文件是否符合规范
-
- Preparing :准备数据结构用于存储类信息
-
- Resolving :将类的常量替换为直接引用
- Initialization :执行类的初始化程序
ClassLoader类定义于java.lang.ClassLoader
中,java中类初始化时调用该类并返回一个java.lang.Class
实例。主要包含如下核心方法:
- loadClass:加载指定的Java类
- findClass:查找指定的Java类
- findLoadedClass:查找JVM已经加载过的类
- defineClass:定义一个Java类
- resolveClass:链接指定的Java类
2.java类的动态加载
java开发的时候如果需要使用某个类可以对类进行动态加载,需要使用某个类就加载。类似C语言开发过程中动态加载dll的loadLibrary方法。常用的LoadLibrary方法主要包含以下两种:
假如要加载的类为LearnJVM.class
,其源码如下:
public class LearnJVM { public static void SayHi(){ System.out.println("Hello Loader!"); } public static void main(String argvs[]){ System.out.println("Hello JVM!"); } }
则我们可以使用如下两种方法动态加载该类,并实例化该类,从而调用其内部方法。
1.反射加载
//反射加载 public class ClassLoaderTest { public static void main(String argvs[]) throws ClassNotFoundException{ ClassLoaderTest myClassLoaderTest = new ClassLoaderTest(); Class myTest = Class.forName("LearnJVM"); LearnJVM l = new LearnJVM(); l.SayHi(); } }
2.ClassLoader加载
public class ClassLoaderTest { public static void main(String argvs[]) throws ClassNotFoundException{ ClassLoaderTest myClassLoaderTest = new ClassLoaderTest(); Class myTest = myClassLoaderTest.getClass().getClassLoader().loadClass("LearnJVM"); LearnJVM l = new LearnJVM(); l.SayHi(); } }
3.自定义ClassLoader
如下图所示ClassLoader有很多,不同的ClassLoader负责加载不同的类。并且每一个ClassLoader都有一个父加载器
(不是父类)。
类加载器加载类的时候是通过委托模式
进行的,如下图所示:
具体ClassLoader的加载过程可以参考一看你就懂,超详细java中的ClassLoader详解一文。总之一个核心思想就是不同的加载器有不同的加载路径,并且我们可以自己实现一个自定义的加载器从而实现任意路径的Class加载。自定义加载器代码如下:
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URLClassLoader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class ClassLoaderTest extends ClassLoader { @Override public Class<?> findClass(String name) throws ClassNotFoundException { String fileName = name; if (fileName == "/tmp/LearnJVM.class") { try { File file = new File(fileName); FileInputStream is = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; try { while ((len = is.read()) != -1) { bos.write(len); } } catch (IOException e) { e.printStackTrace(); } byte[] data = bos.toByteArray(); is.close(); bos.close(); return defineClass("LearnJVM", data, 0, data.length);//这里写了个硬编码的name } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { return super.findClass(name); } return super.findClass(name); } public static void main(String argvs[]) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { ClassLoaderTest myClassLoaderTest = new ClassLoaderTest(); Class myTest = myClassLoaderTest.loadClass("/tmp/LearnJVM.class"); System.out.println(myTest); LearnJVM l = new LearnJVM(); l.SayHi(); } }
执行结果: javac ClassLoaderTest.java java ClassLoaderTest class LearnJVM Hi Loader!