• JVM学习二


    类加载器

    在这里插入图片描述
    不同的类加载器负责加载不同的class, null(代表最顶层的类加载器,由于bootstrap是用C++实现的但是java类里面
    没有与之相对应的,因此返回的是个空值),注意父加载器不是继承关系,只是我自己加载不了委托给父加载器去加载

    public class T002_ClassLoaderLevel {
        public static void main(String[] args) {
            System.out.println(String.class.getClassLoader());
            System.out.println(sun.awt.HKSCS.class.getClassLoader());
            //位于ext目录下的 extClassLoader
            System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
            //自己的是AppClassLoader【自己的】
            System.out.println(T002_ClassLoaderLevel.class.getClassLoader());
    
            System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());
            System.out.println(T002_ClassLoaderLevel.class.getClassLoader().getClass().getClassLoader());
    
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述
    双亲委派机制(类加载过程):
    假如要加载一个class文件了,它会首先去它自定义的classLoader里面去找【内部维护一个缓存,如果在缓存里面找到了,判断是否已经加载进来了则不需要再加载了,如果没有加载则把这个class加载进来】,如果缓存里面没有找到的话,则会去父加载器App的ClassLoader里面去找,如果App的类加载器里面没有,那么会在extension的classloader里面去找,如果extension里面没有则会再往上的bootstrap的classloader里面去找,如果都没能找到这个class文件对应的classLoader,则Bootstrap会往下委托extension加载,extension加载不出来则委托App加载,如果App没加载出来则委托CustomClassLoader去加载。【如果最后没有把s加载进来,则抛出ClassNotFoundException】
    在这里插入图片描述

    注意点

    为什么有双亲委派机制?
    首先,双亲委派就是从下往上,委派给双亲去加载,如果父类加载器加载过,那么就不会重复加载了。那么目的是什么呢?保证JVM加载类的安全性。保证JDK内部加载的类,不会被覆盖掉(比如,jvava.lang.String类,不会被我们自定义的类覆盖掉,查找到Bootstrap里面已经加载了String类,那么会直接返回)次要点是解决资源浪费问题

    父加载器:父加载器不是类加载器的加载器,也不是类加载器的父类加载器
    双亲委派是一个孩子向父亲方向,然后父亲向孩子方向的双亲委派过程。

    public class T004_ParentAndChild {
        public static void main(String[] args) {
            System.out.println(T004_ParentAndChild.class.getClassLoader());
            System.out.println(T004_ParentAndChild.class.getClassLoader().getClass().getClassLoader());
            System.out.println(T004_ParentAndChild.class.getClassLoader().getParent());
            System.out.println(T004_ParentAndChild.class.getClassLoader().getParent().getParent());
            //NullPointException
            //System.out.println(T004_ParentAndChild.class.getClassLoader().getParent().getParent().getParent());
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    类加载器的范围

    从上面的输出可以看出AppClassLoader,ExtClassLoader是Launcher的一个内部类,Launcher是classLoader的一个包装类,启动类
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    通过如下代码获取对应的配置就能看到属于对应类加载器的有哪些目录下的文件了:

    public class T003_ClassLoaderScope {
        public static void main(String[] args) {
            String pathBoot = System.getProperty("sun.boot.class.path");
            System.out.println(pathBoot.replaceAll(";", System.lineSeparator()));
    
            System.out.println("--------------------");
            String pathExt = System.getProperty("java.ext.dirs");
            System.out.println(pathExt.replaceAll(";", System.lineSeparator()));
    
            System.out.println("--------------------");
            String pathApp = System.getProperty("java.class.path");
            System.out.println(pathApp.replaceAll(";", System.lineSeparator()));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    自定义类加载器

    在这里插入图片描述

    常见的加载类的方法是通过获取AppClassLoader然后再通过给定类路径去加载类的。

        public static void main(String[] args) throws ClassNotFoundException {
            Class clazz = T005_LoadClassByHand.class.getClassLoader().loadClass("com.xxx.jvm.classloader.T002_ClassLoaderLevel");
            System.out.println(clazz.getName());
            
            //利用类加载器加载资源,获取图片等资源
            //T005_LoadClassByHand.class.getClassLoader().getResourceAsStream("");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    什么时候需要去加载一个类?
    spring动态代理是将新的class load到内存中的

    看一下源码:

     protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                //首先检查这个class是否已经被加载了
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                //没有load进来
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {
                        //调用父加载器的loadclass
                            c = parent.loadClass(name, false);
                        } else {
                        //如果父亲为null了则委托BootStrap来加载
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
    				//如果都没有找到只能自己去加载了
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        //调用此处的findClass找到自己重写的findClass方法
                        c = findClass(name);
    
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    
    • 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

    在这里插入图片描述
    默认父加载器指定:
    getSystemClassLoader默认的是AppClassLoader

            System.out.println(ClassLoader.getSystemClassLoader());
    
    • 1

    在这里插入图片描述

    有如下代码:

    public class Hello {
        public void m() {
            System.out.println("Hello JVM!");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后通过类加载器去加载这个class文件:

    public class T006_TestClassLoader extends ClassLoader {
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            File f = new File("C:/Users/dell/Desktop/JVM/", name.replace(".", "/").concat(".class"));
            try {
                FileInputStream fis = new FileInputStream(f);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int b = 0;
    
                while ((b=fis.read()) !=0) {
                    baos.write(b);
                }
    
                byte[] bytes = baos.toByteArray();
                baos.close();
                fis.close();//可以写的更加严谨
    
                return defineClass(name, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return super.findClass(name); //throws ClassNotFoundException
        }
    
        public static void main(String[] args) throws Exception {
            ClassLoader l = new T006_TestClassLoader();
            //需要加载这个类的时候会先去父亲(App,类路径)里面去找,再问Ext里面加载过吗,
            //再问BootStrap里面加载过吗,说没有加载过,自己尝试加载以下(没加载出来)
            //再找Ext能不能加载,也不能,再找App里面能不能加载(class类路径),发现找不到
            //再到自定义类加载器上面加载,【findClass()自己的,然后找到我上面定义的findClass】
            Class clazz = l.loadClass("com.lsd.jvm.Hello");
            Class clazz1 = l.loadClass("com.lsd.jvm.Hello");
    
            System.out.println(clazz == clazz1);
    
            Hello h = (Hello)clazz.newInstance();
            h.m();
    
            System.out.println(l.getClass().getClassLoader());
            System.out.println(l.getParent());
    
            System.out.println(getSystemClassLoader());
        }
    }
    
    • 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

    在这里插入图片描述

    class加密

    如果要加载的类不想被别人反编译看源码可以采用此方式:

    public class T007_TestClassLoaderWithEncription extends ClassLoader {
    
        public static int seed = 0B10110110;
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            File f = new File("C:/Users/dell/Desktop/JVM/",
                    name.replace(".", "/").concat(".testclass"));
    
            try {
                FileInputStream fis = new FileInputStream(f);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int b = 0;
    
                while ((b = fis.read()) != 0) {
                baos.write(b ^ seed);
                }
    
                byte[] bytes = baos.toByteArray();
                baos.close();
                fis.close();//可以写的更加严谨
    
                return defineClass(name, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return super.findClass(name); //throws ClassNotFoundException
        }
    
        public static void main(String[] args) throws Exception {
    
            encFile("com.lsd.jvm.Hello");
    
            ClassLoader l = new T007_TestClassLoaderWithEncription ;
            Class clazz = l.loadClass("com.lsd.jvm.Hello");
            Hello h = (Hello) clazz.newInstance();
            h.m();
    
            System.out.println(l.getClass().getClassLoader());
            System.out.println(l.getParent());
        }
    
        private static void encFile(String name) throws Exception {
            File f = new File("C:/Users/dell/Desktop/JVM/", name.replace('.', '/').concat(".class"));
            FileInputStream fis = new FileInputStream(f);
            FileOutputStream fos = new FileOutputStream(new File("C:/Users/dell/Desktop/JVM/",
                    name.replace('.', '/')
                            .concat(".testclass")));
            int b = 0;
    
            while ((b = fis.read()) != -1) {
                fos.write(b^seed);
            }
    
            fis.close();
            fos.close();
        }
    }
    
    
    • 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

    编译器

    JIT -just In-Time compiler ->指的是有某些代码需要编译成本地代码来执行
    在这里插入图片描述
    热点代码检测:
    多次被调用的方法(方法计数器,监测方法执行频率)
    多次被调用的循环(循环计数器:检测循环执行频率)
    赋如下测试代码,调整JVM参数分别为混合型以及解释型和编译型(发现混合型和编译型相比较只慢了一点点,而)

    public class T009_WayToRun {
        public static void main(String[] args) {
            for(int i=0; i<10_0000; i++) {
                m();
            }
    
            long start = System.currentTimeMillis();
            for(int i=0; i<10_0000; i++) {
                m();
            }
            long end = System.currentTimeMillis();
            System.out.println(end - start);
        }
    
        public static void m() {
            for(long i=0; i<10_0000L; i++) {
                long j = i%3;
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    JVM懒加载

    如果项目启动的时候把所有类加载到内存里面,那么可能会一下把内存给挤爆
    在这里插入图片描述
    Java的类分为如下几种:
    1、系统类 (rt包等自带内容)
    2、拓展类 (对接第三方的时候用)
    3、开发自己写的类

    对于java的类来说是用的时候才加载不用的时候不加载。
    在这里插入图片描述

    总结

    如果想打破双亲委派机制的话,就只有去重写ClassLoader里面的loadClass方法了。

    public class T011_ClassReloading1 {
        public static void main(String[] args) throws Exception {
            T006_TestClassLoader testClassLoader = new T006_TestClassLoader();
            Class clazz = testClassLoader.loadClass("com.lsd.jvm.Hello");
    
            testClassLoader = null;
            System.out.println(clazz.hashCode());
    
            testClassLoader = null;
    
            testClassLoader = new T006_TestClassLoader();
            Class clazz1 = testClassLoader.loadClass("com.lsd.jvm.Hello");
            System.out.println(clazz1.hashCode());
    
            System.out.println(clazz == clazz1);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述
    如上符合双亲委派的加载机制的,只要我加载了一次那么我的classLoader里面就会缓存这个信息,所以返回的true,而不是去重写生成一个。

    如何做到热加载呢:
    打破双亲委派机制

    public class T012_ClassReloading2 {
        private static class MyLoader extends ClassLoader {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
    
                File f = new File(""C:/Users/dell/Desktop/JVM/" + name.replace(".", "/")
                        .concat(".class"));
    
                if(!f.exists()) return super.loadClass(name);
    
                try {
    
                    InputStream is = new FileInputStream(f);
    
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
                return super.loadClass(name);
            }
        }
    
        public static void main(String[] args) throws Exception {
            MyLoader m = new MyLoader();
            Class clazz = m.loadClass("com.lsd.jvm.Hello");
    
            m = new MyLoader();
            Class clazzNew = m.loadClass("com.lsd.jvm.Hello");
    
            System.out.println(clazz == clazzNew);
        }
    }
    
    
    • 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
  • 相关阅读:
    【计算机网络】Tcp详解
    Matlab基础用法
    Java Number包含哪几种类型呢?
    Flutter的Widget, Element, RenderObject的关系
    供应链 | 在线平台的研究与思考(一):销售渠道与模式选择
    设计模式【java提高】
    博客积分上一万了
    Job for redis-server.service failed because the control process exited with error code(Centos 7 设置Redis开机自启报错)
    Python_面向对象编程
    nvm常用命令有哪些?nvm如何切换node版本?nvm如何下载node?nvm安装包
  • 原文地址:https://blog.csdn.net/lsdstone/article/details/126595008