Java高频面试考点之java基础知识

Java基础考点:
在这里插入图片描述

开放式面试题
在这里插入图片描述
在这里插入图片描述
面试题:java是如何实现平台无关性的啊?

答案如上图

面试题:为什么需要把源码编译成字节码,然后交由jvm进行解析成机器码
在这里插入图片描述

1.该题也可以理解为 编译的作用是什么? ,如果没有编译这个过程,那么每次执行程序前做的语法,句法,语义的检查的结果都不会被保存,也就是,每次在执行程序前都需要重新进行检查,这样做了很多重复的工作,然后影响性能
2.只要对源码进行了编译,我们多次执行程序也不需要进行多次的检查.

面试题:jvm如何加载.class文件
在这里插入图片描述
解答如下:

首先,ClassLoader把经过编译之后生成的.class文件依据一定的格式加载到内存中,然后Execution Engine,对内存中的字节码文件进行解析,然后解析生成的机器码会被提交给操作系统进行执行.

jvm架构图:
在这里插入图片描述
前提知识补充(上图中4大组件的作用如下):

1. classLoader: 它负责把经过编译生成的.class文件加载到内存.(它对所要加载的文件的格式是有要求的,只有复合一定格式的才会加载),生成相应的类的对象

2. Execution Engine:对字节码文件进行解析,解析完成之后的命令会被提交到操作系统中进行执行

3. Native Interface : 本地接口,它主要是融合不同语言开发的原生库为java所用

4. Runtime Data Area : JVM内存空间结构模型,该结构的设计堪称神作,是我们学习和性能优化的重点.

重点: 关于反射的面试题
在这里插入图片描述

反射(reflect) :是指在运行状态时,对于任何一个类我们都能够知道该类的所有的属性和方法,对于任意一个对象,都能够调用该对象的所有方法和属性,这种动态的获取信息以及动态调用对象方法的功能称为是 java的反射机制

面试题:列举出几个常见的反射函数,或者写几个反射的例子
在这里插入图片描述
定义一个实例Robot

package reflect;
public class Robot {
    private String name;
    public void sayHi(String helloSentence){
        System.out.println(helloSentence + " " + name);
    }

    private String throwHello(String tag){
        return "Hello " + tag;
    }
}

创建反射实例:

package reflect;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

 class ReflectSimple {
    public static void main(String[] args) throws ClassNotFoundException,
            IllegalAccessException, InstantiationException,
            InvocationTargetException, NoSuchMethodException, NoSuchFieldException {
        //通过类的全路径类名,获取类
        Class rc = Class.forName("reflect.Robot");
        //创建类的实例,然后强转为robot,默认返回值是Object类型的
        Robot r = (Robot) rc.newInstance();
        //打印出当前类的名字
        System.out.println("Class name is "+ rc.getName());
        //获取对象私有的方法
        //在java世界中,万物皆是对象,我们通过Method的实例来接收通过反射获取的方法
        //第一个参数是方法的名称,第二个是方法的参数的类型
        Method getHello = rc.getDeclaredMethod("throwHello", String.class);
        //私有的属性,或者是方法,要进行设置,默认值是false,否则会报错
        getHello.setAccessible(true);
        //执行方法,需要传入对象实例和方法所需的参数,默认返回值是Object
        Object str = getHello.invoke(r, "Bob");
        System.out.println("getHello result is "+ str);
        //获取被public修饰的方法
        Method sayHi = rc.getMethod("sayHi", String.class);
        sayHi.invoke(r,"welcome");
        //获取私有的属性,通过Field接收
        Field name = rc.getDeclaredField("name");
        name.setAccessible(true);
        name.set(r, "Alice");
        sayHi.invoke(r,"welcome ");
    }
}

运行结果如下图:
在这里插入图片描述

小结:

1..getDeclaredMethod :,他能够获取类的public(公有的),private(私有的),protected,等所有的方法,除了,类继承的方法,还有类所实现的接口的方法
2..getMethod: 它只能获取public(公有的)方法,但是,它还可以获取所继承类的公有的方法,所实现的接口的方法.
3.获取私有的方法,或者属性,一定要执行,method.setAccessible(true);或者,field.setAccessible(true);,否则会报错.
4.反射就是把java类中的各种成分,映射成一个个的对象,

