虚拟机执行 - JVM 面试题及答案
虚拟机执行
42.能说一下类的生命周期吗?
一个类从被加载到虚拟机内存中开始,到从内存中卸载,整个生命周期需要经过七个阶段:加载 (Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading),其中验证、准备、解析三个部分统称为连接(Linking)。

43.类加载的过程知道吗?
加载是JVM加载的起点,具体什么时候开始加载,《Java虚拟机规范》中并没有进行强制约束,可以交给虚拟机的具体实现来自由把握。
在加载过程,JVM要做三件事情:

1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
加载阶段结束后,Java虚拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区之中了,方法区中的数据存储格式完全由虚拟机实现自行定义,《Java虚拟机规范》未规定此区域的具体数据结构。
类型数据妥善安置在方法区之后,会在Java堆内存中实例化一个java.lang.Class类的对象, 这个对象将作为程序访问方法区中的类型数据的外部接口。
44.类加载器有哪些?
主要有四种类加载器:
启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。
用户自定义类加载器 (user class loader),用户通过继承 java.lang.ClassLoader类的方式自行实现的类加载器。
45.什么是双亲委派机制?

双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。
46.为什么要用双亲委派机制?
答案是为了保证应用程序的稳定有序。
例如类java.lang.Object,它存放在rt.jar之中,通过双亲委派机制,保证最终都是委派给处于模型最顶端的启动类加载器进行加载,保证Object的一致。反之,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的 ClassPath中,那系统中就会出现多个不同的Object类。
47.如何破坏双亲委派机制?
如果不想打破双亲委派模型,就重写ClassLoader类中的fifindClass()方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。而如果想打破双亲委派模型则需要重写loadClass()方法。
48.历史上有哪几次双亲委派机制的破坏?
双亲委派机制在历史上主要有三次破坏:

第一次破坏
双亲委派模型的第一次“被破坏”其实发生在双亲委派模型出现之前——即JDK 1.2面世以前的“远古”时代。
由于双亲委派模型在JDK 1.2之后才被引入,但是类加载器的概念和抽象类 java.lang.ClassLoader则在Java的第一个版本中就已经存在,为了向下兼容旧代码,所以无法以技术手段避免loadClass()被子类覆盖的可能性,只能在JDK 1.2之后的java.lang.ClassLoader中添加一个新的 protected方法findClass(),并引导用户编写的类加载逻辑时尽可能去重写这个方法,而不是在 loadClass()中编写代码。
第二次破坏
双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷导致的,如果有基础类型又要调用回用户的代码,那该怎么办呢?
例如我们比较熟悉的JDBC:
各个厂商各有不同的JDBC的实现,Java在核心包\lib里定义了对应的SPI,那么这个就毫无疑问由启动类加载器加载器加载。
但是各个厂商的实现,是没办法放在核心包里的,只能放在classpath里,只能被应用类加载器加载。那么,问题来了,启动类加载器它就加载不到厂商提供的SPI服务代码。
为了解决这个问题,引入了一个不太优雅的设计:线程上下文类加载器 (Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContext-ClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
JNDI服务使用这个线程上下文类加载器去加载所需的SPI服务代码,这是一种父类加载器去请求子类加载器完成类加载的行为。
第三次破坏
双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求而导致的,例如代码热替换(Hot Swap)、模块热部署(Hot Deployment)等。
OSGi实现模块化热部署的关键是它自定义的类加载器机制的实现,每一个程序模块(OSGi中称为 Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi环境下,类加载器不再双亲委派模型推荐的树状结构,而是进一步发展为更加复杂的网状结构。
49.你觉得应该怎么实现一个热部署功能?
我们已经知道了Java类的加载过程。一个Java类文件到虚拟机里的对象,要经过如下过程:首先通过Java编译器,将Java文件编译成class字节码,类加载器读取class字节码,再将类转化为实例,对实例newInstance就可以生成对象。
类加载器ClassLoader功能,也就是将class字节码转换到类的实例。在Java应用中,所有的实例都是由类加载器,加载而来。
一般在系统中,类的加载都是由系统自带的类加载器完成,而且对于同一个全限定名的java类(如com.csiar.soc.HelloWorld),只能被加载一次,而且无法被卸载。
这个时候问题就来了,如果我们希望将java类卸载,并且替换更新版本的java类,该怎么做呢?
既然在类加载器中,Java类只能被加载一次,并且无法卸载。那么我们是不是可以直接把Java类加载器干掉呢?答案是可以的,我们可以自定义类加载器,并重写ClassLoader的findClass方法。
想要实现热部署可以分以下三个步骤:
- 销毁原来的自定义ClassLoader
- 更新class类文件
- 创建新的ClassLoader去加载更新后的class类文件。
到此,一个热部署的功能就这样实现了。
50.Tomcat的类加载机制了解吗?
Tomcat是主流的Java Web服务器之一,为了实现一些特殊的功能需求,自定义了一些类加载器。
Tomcat类加载器如下:

Tomcat实际上也是破坏了双亲委派模型的。
Tomact是web容器,可能需要部署多个应用程序。不同的应用程序可能会依赖同一个第三方类库的不同版本,但是不同版本的类库中某一个类的全路径名可能是一样的。如多个应用都要依赖hollis.jar,但是A应用需要依赖1.0.0版本,但是B应用需要依赖1.0.1版本。这两个版本中都有一个类是com.hollis.Test.class。如果采用默认的双亲委派类加载机制,那么无法加载多个相同的类。
所以,Tomcat破坏了双亲委派原则,提供隔离的机制,为每个web容器单独提供一个WebAppClassLoader加载器。每一个WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交CommonClassLoader加载,这和双亲委派刚好相反。
作者:三分恶
来源:https://www.cnblogs.com/three-fighter/p/15743700.html
最近建一些几十个工作内推群,各大城市都有,群里目前已经收集了很多内推岗位,大厂、中厂、小厂、外包都有。 欢迎HR、开发、测试、运维和产品加入。

扫描下方微信,备注:网站+所在城市,即可拉你进工作内推群。
