JDK JVM JRE Java虚拟机概念区别和一个类加载器实例

来源:互联网 发布:苹果下载iso软件 编辑:程序博客网 时间:2024/06/05 03:51

JDK JVM JRE Java虚拟机概念区别和一个类加载器实例

本来是要分析AOP源码的,它需要分析动态代理的源码,动态代理又需要分析类的加载和生成的过程,所以进入到了这篇的学习,现做一下总结:

1.一些概念

1)什么是JDK?

JDK就是Java Development Kit.简单的说JDK是面向开发人员使用的软件开发包,它提供了Java的开发化境和运行环

2)什么是JRE

Java Runtime Enviroment是指Java的运行环境,是面向Java程序的使用者,而不是开发者。

3)什么是JVM?

JVM -- java virtual machineJVM就是我们常说的java虚拟机,它是整个java实现跨平台的最核心的部分,所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行,也就是说class并不直接与机器的操作系统相对应,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行

4)什么Java虚拟机

JVM其实就是Java虚拟机

5)之间的关系?

JDK安装之后内部包含了JRE,这个JRE是供开发使用的;其实在安装JDK的过程中外部也会有一个JRE,这个是给普通使用者使用的,可以用来运行编译后的字节码,当我们进行开发时要指定JRE的安装目录,这个时候应该选择JDK下面的JRE。另外JRE中的bin目录就是Java虚拟机,我们需要运行Java程序时需要安装Java虚拟机,就是指安装Java的运行环境JRE(包含bin目录)的过程。

2.从一张图看它们的区别(来自http://blog.csdn.net/luanlouis/article/details/24589193),以及引申到类加载器实例


Java编译器指的就是JDK中的javac.exe,JRE中是没有的,不过一般来说IDE中如Eclipse都包含了这个。简单梳理下代码的执行流程,首先是源文件,然后经过Java编译器生成.class的二进制文件,这个文件经过本地或者网络(一般是本地,如果要处理远程的class文件,需要自定义类加载器),然后经过JDK中的类加载器的和Java类库和校验。然后再由Java解释器(java.exe)和即时编译器的处理。

Java解释器:是JRE的一部分,能够把.class的字节码一行一行直接翻译成机器码,然后由运行期系统执行。

即时编译(Just-in-time compilation:JTI):是JRE的一部分,又叫实时编译,是一种把字节码翻译成机器码并且缓存起来以降低性能耗损,被用来改善虚拟机性能。

然后处理之后的机器码就由运行期系统来指定,运行期系统执行机器码同时还通过诸如本地方法与操作系统进行交互。这个就是类从编译到运行的过程。

3.下面讲述类加载器

1) 自定义类加载器如何运行的?

先要明确自定义类的加载器会是如何运行的,才能够自定义类加载器,下面一个简单的分析:

我们找到类加载所在的类java.lang.ClassLoader(这个就是我们自定义类加载器要继承的类), 然后发现如下过程


进入loadClass:


进入findClass:


发现这个方法直接抛出异常,原来是要我们自定义时进行重写的。至于重写的时候应该写那些内容,执行哪些操作,上面叙述的是follow the delegation model for loading classes, and will be invoked by the {@link #loadClass <tt>loadClass</tt>} method after checking the parent class loader for the requested class,它的意思以后深究,总之是要完成这样的功能:即找到类,然后进行加载。

2)如何自定义类加载器?

自定义类加载器需要继承ClassLoader类,重写findClass方法,在findclass方法中完成A)找到类,放入字节数据

B)分析字节数据,加载类,如下:

MyClassLoader.java

