目录
3.Class.forName()和ClassLoader.loadClass()的区别?
16.什么是TLAB(Thread Local Allocation Buffer)?
22.什么是Minor GC,Major GC,Full GC?
1.类加载的生命周期?
类加载的生命周期可以分为以下几个阶段:
1. 加载(Loading):将类的字节码文件加载到内存中。
2. 验证(Verification):确保加载的字节码文件符合 JVM 规范,不会造成安全问题。
3. 准备(Preparation):为静态变量分配内存空间,并设置默认初始值。
4. 解析(Resolution):将符号引用解析为直接引用。符号引用包括类或接口、字段、方法等。
5. 初始化(Initialization):对类进行初始化,包括执行静态变量赋值语句和静态代码块。在初始化阶段,JVM 会确保类的初始化是线程安全的。
6. 使用(Using):使用类或接口,包括创建对象、调用方法等。
7. 卸载(Unloading):当类不再被使用时,从内存中卸载,释放内存空间。
需要注意的是,类的加载顺序是按需加载的,即在使用某个类或接口之前,它的字节码文件会被加载、验证、准备、解析和初始化。同时,类加载的生命周期可能会被中断或重启,例如通过类加载器重新加载字节码文件。
2.类加载器的层次?
在Java中,类加载器按照层次结构分为以下几种:
1. 启动类加载器(Bootstrap ClassLoader):它是Java虚拟机的一部分,负责加载Java的核心类库,如rt.jar中的类。
2. 扩展类加载器(Extension ClassLoader):它是sun.misc.Launcher$ExtClassLoader类的实例,负责加载Java的扩展类库,如jre/lib/ext目录下的类。
3. 应用程序类加载器(Application ClassLoader):它是sun.misc.Launcher$AppClassLoader类的实例,负责加载应用程序的类,是默认的类加载器。
4. 自定义类加载器:开发者可以通过继承java.lang.ClassLoader类来实现自己的类加载器,以满足特定的加载需求。自定义类加载器可以覆盖父类加载器的行为。
这些类加载器按照层次结构依次加载类,子类加载器可以访问父类加载器加载的类,但父类加载器无法访问子类加载器加载的类。这种层次结构保证了类的隔离性和安全性。
3.Class.forName()和ClassLoader.loadClass()的区别?
Class.forName() 和 ClassLoader.loadClass() 都是 Java 用于加载类的方法,但是它们之间有一些区别。
Class.forName() 是一个静态方法,它接受一个字符串参数,该参数是要加载的类的全限定名。它会返回一个 Class 对象,如果没有找到指定的类,会抛出 ClassNotFoundException 异常。Class.forName() 方法还有一个重载方法,可以指定一个布尔值参数来控制是否初始化加载的类。
ClassLoader.loadClass() 是一个实例方法,它接受一个字符串参数,该参数是要加载的类的全限定名。它会返回一个 Class 对象或者 null,如果没有找到指定的类。ClassLoader.loadClass() 方法不会抛出 ClassNotFoundException 异常,而是返回 null。
另一个区别是 Class.forName() 方法会触发静态初始化代码的执行,而 ClassLoader.loadClass() 方法不会。静态初始化代码是在类加载过程中执行的,一般用于初始化静态字段和执行静态块中的代码。
因此,Class.forName() 方法更适用于需要动态加载和初始化类的情况,而 ClassLoader.loadClass() 方法更适用于只需要加载类但不需要初始化的情况。
4.JVM类加载器机制有哪些?
1.全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入。
2.父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
3.缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效。
4.双亲委派机制,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父类加载器去完成,依次向上,因此,所有的类加载器请求最终都应该被传递到顶层的启动类加载器中,只有当付加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
5.双亲委派机制过程?
双亲委派机制是一种用于处理Java类加载的机制。
当一个Java程序运行时,Java虚拟机(JVM)会根据需要动态加载需要的类文件。在加载类文件的过程中,JVM采用了双亲委派机制来确保类的安全性和一致性。
双亲委派机制的过程如下:
1. 当一个类需要被加载时,首先会去检查该类是否已经被加载过了。如果已经加载过,JVM会直接返回已经加载的类。
2. 如果该类还没有被加载过,则会将加载请求传递给父类加载器。父类加载器有可能是一个独立的类加载器,也有可能是JVM内置的类加载器。
3. 父类加载器首先会尝试加载该类。如果父类加载器找到了该类,就会返回给子类加载器,并由子类加载器返回给JVM。
4. 如果父类加载器无法找到该类,会将加载请求传递给它的父类加载器,重复步骤3。这个过程会一直持续到达到了最顶层的父类加载器(一般是Bootstrap类加载器)为止。
5. 如果所有的父类加载器都无法找到该类,最后会由当前类加载器尝试加载。如果当前类加载器找到了该类,就会返回给JVM。
6. 如果当前类加载器无法找到该类,会抛出ClassNotFoundException异常。
通过双亲委派机制,JVM可以保证类的加载是从上往下的,避免了重复加载和版本冲突的问题。同时,也可以确保Java核心类库的安全性,防止恶意代码替换核心类。
6.说说JVM内存整体结构,线程私有还是共享的?
JVM内存结构可以分为以下几个部分:
1. 线程私有部分:
- 程序计数器(Program Counter):用于记录当前线程执行的字节码指令地址。
- 虚拟机栈(VM Stack):每个线程在执行Java方法时,都会有一个对应的栈帧(Stack Frame),用于存储局部变量、操作数栈、动态链接、方法出口等信息。
- 本地方法栈(Native Method Stack):与虚拟机栈类似,用于支持Native方法的执行。
2. 线程共享部分:
- 堆(Heap):用于存储对象实例和数组,是JVM中最大的一块内存区域。堆被所有线程共享,每个线程的堆栈指针指向堆中的某个位置,用于分配内存和垃圾回收。
- 方法区(Method Area):用于存储类的结构信息(如运行时常量池、字段和方法数据)、静态变量、常量等。方法区也被所有线程共享。
- 运行时常量池(Runtime Constant Pool):存放编译期生成的各种字面量和符号引用。
需要注意的是,虽然堆和方法区是线程共享的,但是Java中的对象和类一般是线程私有的,每个线程都有自己独立的对象实例和类信息。
7.什么是程序计数器(线程私有)?
程序计数器(Program Counter,PC)是计算机体系结构中的一个特殊寄存器,用于存储当前正在执行的指令的地址。在单线程的程序中,程序计数器指向下一条将要执行的指令的地址。当一条指令执行完成后,程序计数器会自动递增,指向下一条指令的地址,以便继续执行。
对于多线程程序来说,每个线程都有自己的程序计数器。线程私有的程序计数器是为了支持多线程并发执行时的线程切换。当一个线程执行完毕后,程序计数器保存了下一个要执行的指令地址。当另一个线程被调度并开始执行时,它的程序计数器被加载,并开始从指定地址处执行指令。
程序计数器在计算机系统的执行过程中起到一个关键的作用,它充当了指令流的指针,帮助处理器按照指定的顺序执行指令,从而实现程序的逻辑控制。
8.什么是虚拟机栈(线程私有)?
虚拟机栈是Java虚拟机(JVM)中的一部分,用于管理线程的方法调用和局部变量等信息。每个线程在运行时都会创建一个对应的虚拟机栈。
虚拟机栈是线程私有的,即每个线程都拥有自己的虚拟机栈。它的生命周期与线程的生命周期一样,当线程创建时,虚拟机栈也被创建,当线程结束时,虚拟机栈也被销毁。
虚拟机栈由一系列栈帧(Stack Frame)组成,每个栈帧对应一个方法的调用。栈帧包含了方法的局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。每当一个方法被调用时,虚拟机都会创建一个新的栈帧,并将其推入虚拟机栈的栈顶,方法的执行就在这个栈帧中进行。
虚拟机栈的大小是有限制的,当栈中的栈帧数量超过了其允许的最大值,或者栈帧中的局部变量表和操作数栈超过了其允许的最大容量,就会抛出StackOverflowError或OutOfMemoryError异常。
虚拟机栈的大小可以通过设置JVM参数来进行调整,一般来说,较大的虚拟机栈能够容纳更多的栈帧,但同时也会占用更多的内存空间。