面试题首页 > JVM面试题

JVM类加载器面试题

001JDK、JRE、JVM关系?

JDK (Java Development Kit) : java语言的软件开发包。包括Java运行时环境JRE。
JRE (Java Runtime Environment) :Java运行时环境,包括JVM。
JVM (Java Virtual Machine) :一种用于计算机设备的规范。
Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

002什么是类加载器,类加载器有哪些?

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
启动类加载器(Bootstrap ClassLoader)用来加载 java 核心类库,无法被 java 程序直接引用。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH) 来加载 Java类。一般来说,Java应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。
用户自定义类加载器,通过继承 java.lang.ClassLoader 类的方式实现。

003ClassLoader如何工作的?

ClassLoader 顾名思义就是类加载器。Java源代码首先被jvm编译成.class文件。类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking)。
加载:通过一个类的全限定名来获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,在内存中生成一个代表这个类的Class对象,作为方法去这个类的各种数据的访问入口;
验证:验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟自身的安全;
准备:准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法去中进行分配。这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
解析:解析阶段是虚拟机将常量池内的符号(Class文件内的符号)引用替换为直接引用(指针)的过程。
初始化:初始化阶段是类加载过程的最后一步,开始执行类中定义的Java程序代码(字节码)。

004静态代码块在程序中的执行顺序?

父类静态域——》子类静态域——》父类成员初始化——》父类构造块——》父类构造方法——》子类成员初始化——》子类构造块——》子类构造方法;

005对象创建的过程?

类加载检查:虚拟机遇到一条 new 指令时,首先检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
分配内存:在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
初始化零值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
设置对象头:初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
执行init方法:在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

006什么是双亲委派机制?

1.如果一个类加载器收到了类加载请求,它并不会自己先加载,而是把这个请求委托给父类的加载器去执行;
2.如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的引导类加载器;
3.如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制;

007双亲委派机制的优势?

避免类的重复加载,保护程序安全,防止核心API被随意篡改。

008如何破坏双亲委派模型?

JNDI(Java Naming and Directory Interface,Java命名和目录接口)便是最典型的例子。JND需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能“认识”这些代码那该怎么办?
为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的 setContextClassLoaser()方法进行设置。有了线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
例如JDBC在rt里面定义了这个SPI,那MySQL有MySQL的JDBC实现,Oracle有Oracle的JDBC实现,反正我java不管你内部如何实现的,反正你们都得统一按我这个来,这样我们java开发者才能容易的调用数据库操作。所以因为这样那就不得不违反这个约束啊,Bootstrap ClassLoader就得委托子类来加载数据库厂商们提供的具体实现。因为它的手只能摸到<JAVA_HOME>\lib中,其他的它无能为力,这就违反了自下而上的委托机制了。

009可不可以自己写个String类?

不可以。因为在类加载中,会根据双亲委派机制去寻找当前java.lang.String是否已被加载。由于启动类加载器已在启动时候加载了所以不会再次加载,因此使用的String是已在java核心类库加载过的String,而不是新定义的String。

010什么是字节码?

Java源程序.java通过编译器编译成字节码.class文件,也就是计算机可以识别的二进制文件。

011字节码文件组成结构是什么?

根据 Java 虚拟机规范的规定,class 文件格式采用一种类似于 C 语言的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表。

无符号数属于基础数据类型,以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成的字符串值。
是由多个无符号数或者其他表作为数据项构成的复合数据结构,所有表都习惯性地以 _info 结尾。表用于描述有层次关系的复合结构的数据,整个 class 文件本质上就是一张表。

012对象的finalization机制。

Java语言提供了对象终止(finalization)机制来允许开发人员提供对象被销毁之前的自定义处理逻辑。当垃圾回收器发现没有引用指向一个对象,即:垃圾回收此对象之前,总会先调用这个对象的finalize()方法,finalize()只会被调用一次。finalize() 方法允许在子类中被重写,用于在对象被回收时进行资源释放。通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件、套接字和数据库连接等。

013如何判断两个class对象是否相同?

在JVM中表示两个class对象是否为同一个类存在两个必要条件:
1)类的完整类名必须一致,包括包名。
2)加载这个类的ClassLoader(指ClassLoader实例对象)必须相同。

014类的主动使用有哪些?

主动使用,分为七种情况:
1)创建类的实例
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法I
4)反射(比如:Class.forName(“com.atguigu.Test”))
5)初始化一个类的子类
6)Java虚拟机启动时被标明为启动类的类
7)JDK7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF getStatic、REF putStatic、REF invokeStatic句柄对应的类没有初始化,则初始化
除了以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化。

015深拷贝和浅拷贝区别?

浅拷贝:只是增加了一个指针指向已存在的内存地址,
深拷贝:是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。

016启动程序如何查看加载了哪些类,以及加载顺序?

java -XX:+TraceClassLoading 具体类
Java -verbose 具体类

目录

返回顶部