package day_20160929;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.InputStream;public class MyClassLoader extends ClassLoader{private String name;//类加载器名字private String path;//加载类的路径private final String fileType = ".class";//class文件的扩展名public MyClassLoader(String name){super();  //让系统类加载器成为该类加载器的父加载器this.name = name;}public MyClassLoader(ClassLoader parent,String name) {super(parent);this.name = name;}@Overridepublic String toString() {return this.name;}public String getPath() {return path;}public void setPath(String path) {this.path = path;} /**     * @param 类文件的名字     * @return 类文件中类的class对象     *      * 在这里我们并不需要去显示的调用这里的findclass方法,在上篇文章中,我们通过查看     * loadclass的源码可以发现,她是在loadclass中被调用的,所以这里我们只需重写这个方法,     * 让它根据我们的想法去查找类文件就ok,他会自动被调用     *      *      * defineClass()将一个 byte 数组转换为 Class 类的实例。必须分析 Class,然后才能使用它     * 参数:         * name - 所需要的类的二进制名称,如果不知道此名称,则该参数为 null         * b - 组成类数据的字节。off 与 off+len-1 之间的字节应该具有《Java Virtual Machine Specification            》定义的有效类文件的格式。         * off - 类数据的 b 中的起始偏移量         * len - 类数据的长度      */    @Override    public Class<?> findClass(String name) throws ClassNotFoundException {        byte[] data = this.loadClassData(name);//获得类文件的字节数组        return this.defineClass(name, data, 0, data.length);//    }        /**     *      * @param 类文件的名字     * @return 类文件的 字节数组     * 通过类文件的名字获得类文件的字节数组,其实主要就是用     * 输入输出流实现。     */    private byte[] loadClassData(String name) {        InputStream is = null;        byte[] data = null;        ByteArrayOutputStream baos = null;        try {            name = name.replace(".", "\\");            is = new FileInputStream(new File(path + name + fileType));            baos = new ByteArrayOutputStream();            int ch = 0;            while (-1 != (ch = is.read())) {                baos.write(ch);            }            data = baos.toByteArray();        } catch (Exception ex) {            ex.printStackTrace();        } finally {        try {            is.close();            baos.close();        } catch (Exception ex) {            ex.printStackTrace();        }    }    return data;}}

App.java

package day_20160929;/*测试的基础类App*/public class App {public void appSay(){System.out.println("App.appSay()");}}
Main.java

package day_20160929;public class Main {public static void main(String[] args) {/*创建loader1类加载器,设置父类加载器为系统类加载器*/MyClassLoader loader1 = new MyClassLoader("loader1");loader1.setPath("E:\\workspaces\\myapp\\target\\test-classes\\");/*创建loader2类加载器,设置父类加载器为loader1*/MyClassLoader loader2 = new MyClassLoader(loader1,"loader2");loader2.setPath("E:\\workspaces\\myapp\\target\\test-classes\\");/*创建loader3类加载器,设置父类加载器为根类加载器*/MyClassLoader loader3 = new MyClassLoader(null,"loader3");loader3.setPath("E:\\workspaces\\myapp\\target\\test-classes\\"); try {test(loader1);System.out.println("----------");test(loader2);System.out.println("----------");test(loader3);} catch (Exception e) {e.printStackTrace();}}public static void test(ClassLoader loader) throws Exception {    /*加载类*/    Class clazz = loader.loadClass("day_20160929.App");    System.out.println("Main.test():"+clazz.getCanonicalName());    /*创建类实例*/    Object object = clazz.newInstance();    System.out.println("Main.test():"+object.toString());}}


将上面的执行过程概括一下:

首先在构造方法中,我们可以通过构造方法给类加载器起一个名字,也可以显示的指定他的父类加器器,如果没有显示的指出父类加载器的话他默认的就是系统类加载器。由于我们继承了ClassLoader类,所以它自动继承了父类的loadclass方法。我们前面看了loadclass的源码知道,它调用了findclass方法去查找类文件。所以在这里我们重写了ClassLoader的findclass方法。在这个方法中首先调用loadClassData方法,通过类文件的名字获得类文件的字节数组,其实主要就是用输入输出流实现。然后调用defineClass()将一个 字节 数组转换为 Class 类的实例。

注:整个类加载会按照这个顺序来调用loadClass->findClass->defineClass,我们要做的就是在findClass方法中,获得类的字节数组,然后再调用系统的defineClass方法,其实整个过程我们做的就是分析类文件,得到它的字节数组,其他的由系统来做。

3)为什么要自定义类加载器?

自定义类加载器的其中一个应用场景是:通过网络来传输 Java 类的字节代码(.class文件),为了保证安全性,这些字节代码经过了加密处理。在接收的时候,就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类。

总结:主要学习了Java一些基本概念和类加载器的执行流程以及如何自定义类加载器~    后面会学习动态代理和AOP相关的内容。

上面遗漏了很多的问题,其实下面这些问题都是由这篇文章引出,然后主动学习总结的:

1)为什么自定义类加载器会执行findClass方法?

2)什么是双亲委派模型?其中的各个加载器分别加载哪些类?

3)这个模型与类的生命周期由什么样的对应关系?

4)刚才我们在使用自定义类加载器加载类时,为什么类要使用"day_20160929.App"这样的全限定类名,使用App不行么?

5)类的生命周期是怎样的?

后面的文章会一一解释

0 0
原创粉丝点击