• java加载机制整理


    本文是根据李刚的《疯狂讲义》作的笔记,程序有的地方做了修改,特别是路径,一直在混淆,浪费了好多时间!!希望懂的同学能够指导本人,感激尽。。。。。。。。。。。。

    1.jvm 和 类的关系

    当调用 java命令运行一个java程序时,必会启动一个jvm即java虚拟机。(5.6.处有联系!!)
    该java程序的所有线程,变量都处于jvm中,都使用该jvm的内存区

    jvm终止的情况:
    1.程序自然运行结束
    2.遇到System.exit();Runtime.getRuntime.exit();
    3.遇到未捕获异常或错误时
    4.程序所在的平台强制结束了JVM进程
    jvm终止,jvm内存中的数据全部丢失。

    举例:

    定义一个类,包含静态变量

    class AClass{
        public static int a = 1;
    }

    使变量自加

    public class ATest {
        public static void main(String[] args) {              
            AClass.a++;
            System.out.println("a的值:"+AClass.a);//输出:a的值:2
        }
    
    }

    另起一类,查看变量变化情况

    public class ATest2 {
        public static void main(String[] args) {      
            System.out.println("a的值:"+AClass.a);//输出:a的值:1
        }
    
    }

    从输出对比可知,虽然ATest1自加,改变的类变量a的值,但是ATest1与ATest2处于不用的JVM中,当ATest1结束时,对a的修改都丢失了

    2.类的加载

    类的加载 又称为 类的初始化,实际上可细分为 类的 加载、连接、初始化。下面将讲述着三个阶段的过程!

    类的加载  指.class文件读入内存,并为之创建一个 java.lang.Class对象

    类加载,是通过类加载器来完成的,类加载器通常由JVM提供,通常称为系统类加载器(也可以是自己写的加载器,只要继承ClassLoader基类)。

    类加载无须等到“首次使用该类”时加载,jvm允许预加载某些类。。。。

    加载来源:

    1.本地.class文件
    2.jar包的.class文件
    3.网络.class文件
    4.把一个java源文件动态编译,加载

    3.类的连接

    负责把类的二进制数据合并到JRE(java运行环境)中
    1.验证 检测被加载的类是否有正确的内部结构,并和其他类协调一致
    2.准备 负责为类的类变量(非对象变量)分配内存,并设置默认初始值
    3.解析 将类的二进制数据中的符号引用替换成直接引用。。(static final 好像跟这个有点关系????5.6.处有联系)

    4.类初始化
    主要对类变量(而非对象变量)的初始化
    声明类变量的初始值 = 静态初始化块 他们是相同的,等效的。都会被当成类的初始化语句,JVM会按照这些语句在程序中的顺序依次执行他们

    public class Test {
        static int a = 5;    //初始化时赋值
        static int b;        //初始化时赋值--静态块
        static int c;        //连接时赋默认值值
        static{
            b = 6;
        }
        public static void main(String[] args) {
            System.out.println(a);
            System.out.println(b);
            System.out.println(c);
            
        }

    输出:

    5
    6
    0

    JVM初始化一个类包含如下几个步骤:
    1.假设类还没有被加载和连接,那么先加载和连接该类
    2.假设该类的父类还没被初始化,那么先初始化父类 ----jvm总是最先初始化java.lang.Object类
    3.假设类中有初始化语句,则一次执行这些初始化语句

    当程序主动使用任何一个类时,系统会保证该类以及所有父类(直接父类和间接父类)都会被初始化

    5.类初始化的时机:


    1.创建类的实例。new,反射,反序列化
    2.使用某类的类方法--静态方法
    3.访问某类的类变量,或赋值类变量
    4.反射创建某类或接口的Class对象。Class.forName("Hello");---注意:loadClass调用ClassLoader.loadClass(name,false)方法,没有link,自然没有initialize
    5.初始化某类的子类
    6.直接使用java.exe来运行某个主类。即cmd java 程序会先初始化该类。

    class Tester {
        public static int value = 10;
        public static String name;
    
        public static void method() {
            System.out.println("一个类方法");
        }
    
        static {
            System.out.println("Tester的类的静态初始化。。。");
        }
    }
    
    public class ClassLoadTest {
    
        static String classPath = "Chapter18.Tester";
    
        public static void main(String[] args) throws ClassNotFoundException,
                Exception, IllegalAccessException {
            ClassLoader cl = ClassLoader.getSystemClassLoader();
            Class<?> a = cl.loadClass(classPath); // 用到了 Tester类 加载一个类,并不会导致一个类的 初始化
    
            /* 初始化的时机 */
    
            // 调用newInstance()方法
            a.newInstance(); // 输出:____________静态块加载
            // 使用Class.forName方法
            Class.forName(classPath); // 输出:____________静态块加载
            // 调用类变量
            int value = Tester.value;
            Tester.name = "jason"; // 输出:____________静态块加载
            // 调用类方法
            Tester.method(); // 输出:____________静态块加载   一个类方法
            // new 实例化
            Test t = new Test(); // 输出:____________静态块加载
        }
    }


    特殊情形:final 类型的类变量,如果在编译时(转成.class文件)就可以确定,那么这个类变量就相当于“宏变量”,编译时,直接替换成值。
    所以,即使使用这个类变量,程序也不会导致该类的初始化!!----相当于直接使用 常量

    public class Test {    
    
        public static void main(String[] args) {
            System.out.println(aClass.A);        
        }
    }
    class aClass{
        static final int A = 111;//可以确定
        static{
            System.out.println("静态块初始化");
        }    
    }

    输出:111

    public class Test {    
    
        public static void main(String[] args) {
            System.out.println(aClass.A);        
        }
    }
    class aClass{
        static final long A = System.currentTimeMillis();//在编译时无法确定
        static{
            System.out.println("静态块初始化");
        }    
    }

    输出:

    静态块初始化
    1468309845203


    使用ClassLoader类的 loadClass方法来加载类时,只是加载该类,而不会执行该类的初始化!!使用Class的forName()静态方法,才会导致强制初始化该类。

     6.类加载器

    类加载器 负责加载所有的类,为被加载如内存中的类生成一个java.lang.Class实例。一旦类被载入内存,同一个类就不会再加载第二次

    如何判断是同一个类:
    java中 一个类用其 全限定类名标示--包名+类名
    jvm中 一个类用其 全限定类名+加载器标示---包名+类名+加载器名

    加载器层次结构:
    JVM启动时,姓曾的三个类加载器组成的机构
    1.Bootstrap ClassLoader 根类 ------引导类加载器,加载java核心类。非java.lang.ClassLoader子类,而是JVM自身实现
    2.Extension ClassLoader 扩展类-----加载JRE的扩展目录中的JAR包的类(%JAVA_HOME%/jre/lib/ext或java.ext.dirs系统属性指定的目录)
    3.System ClassLoader 系统类-----加载cmd java -cp **,环境变量指定的jar包和类路径。ClassLoader.getSystemClassLoader获得 系统类加载器。

    4.用户类加载器。。。

    public class BootstrapTest {
        public static void main(String[] args) {
            // 获取根类加载器所加载的全部URL数组
            URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();    //无需理会警告
            //遍历 输出根类加载器 加载的全部URL
            for (int i = 0; i < urls.length; i++) {
                System.out.println(urls[i].toExternalForm());
            }        
        }
    }

    输出:

    file:/C:/Program%20Files/Java/jre1.8.0_20/lib/resources.jar
    file:/C:/Program%20Files/Java/jre1.8.0_20/lib/rt.jar
    file:/C:/Program%20Files/Java/jre1.8.0_20/lib/sunrsasign.jar
    file:/C:/Program%20Files/Java/jre1.8.0_20/lib/jsse.jar
    file:/C:/Program%20Files/Java/jre1.8.0_20/lib/jce.jar
    file:/C:/Program%20Files/Java/jre1.8.0_20/lib/charsets.jar
    file:/C:/Program%20Files/Java/jre1.8.0_20/lib/jfr.jar
    file:/C:/Program%20Files/Java/jre1.8.0_20/classes

    7.类的加载机制:
    1.全盘负责。某类以及其所依赖的所有类,都由一个加载器负责加载。除非显示使用另外一个加载器。
    2.父类委托。先父类加载器加载改Class,不行后,才尝试从自己的类路径中加载该类
    3.缓存机制。缓存机制将会保证所有加载过的Class都会被缓存。。当程序需要Class时,先从缓存区中寻找Class对象,没有的话,才加载该类的.class对象。

    8.访问类加载器

        public static void main(String[] args) throws IOException {
            ClassLoader systemLoader = ClassLoader.getSystemClassLoader();//  get system loader 获得系统的类加载器
        
            System.out.println("系统类加载器:        "+systemLoader);
            /*
             * 获取 系统类加载器 的加载路径--通常由CLASSPATH 环境变量 指定,
             * 如果,操作系统 没有指定CLASSPATH环境变量, 则 默认以当前路径作为 系统类加载器 的家在路径
             */
            Enumeration<URL> eml = systemLoader.getResources("");
            while(eml.hasMoreElements()){
            System.out.println("SYSTEMClassLoader Route:  "+eml.nextElement());//系统类加载器的加载路径 是程序运行的当前路径
            }
            //获取 系统类加载器 的父类加载器,得到 扩展类加载器
            ClassLoader extenionLoader = systemLoader.getParent();
            System.out.println("扩展类加载器:            "+extenionLoader);
            System.out.println("扩展类加载器的 路径:      "+System.getProperty("java.ext.dirs"));
            ClassLoader baseLoader = extenionLoader.getParent();
            System.out.println("扩展类加载器的 父类:"+baseLoader);//The parent Classload is Bootstrap ClassLoader.BC is not use Java Language...So..
            //根加载器 没有 继承ClassLoader抽象类。所以,返回的是Null
            //但实际上 扩展类加载器 的 父类加载器 是 根类加载器,只是,根类加载器 并不是 java实现的。
            
        }

    输出:

    系统类加载器: sun.misc.Launcher$AppClassLoader@73d16e93
    SYSTEMClassLoader Route: file:/D:/WorkSpace1/Java_Test/bin/
    扩展类加载器: sun.misc.Launcher$ExtClassLoader@7f31245a
    扩展类加载器的 路径: C:Program FilesJavajre1.8.0_20libext;C:WindowsSunJavalibext
    扩展类加载器的 父类:null

    系统类加载器是 AppClassLoader 的实例,扩展类加载器是 ExtClassLoader 的实例,这两个类都是 URLClassLoader 的实例

    9,类加载器加载Class大致要经过9个步骤:

    1.检测此Class 是否被载入过(即在缓存区中是否由此 Class),有,则进入第8步,否则执行第2步。

    2.如果父类加载器不存在(要么parent 一定是根类加载器,要么本身就是根类加载器),则跳到第4步;如果父类加载器存在,则执行第3步。

    3.请求使用父类加载器去载入目标类,如果成功则跳到第8步,否则执行第5步

    4.请求使用 根类加载器 载入目标类,成功则跳到第8步,否则跳到第7步

    5.当前类加载器 尝试寻找 Class文件(从与此ClassLoader相关的类路径中寻找),如果找到则执行第6步,否则跳到第7步。

    6.从文件中载入Class,成功后跳到第8步。

    7.抛出ClassNotFoundException异常。

    8.返回对应的 java.lang.Class对象。

    其中 第5,6步允许重写ClassLoader的findClass()方法来实现自己的载入策略,甚至重写loadClass()方法来实现自己 的载入过程

    10.一个自定义的ClassLoader

     由于是用eclipse编写程序,所以.java和.class文件分别放于不同的文件夹。写此程序的时候 要注意路径问题

    本项目的包结构:

    Java_Test|

         |src |

                 |Chapter18|

                 | ClassloaderMineTest.java

                 | Test.java

    项目路径:D:WorkSpace1Java_Testin;    D:WorkSpace1Java_Testsrc

        /**
         * 读取一个文件 的 内容,返回byte[]
         * @param filename
         * @return
         * @throws IOException
         */
        private byte[] getBytes(String filename) throws IOException {
            System.out.println("使用getBytes方法");
            File file = new File(filename);//路径 bin/Chapter18/Test.class
            long len = file.length();
            byte[] raw = new byte[(int)len];
            
            try (FileInputStream fin = new FileInputStream(file)){
                //一次读取Class文件的全部二进制数据
                    int r = fin.read(raw);
                    if(r!=len) throw new IOException("无法读取全部文件:"+r+"!="+len);
                    return raw;
                }        
        }
        /**
         * 编译指定JAVA文件,返回编译的结果
         * @param javaFile
         * @return
         * @throws IOException
         */
        private boolean compile(String javaFile) throws IOException{
            System.out.println("myClassLoader:正在编译"+javaFile+"..........");
            //调用系统的javac命令--指定了.class生成的路径,注意空格。此处为何不加上Chapter18呢?难道是java命名唯一性的缘故,包名+类名,已经知道了包名??
            Process p = Runtime.getRuntime().exec("javac -d d:/WorkSpace1/Java_Test/bin/ "+javaFile);
            try {
                //其他线程都等待这个线程完成
                p.waitFor();
                
            } catch (Exception e) {
                // TODO: handle exception
                System.out.println(e);
            }
            //获取javac线程的退出值
            int ret = p.exitValue();
            //返回编译是否成功        return res == 0;        
        }
        /**
         * 重写的findClass方法
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {//name = Test
            Class clazz = null;
            String javaFilename = "src/Chapter18/"+fileStub+".java";
            String classFilename = "bin/Chapter18/"+fileStub + ".class";
            
            
            File javaFile = new File(javaFilename);  //相对路径,相对于项目的跟路径:D:WorkSpace1Java_Test
            File classFile = new File(classFilename);
       //当指定Java源文件存在,且,Class文件不存在,或者Java源文件的修改时间比class文件的修改时间更晚时,重新编译if(javaFile.exists() && (!classFile.exists()||javaFile.lastModified()>classFile.lastModified())){
                try {
                    //如果编译失败,或者该class文件不存在
                    if(!compile(javaFilename)||!classFile.exists()){
                        throw new ClassNotFoundException("ClassNotFoundException:"+javaFilename+ " or "+classFile+" is not exists");
                    }
                } catch (Exception e) {
                    // TODO: handle exception
                    e.printStackTrace();
                }
            }
            //如果class文件存在,系统负责将该文件转换成class对象
            if(classFile.exists()){
                try {
                    //将class文件的二进制数据读入数组                
                    byte[] raw = getBytes(classFilename);
                    //调用ClassLoader的defineClass 方法将二进制数据转换成class对象
                    clazz = defineClass("Chapter18.Test",raw,0,raw.length);//路径 Chapter18.Test
                } catch (Exception e) {
                    // TODO: handle exception
                    e.printStackTrace();
                }
            }
            
            //如果clazz为null,表明加载失败,则抛出异常
            if(clazz == null){
                throw new ClassNotFoundException(name);
            }
            
            return clazz;    
        }
        /**
         * main方法
         * @param args
         * @throws Exception
         */
        public static void main(String[] args) throws Exception {
            
            ClassloaderMineTest mc = new ClassloaderMineTest();
            Class<?> clazz1 = mc.findLoadedClass("Chapter18.Test");
    
            Class<?> clazz = mc.loadClass("Test",false);       //路径 Test?
            Class<?> clazz2 = mc.findLoadedClass("Chapter18.Test"); //路径 Chapter18.Test??,为什么又和loadClass的路径不一样呢        
     //得到Class对象后就可以反射了
            Method main = clazz.getMethod("main", (new String[0].getClass()));
            String[] progArgs = {"Chapter18.Test"};
            Object argsArray[] = {progArgs};
            main.invoke(null, argsArray);
        }

    11.URLClassLoader 类

     java 为 ClassLoader 提供了一个 实现类 URLClassLoader ,该类 也是系统类加载器 和 扩展类加载器的 父类

    URLClassLoader 的 两个构造器

    URLClassLoader(URL[] urls)

    URLClassLoader(URL[] urls, ClassLoader parent)

    一旦 获得URLClassLoader对象后,就可以调用对象的 loadClass()方法 来加载指定的类。

    下面展示的程序示范了如何直接从文件系统中加载MySQL驱动,并且使用该驱动来获取数据库连接。。通过这种方式,可以无须将MySQL驱动添加到CLASSPATH环境变量中

    public class URLClassLoaderTest {
        private static Connection conn;
        
        private String url;
        private String driver;
        
        /** 
         * 获得该用户下面的所有表 
         */  
        public static void getAllTableList(String schemaName,DatabaseMetaData dbMeta) {  
            try {  
                // table type. Typical types are "TABLE", "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM".  
                String[] types = { "TABLE" };  
                ResultSet rs = dbMeta.getTables(null, schemaName, "%", types);  
                while (rs.next()) {  
                    String tableName = rs.getString("TABLE_NAME");  //表名  
                    String tableType = rs.getString("TABLE_TYPE");  //表类型  
                    String remarks = rs.getString("REMARKS");       //表备注  
                    System.out.println(tableName + "-" + tableType + "-" + remarks);  
                }  
            } catch (SQLException e) {  
                e.printStackTrace();  
            }  
        }
        
        //定义一个获取数据库 连接的方法
        public static Connection getConn(String url,String user,String pass) throws Exception{
            if(conn==null){
    
                //file 表名是从 本地文件系统加载,
                //http:为前缀,表明从互联网通过HTTP来访问
                //注意 URL 路径
    //            URLClassLoader loader = new URLClassLoader(new URL[]{new URL("file:D:/WorkSpaceMyEclipse/Meeting_Hall/WebRoot/WEB-INF/classes/")});
                URL[] urls = {new URL("file:mysql-connector-java-5.1.30-bin.jar")};//从本地加载,jar包由外界导入,或者是放在项目的跟路径D:WorkSpace1Java_Test
                
                //以 默认的 ClassLoader 作为父 ClassLoader,创建 URLClassLoader
                URLClassLoader myClassLoader = new URLClassLoader(urls);
                //加载MYSQL的JDBC驱动, 并创建默认实例-------与 普通的通过 new 生成实例的方法 对比
                Driver driver = (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance();
                //创建一个 设置JDBC连接属性的 Properties对象
                Properties props = new Properties();        
                
                //至少需要为改对象传入 user和password两个属性
                props.setProperty("user", user);
                props.setProperty("password", pass);
                //调用Driver 对象的 connect方法来取得数据库连接
                conn = driver.connect(url, props);
            }
            return conn;
        }
        public static void main(String[] args) throws Exception {
            getConn("jdbc:mysql://localhost:3306/user_login", "root", "root");            
            DatabaseMetaData dbMeta = conn.getMetaData();
            // 获取表中索引信息
            getAllTableList(null, dbMeta);    
        }
    
    }

    输出:

    affair-TABLE-
    comment-TABLE-
    sort-TABLE-
    user-TABLE-

        /**
         * 普通的 数据库连接方法
         * @return
         */
        public static Connection getDBConnection() {
            try {
                // 加载数据库
                Class.forName("com.mysql.jdbc.Driver");
                String url = "jdbc:mysql://localhost:3306/user_login";
                String user = "root";
                String password = "root";
                // 连接
                conn = DriverManager.getConnection(url, user, password);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return conn;
        }
  • 相关阅读:
    angular resolve路由
    SignalR 2.x入门(二):SignalR在MVC5中的使用
    SignalR 2.x入门(一):SignalR简单例子
    【安卓】手把手教你安卓入门(一)
    【UWP】 win10 uwp 入门
    【资讯】苹果AirPods无线耳机国行版开箱初体验
    【IOS】Swift语言
    用命令行创建.NET Core
    IT笑话一则
    5.Arduino的第一个程序
  • 原文地址:https://www.cnblogs.com/jasonstorm/p/5663864.html
Copyright © 2020-2023  润新知