[Java]虚拟机面试知识点总结

  • Java虚拟机

    • jvm的四大部分
      ClassLoader:根据特定格式,加载class文件
      Execution Engine:对命令进行解析
      Native Interface: 融合不同开发语言的原生库 native方法(class.forName())
      Runtime Data Area:JVM内存空间结构模型
    • Jvm如何加载.class文件
      通过Class Loader符合其格式要求的 .class加载到内存,通过ExecutionEngine对class的字节码进行解析为机器码,交给操作系统去执行
    • 谈谈你对反射的理解?
      JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
  • 谈谈ClassLoader?

    • ClassLoader在java中有着非常重要的作用,它主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流。它是JAVA的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接,初始化等操作
    • ClassLoader的种类
      • 启动类加载器(Bootstrap ClassLoader):这个类加载器负责装载Java API(java 核心类库) 。将存放在\lib 目录中的或者被-Xbootclasspath参数所指定的路径中的,并且是被虚拟机识别的类库加载到虚拟机内存中。这个类加载器是在JVM启动的时候创建的,和其他的类装载器不同的地方在于这个装载器是通过native code来实现的,而不是用Java代码。

      • 扩展类加载器(Extension classLoader):它负责加载\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可用直接使用扩展类加载器。

      • 应用程序类加载器(Application ClassLoader):也称为系统类加载器,它负责加载类路径(ClassPath) 上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己

      • 自定义加载类

        • 代码

        public class MyClassLoader extends ClassLoader{

        //指定加载类的目录
        private String path;
        
        public MyClassLoader(String path){
            this.path = path;
        }
        @Override
        public Class findClass(String className){
            byte [] b = loadClassData(className);
            System.out.println(b.length);
            return defineClass(null, b, 0, b.length,null);
        }
        private byte[] loadClassData(String className) {
            FileInputStream in = null;
            ByteArrayOutputStream out = null;
            try{
                String classpath = path+className+".class";
                in =  new FileInputStream(classpath);
                out  = new ByteArrayOutputStream();
                byte[] b = new byte[1024];
                int len = -1;
                while((len=in.read(b))!=-1){
                    out.write(b,0,len);
                }
        
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                try {
                    in.close();
                    out.close();
                } catch (IOException e) {}
            }
            return out.toByteArray();
        }
        

        }

  • 谈谈ClassLoader的双亲委派机制

    • 1.自底向上检查类是否已经被加载
    • 2.自顶向下尝试加载类
    • 在这里插入图片描述
      执行流程:比如现在我们第一次加载String类,首先到Custom ClassLoader即自定义的ClassLoader进行查找是否曾经加载过String类,如果有,直接返回Class对象。如果没有,到父级的AppClassLoadr进行查找是否曾经加载过String类,如果没有,继续往父级ExtensionClassLoader中检查是否已经加载过。如果仍然没有到BootStrap ClassLoader中检查是否被加载过。
      我们知道String类是核心类(Bootstrap ClassLoader):这个类加载器负责装载Java API(java 核心类库)。如果到BootStrap ClassLoader中检查也没被加载过。就会到对应的路径下的jar包找到指定的class文件进行加载。
      这就是自底向上检查,自顶向下加载的流程
    • 下面深入到源码进行解释
      • ClassLoader的子父关系

        public class TestMyClassLoader {
            public static void main(String[] args) throws Exception {
                MyClassLoader myClassLoader = new MyClassLoader("D:\\idea\\intellij Work_Space\\study\\out\\production\\study\\jvm\\");
                Class clazz = myClassLoader.findClass("Myclass");
                Object o = clazz.newInstance();
        
                System.out.println(myClassLoader.getParent());
                System.out.println(myClassLoader.getParent().getParent());
                System.out.println(myClassLoader.getParent().getParent().getParent());
        
            }
        }
        

        输出结果
        486
        这是自定义加载器测试类
        sun.misc.Launcher A p p C l a s s L o a d e r @ 18 b 4 a a c 2 s u n . m i s c . L a u n c h e r AppClassLoader@18b4aac2 sun.misc.Launcher AppClassLoader@18b4aac2sun.misc.LauncherExtClassLoader@1540e19d
        null
        可以看到自定义加载器的父类是AppClassLoader,AppClassLoader的父类是ExtClassLoader,
        ExtClassLoader父类显示为null。为什么ExtClassLoader的父级为空呢?因为ExtClassLoader的父级时BootStrapClassLoader是底层的代码,我们看不到

        我们接着分析源码!!!

        下面我们来看ClassLoader的loadClass方法
        在loadclass方法中
        1.首先判断当前加载器是否加载过该类字节码文件,如果加载过直接返回类的Class对象//1

        2.如果当前加载器没有加载过,如果有父级加载器,调用父级的loadclass方法去加载//2//3

        3.如果没有父级加载器,调用findBootstrapClassOrNull(name),调用底层的BootStrapClassLoadder加载器检查该类是否被加载过,如果加载过直接返回Class对象,如果没有加载过,到对应的路径下的jar包寻找该类进行加载并返回//4

        4.如果对应的jar包找不到该类字节码文件 调用子级的加载器到对应路径进行加载 //5

        protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                Class<?> c = findLoadedClass(name);//1
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {//2
                            c = parent.loadClass(name, false);//3
                        } else {
                            c = findBootstrapClassOrNull(name);//4
                        }
                    } 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();
                        c = findClass(name);//5
        
                        // 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;
            }
        }
        
    • 为什么要用双亲委派机制进行加载?
      节约内存空间,如果某个加载器进行加载过类,它会到内存中保存一份。如果又要用到该类时,自底向上检查类加载器,检查到了直接返回即可。
    • ClassLoader.loadClass()和Class.forName() 区别?
      类的加载步骤
      class加载到JVM中有三个步骤
      • 装载:(loading)找到class对应的字节码文件。

      • 连接:(linking)将对应的字节码文件读入到JVM中。

      • 初始化:(initializing)对class做相应的初始化动作
        1.用ClassLoader.loaderClass()加载不会进行连接,初始化操作
        protected Class<?> loadClass(String name, boolean resolve)
        我们看loadClass方法中的resovle参数,如果为false表示不进行连接操作,而底层默认调用的
        public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
        }
        2.用Class.forName()加载会进行初始化操作
        初始化操作指的是初始化静态变量,执行静态代码块等

      • 栗子
        JDBC注册驱动用 Class.forName(“com.mysql.jdbc.Driver”)
        public class Driver extends NonRegisteringDriver
        implements java.sql.Driver
        {
        //注意,这里有一个static的代码块,这个代码块将会在class初始化的时候执行
        static
        {
        try
        {
        //将这个驱动Driver注册到驱动管理器上
        DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
        throw new RuntimeException(“Can’t register driver!”);
        }
        }
        }

        我们看出mysql 的驱动包源码中有一个静态代码块用来注册驱动,所以我们用Class.forName()
        如果我们用ClassLoader.loaderClass()加载,驱动就不会注册到驱动管理器上,我们就不能正常和数据库连接了!

  • java内存模型
    在这里插入图片描述

    • 从线程角度来考虑
      • 每个线程独占的有程序计数器,虚拟机栈,本地方法栈
        • 程序计数器:
          1.当前线程所执行的字节码行号指示器
          2.改变计数器的值来选取下一条需要执行的字节码指令
          3.如果时Native方法则计数器的值为Undefine

        • 虚拟机栈:每次java方法调用后创建栈帧压入栈顶,方法执行结束后栈帧弹出栈,释放内存空间
          1.java方法执行的内存模型
          2.包含多个栈帧
          ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191018234401673.JPG在这里插入图片描述

          主要有局部变量表,操作栈,操作指令,返回地址构成
          操作栈用作计算结果,局部变量表用作缓存参与计算的值。动态连接和返回地址在方法的上下文切换上起作用。
          StackOverflow异常:每调用一次方法就会给虚拟机栈压入栈帧,当递归调用的层数很深时,栈就会爆掉
          OutOfMemerory异常:如果方法中定义了很多局部变量,方法又递归的调用自身。造成内存不足
          虚拟机栈为什么么不用gc?因为调用时会生成栈帧,执行结束后会自动销毁

        • 本地方法栈
          和虚拟机栈相同,用于native方法执行的内存模型

      • 线程共享的
        • 1.方法区
          主要用于存储类的信息、常量池、方法数据、方法代码
          在jdk1.8以前叫做永久代,使用的是jvm内存
          jdk1.8以后叫做元空间,使用的是本地内存,字符串常量池换到了堆中存储
          为什么替换?
          字符串常量池存在永久代中,容易出现性能问题和内存溢出
          类和方法的信息大小难以确定,给永久代的大小指定带来困难
          永久带会为GC带来不必要的复杂性
        • 2.堆空间
          堆空间用来保存实例变量的信息在这里插入图片描述
          在这里插入图片描述
  • java内存模型面试题目

    • jvm三大调优性能参数 -Xms,-Xmx,-Xss
      -Xss 虚拟机栈的大小
      -Xms head的初始值
      -Xmx head的最大值
      Xms 和 Xmx的值最好设置相同,否则扩容的是否会出现内存抖动现象。影响程序的稳定性
    • Java内存中堆和栈的区别
      堆空间和栈的联系:引用对象,数组时,栈里定义变量保存堆中目标的引用
      在这里插入图片描述

区别:

			管理方式:栈自动释放,堆需要GC

			空间大小:栈比堆小

			碎片相关:栈产生的碎片远小于堆

			分配方式:栈支持静态分配和动态分配,而堆空间仅支持动态分配

			效率:栈的效率比堆高

	总结:元空间:存储类的字段,方法。

		堆空间:存储实例变量。

		线程独占空间:有本地栈空间,虚拟机栈空间(方法链接,局部变量表,操作栈,操作指令),程序计数器。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值