在这里插入图片描述
面试题
在这里插入图片描述
ClassLoader小结

ClassLoader在java中发挥非常重要的作用,它主要工作在Class装载的加载阶段,(Class的装载总共包括加载,连接,和初始化三个阶段),其主要的作用就是从系统外部获得Class二进制数据流,它是java的核心组件,所有的类都是ClassLoader进行加载的,ClassLoader负责将class文件中的二进制流文件加载到系统,然后交给jvm进行连接和初始化等操作.

ClassLoader的种类

  1. BootStrapClassLoader :它主要是用来加载Java自带的核心类,是由C++编写,它主要加载java.lang包下的内容更,加载的都是java最核心的的内容.
  2. ExtClassLoader : 它是用户可见的Classloader,用户加载jre外,Ext目录下的jar包,用户自定义的jar包也可以放到该目录下.
  3. AppClassLoader : 他主要是用来加载Classpath路径下的内容
  4. 自定义Classloader : 定制化加载内容和加载方式.

自定义ClassLoader
在这里插入图片描述
关于自定义的ClassLoader:

自定义ClassLoader有很多的好处,我们可以指定加载的路径(这个路径可能不在系统的Classpath范围内),定制化加载(要加载的甚至不是class文件或者是jar文件),自定义的ClassLoader有很多的好处,关键是重写图片上的两个方法,findClass,和 defineClass

自定义ClassLoader的实例Demo:
前提是需要预先创建目标类并且对目标类进行编译,否则会报错.

package reflect;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {
	//类加载器名称
    private String classLoaderName;
    //加载路径
    private String path;

    public MyClassLoader(String path, String name) {
        this.classLoaderName = name;
        this.path = path;
    }

    //用于寻找类文件,根据名称加载.class字节码文件
    @Override
    public Class findClass(String name){
        //通过loadClassData方法,把二进制的字节码文件转换为byte数组
        byte[] b = loadClassData(name);
        //通过defineClass来解析定义Class字节流返回class对象
        return defineClass(name,b ,0 ,b.length );
    }

    //用于加载类文件(通过类名称进行加载)
    private byte[] loadClassData(String name) {
        //对类名称进行拼接,方便加载指定路径下的指定名称的.class文件
        name = path +name +".class";
        //通过输入流读取类文件
        InputStream in = null;
        //转化为byte数组的输出流
        ByteArrayOutputStream out = null;
        try{
            //读取文件
            in = new FileInputStream(new File(name));
            out  = new ByteArrayOutputStream();
            int i= 0;
            while((i= in.read()) != -1){
                out.write(i);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            try{
                out.close();
                in.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        return out.toByteArray();
    }
}

验证自定义的ClassLoader

public class ClassLoaderChecker {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        MyClassLoader m = new MyClassLoader("D:/", "MyClassLoader");
        Class c = m.loadClass("Wali");
        System.out.println(c.getClassLoader());
        c.newInstance();
    }
}

执行结果如下:
在这里插入图片描述
小结:

1.只要我们传入的二进制流是合法的,我们便可以通过defineClass来进行解析.
2.我们可以使用findclass访问远程的网络,去获取二进制流,并生成我们需要的类,也可以对某些敏感的类进行加密,然后在findclass 中进行解密,还可以对生成的二进制流进行修改,通过修改二进制流的代码来给类添加信息,这就是传说中的字节码增强技术.
3.AOP的实现虽然不是如此,但是也是可以借鉴该方法的.

双亲委派机制的诞生背景:

不同的ClassLoader加载类的方式和路径都是不同的,为了实现分工,各自负责各自的区块,使得逻辑更加的明确,我们才有这么多的ClassLoader并存,加载类的时候肯定会按照各自的管理的区域各司其职,我们肯定要有一个机制,让他们协同工作, 形成一个整体,这个机制就是双亲委派机制

原理图:
在这里插入图片描述
双亲委派机制的工作机制:

  1. 首先会从用户自定义的ClassLoader中查找是否加载过目标类,如果没找到就会委托给它的父类进行loadClass,以此类推,直到BootStrapClassLoader,
  2. 如果查找的结果依然是没有被加载过,那么,BootstrapClassLoader就会去他对应的路径或者是通过参数指定的路径下进行查找,如果没有找到就会委托给它的子类去相应的路径下进行查找.如果都没有直到就会抛出 ClassNotFoundException异常.
  3. 以上过程中,只要有一个步骤找到了目标类,都会直接返回,装载到JVM中,不会继续向下执行

loadClass 的源码分析:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        //锁住这个class,因为有可能会有多个线程调用同一个类加载器去加载同一个类
        //,锁住它可以避免冲出
        synchronized (getClassLoadingLock(name)) {
        
            // First, check if the class has already been loaded
            //检车该类是否已经被加载过,如果加载过就直接返回,然后把该类装载到JVM中
            Class<?> c = findLoadedClass(name);
            
            //c==null,说明该类没有加载过,如果它的父类不为空,直接委托给父类进行加载
            //如果父类为null,说明走到了BootStrapClassLoader.因为它是用C++写的,所以查出来是null
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                    //委托给父类进行加载
                        c = parent.loadClass(name, false);
                    } else {
                    //让BootStrap ClassLoader进行加载
                        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();
                    //如果依然没有找到该类,那么就要按照.
                    //ClassLoader从上到下的顺序执行loadClass方法,对该类进行查找.
                    //具体的查找的路径如上图所示.如果都没有查到到,就会抛出ClassNotFoundException
                    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();
                }
            }
            //resolve默认值是false,也就是说loadClass默认是不会链接目标类的.
            if (resolve) {
                resolveClass(c);
            }
            //以上步骤如果有一个步骤找到了目标类,就会直接返回,都找不到就会抛出异常
            return c;
        }
    }

双亲委派机制的作用
在这里插入图片描述

  1. 主要是为了防止多分同样的字节码文件被重复加载.如果没有双签委派机制,很有可能多个类加载器会把多个同样的字节码文件加载到内存.那么在内存中就会有多个相同的Class对象,这样是比较浪费内存的.内存是宝贵的,

类的加载方式:
在这里插入图片描述
显示加载和隐式加载的区别:

  1. 显示加载
    loadClass和forName 在获取到Class对象之后,都需要调用Class.newInstance()方法才能获取到对象的实例,它不支持直接传入参数,需要通过反射调用构造器对象的newInstance()方法才能支持传入参数.
  2. 隐式加载:
    隐式加载无需调用对象的newInstance()方法,就能获取对象的实例,并且他支持带参数的构造器生成对象的实例.

loadClass和forName的区别和共同点 :
在了解他们之前,我们首先需要了解Java类装载的过程:
在这里插入图片描述

类的装载过程
装载 : 表示Class对象的生成过程,包含加载,链接和初始化*三个步骤.*

装载 : 表示的是外部的二进制字节码文件读入JVM然后是生成Class对象的过程.它包含以下三个部分.

  1. 加载: classloader通过loadClass把字节码文件加载到内存,生成Class对象 (并把静态数据转换成运行时的数据区中的方法区中的类型数据,在运行时,数据区堆中生成一个代表这个类的Java.lang.Class对象,作为方法区类数据的访问入口)
  2. 链接:它包含三个小步骤

校验: 检查加载的class的正确性和安全性.(比如检查class文件的格式是否正确)
准备 为类变量分配存储空间,并且设置类变量的初始值(类变量随类型信息存放在方法区中,生命周期很长,使用不当容易造成内存泄露)
解析 该步骤可选,JVM将常量池总的符号引用转换为直接引用.

  1. 初始化 : 执行类变量赋值和静态代码块

类变量: 即static变量,初始值指的是类变量类型的默认值,而不是实际要赋的值.

在这里插入图片描述
在这里插入图片描述

共同点 :

  1. 他们都能够在运行时对任意一个类,都能够知道该类的所有属性和方法,对于任意一个对象,都能够调用它的任意方法和属性.

区别以及产生的影响:

loadClass :

loadClass执行得到的是还没有进行链接操作的Class对象(只完成到了加载步骤),由于没有执行链接和初始化操作,它可以加快加载的速度,把初始化工作留到实际使用到该类的时候去做…
Spring的IOC容器技术,就大量使用了这种lazyloading (延时加载技术)

forName:

它得到的是已经执行过初始化操作的class对象.
如果你需要在类装载完成就已经完成了初始化,或者说在装载期间执行一些操作(比如给类变量赋值),就可以是用Class.forName()来装载目标类.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值