Jvm常见问题
1. 为什么用元空间替换永久代
- 避免OOM异常:永久代中存放了很多JVM需要的类信息,这些数据大多数是不会被清理的,所以Full GC往往无法回收多少空间。而永久代的空间是有限的,如果经常加载新的类进来或者频繁的创建和删除类,可能会导致OOM异常。而元空间是直接分配在本地内存中,不受JVM内存限制,可以根据实际需要进行动态调整,从而避免OOM异常。
- 支持更大的堆外内存:在永久代中,PermSize和MaxPermSize设置的大小决定了永久代的上限,而且永久代在jvm中能分配到的内存比较小的。而在元空间是在本地内存中分配的,本地内存的大小往往要比jvm大的多。
2. 为什么调整字符串常量池的位置
因为方法区的大小是有限制的,而且方法区的GC回收效率太低,当字符串常量池中存储的字符串数量不断增加时,会导致方法区内存不足,从而导致OutOfMemoryError异常。调整到了Java堆内中也是为了更好的解决这个问题,通过GC机制自动管理内存分配和回收。
3. 什么是执行引擎
执行引擎是Java虚拟机核心组件之一,主要负责将Java字节码转换为机器指令,并执行这些指令。其主要任务包括:
- 解释字节码:执行引擎读取字节码并将其转换为机器指令。
- 执行指令:执行引擎执行生成的机器指令,完成程序的运行。
- 管理内存:执行引擎负责管理Java虚拟机的内存,包括堆内存和栈内存等。
- 优化代码:执行引擎可以对代码进行一些优化,以提高程序的性能。
4. 成员变量、局部变量、类变量分别存储在内存的什么地方
- 成员变量:是定义在类中的变量,它们属于类的实例,也就是说每个对象都有自己的一组成员变量。在Java中,成员变量存储在Java堆中,由Java虚拟机进行管理。
- 局部变量:是定义在方法中的变量,它们只在方法中使用,并且每次方法调用时都会创建一个新的局部变量。在Java中,局部变量存储在Java栈中,由Java虚拟机进行管理。
- 类变量:也称为静态变量,它们是定义在类中但不属于任何对象的变量,属于所有对象共享变量,它们的生命周期与类相同。在JDK8之前,类变量是存储在方法区中,JDK8开始,永久代被移除了,取而代之的是元空间,但元空间中存储的主要是.class文件的元数据信息,类变量的存储位置便由方法区转到了堆内存中。
5. 为什么使用堆外内存
- 提高了Java程序的性能:堆外内存是直接在操作系统的内存中分配的,不受Java虚拟机堆内存的限制,因此可以更快地访问堆外内存中的数据,从而提高Java程序的性能。
- 减少了Java虚拟机的垃圾回收压力:堆外内存的使用可以减少Java虚拟机堆内存的使用,从而减少垃圾回收的频率和压力,提高Java程序的稳定性和可靠性。
- 允许使用更大的内存空间:堆外内存不受Java虚拟机堆内存的限制,可以使用更大的内存空间来存储数据,从而支持更大规模的数据处理和计算。
- 支持更高效的数据传输:使用堆外内存可以避免Java虚拟机堆内存和操作系统内存之间的数据复制和传输,从而提高数据传输的效率。
6. 什么是深拷贝和浅拷贝
- 浅拷贝(shallowCopy):是指复制对象时,只复制了对象的引用,新对象和原对象共享同一个对象实例。如果原对象发生了改变,新对象也会随之改变。
- 深拷贝(deepCopy):是指复制对象时,不仅复制了对象的引用,还复制了对象所包含的所有数据。新对象和原对象之间没有任何关系,它们各自拥有自己的数据副本。
7. 堆栈的区别
- 物理地址
- 堆的物理地址分配对对象是不连续的。
- 栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。
- 内存分配
- 堆因为是不连续的,所以分配的内存是在
运行期
确认的,因此大小不固定。一般堆大小远远大于栈。 - 栈是连续的,所以分配的内存大小要在
编译期
就确认,大小是固定的。
- 堆因为是不连续的,所以分配的内存是在
- 存放的内容
- 堆存放的是对象的实例和数组。因此该区更关注的是数据的存储。
- 栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。
- 程序的可见度
- 堆对于整个应用程序都是共享、可见的。
- 栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
8. 对象的创建方式有哪些
- 使用关键字new创建对象:使用new关键字可以创建一个新的对象,语法如下:
ClassName objectName = new ClassName();
- 使用构造函数创建对象:可以通过定义一个构造函数,在创建对象时传入相应的参数,从而创建一个新的对象。语法如下:
ClassName objectName(argument1, argument2, ...) {
// 构造函数的代码
}
- 使用clone()方法创建对象:clone()方法是Object类中的一个方法,可以用来创建一个新的对象,该对象与原对象具有相同的属性值和状态。语法如下:
ClassName objectName = (ClassName) sourceObject.clone();
- 使用反序列化创建对象:可以将对象序列化成字节流,然后再反序列化成新的对象。这种方式可以实现对象的深拷贝,因为序列化和反序列化过程中会将对象的所有数据都复制一份。
FileInputStream fis = new FileInputStream("objectFile");
ObjectInputStream ois = new ObjectInputStream(fis);
ClassName objectName = (ClassName) ois.readObject();
9. 对象创建的主要流程
- 加载类:在创建对象之前,需要先加载该对象所属的类。类的加载过程包括将类的字节码文件从磁盘读取到内存中,并进行验证、准备和解析等操作。
- 分配内存:在加载类之后,需要为对象分配内存空间。Java使用堆内存来存储对象,堆内存可以动态地分配和回收。
- 初始化对象:在分配内存之后,需要对对象进行初始化。对象的初始化包括执行类的构造函数和初始化实例变量。
- 返回对象的引用:初始化完成后,Java虚拟机会返回对象的引用,这个引用可以被用于访问对象的属性和方法。
- 垃圾回收:当对象不再被引用时,Java虚拟机会将其标记为垃圾对象,并在适当的时候进行垃圾回收。
需要注意的是,对象创建的流程是由Java虚拟机自动完成的,程序员只需要通过new关键字来创建对象即可,不需要手动管理对象的生命周期。
10. 堆内存的分配方式有哪些
- 指针碰撞:是指从一段连续的内存区域开始依次向后移动。将所有使用中的内存放到一边,空闲的内存放到另一边,中间放着一个指针作为分界点指示器。当需要为某个对象分配内存时,指针会向空闲的内存移动一段与对象大小相等的位置。
指针碰撞的优点是实现简单,适用于内存分配比较均匀的场景。但是,在内存分配不均匀的情况下,指针碰撞会导致内存碎片化,从而影响程序的性能。
- 空闲列表:是将堆内存空间划分成若干个大小相等的内存块,并维护一个空闲列表,列表中保存着当前可用的内存块。当程序需要分配内存时,JVM会在空闲列表中查找最小的可用内存块分配给对象,并将其从空闲列表中移除,从而实现内存分配。
空闲列表的优点是能够有效地避免内存碎片化,从而提高内存分配的效率。但是,空闲列表的实现比较复杂,需要维护一个空闲列表和一个哈希表,而且需要考虑并发访问的问题。
11. 如何处理并发安全问题
对象的创建在虚拟机中是一个非常频繁的行为,哪怕只是修改一个指针所指向的位置,在并发情况下也是不安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案:
- 锁分段技术:是一种基于锁的内存分配方式,它将堆内存空间划分成若干个大小相等的内存块,并在每个内存块上加锁,从而保证同一时刻只有一个线程能够访问该内存块。
锁分段技术虽然能够保证多线程并发安全。但是,锁分段技术的实现比较复杂,需要维护一个锁数组和一个内存块链表,而且会产生较多的上下文切换和锁竞争。
- TLAB技术(Thread Local Allocation Buffer):是一种基于线程本地缓存的内存分配方式,它将堆内存空间划分成若干个大小相等的内存块,并为每个线程维护一个TLAB缓存,缓存中保存着当前线程经常使用的内存块。当程序需要分配内存时,JVM会首先在TLAB缓存中查找是否有可用的内存块,如果有,则直接分配给程序使用,否则才会向堆内存中申请内存块。
TLAB技术的实现比较简单,能够减少锁竞争和上下文切换,从而提高程序的性能。但是,TLAB技术需要占用额外的内存空间,并且需要对TLAB缓存进行管理和维护,否则会导致内存泄漏和性能下降。
12. 对象头中包含哪些部分
- Mark Word(标记字):它是一个用于标记对象的位图,用于记录对象的状态信息,如对象的哈希码、锁信息、GC信息等。
- Class Pointer(类指针):它指向对象的类元数据信息,包括类名、字段、方法等。
- Array Length(数组长度):对于数组对象,对象头中会保存数组的长度信息。
- Hash Code(哈希码):它是对象的哈希值,用于快速定位对象在内存中的位置。
- GC Information(GC信息):它记录了对象的GC信息,包括对象的年龄、是否可达等。
- Padding(填充):为了对齐内存,对象头中可能还会包含一些填充字节。
13. 对象的访问定位方式有哪些
Java 程序需要通过 JVM 栈上的reference访问堆中的具体对象。对象的访问方式取决于 JVM 虚拟机的实现。目前主流的访问方式有句柄
和 直接指针
两种方式。
- 句柄方式:是Java中最常用的对象访问方式,它通过句柄池中存放的对象实例地址来访问对象。在Java中,堆内存会划分出一小块内存作为句柄池,里面存放的是对象的实例地址。访问的过程是先通过栈里面的reference存放的具体句柄指针找到堆上的句柄池中的某个句柄,再通过该句柄存放的对象地址指针来找到实际的对象,从而访问对象的属性和方法。
- 直接指针方式:是指栈里面的reference存储的就是实际对象的地址指针,直接可以通过这个直接指针来访问到实际的对象。
使用句柄方式的优点在于栈里面的reference存储的是稳定不变的句柄地址,如果对象的地址发生变化,只需更新句柄池中存放的对象地址就好,reference中存储的句柄地址无需变动。缺点就是会产生一次间接访问的开销,效率相对较低。直接指针的优点就在于无需间接访问的开销,效率较高。缺点就是当对象地址发生变化时,需要手动更新栈里面的指针的值,在多线程编程中会存在安全问题。
14. JRE、JDK、JVM 及 JIT 之间有什么不同?
- JRE(Java Runtime Environment):是 Java 运行的标准环境,提供了JVM来解释字节码并运行Java应用程序。它包含了 Java 虚拟机(JVM)、Java 类库和支持文件等组件,可以在不同的操作系统上运行 Java 程序。但不包含 Java 开发工具包(JDK)中的开发工具和编译器等组件。
- JDK(Java Development Kit):是 Java 开发工具包,是 Java 程序员开发 Java 程序所必需的工具集。它包含了 JRE、Java 编译器、Java 调试器、Java 文档生成器、Java 测试工具等组件,可以帮助程序员开发、调试和测试 Java 程序。JDK 中包含的 JRE 与 JRE 相同,但包含了更多的开发工具和编译器等组件。
- JVM(Java Virtual Machine):是一种虚拟机。它可以将 Java 代码编译成字节码,并解释执行或者将字节码编译成本地机器语言执行,从而实现跨平台运行。
- JIT(Just-In-Time):是 JVM 中的一部分,是一种动态编译技术。它可以将热点代码编译成本地代码,提高程序的执行效率。JIT 可以根据程序运行时的实际情况,动态地生成本地代码,并将其保存在缓存中,以便下次程序运行时直接使用。
15. 什么是内存泄漏
内存泄漏(Memory Leak)是指程序在使用动态分配内存时,由于某些原因未能及时释放已分配的内存,导致这部分内存无法被再次使用,从而浪费了宝贵的系统资源。
通常情况下,内存泄漏是由程序员在编写代码时出现的错误导致的。例如,程序中可能存在未被正确关闭的文件、数据库连接、网络连接等资源,这些资源在使用后应该被及时释放,否则就会导致内存泄漏。
内存泄漏的危害很大,它会导致系统的内存资源逐渐减少,直到最终系统崩溃。因此,程序员在编写代码时应该注意及时释放已分配的内存资源,避免出现内存泄漏问题。
16. 什么是内存溢出
内存溢出(Memory Overflow)是指程序在使用动态分配内存时,申请的内存空间超出了系统可用的内存大小,导致程序无法正常运行。
通常情况下,内存溢出是由程序员在编写代码时出现的错误导致的。例如,程序中可能存在申请的内存空间大小计算错误、数组越界等问题,这些问题都可能导致程序申请的内存空间超出系统可用的内存大小,从而导致内存溢出。
内存溢出的危害很大,它会导致程序崩溃或者系统崩溃,甚至可能会导致数据丢失或者系统安全问题。因此,程序员在编写代码时应该仔细检查内存申请和使用的代码,避免出现内存溢出问题。此外,也可以通过增加系统可用内存大小、优化程序代码等方式来解决内存溢出问题。
17. 简述Java垃圾回收机制
Java垃圾回收机制是一种自动内存管理机制,它可以自动检测不再被程序所使用的对象并将其回收,从而释放内存空间,避免了内存泄漏和内存溢出等问题。
18. 垃圾回收有哪些优点
- 避免内存泄漏:垃圾回收器能够自动检测不再被程序使用的对象并将其回收,避免了内存泄漏的问题。
- 减少程序员的负担:垃圾回收器能够自动管理内存空间,程序员不需要手动管理内存,从而减少了程序员的负担。
- 提高程序的稳定性和可靠性:垃圾回收器能够及时清除不再被程序使用的对象,避免了内存溢出和内存泄漏等问题,从而提高了程序的稳定性和可靠性。
- 提高程序的运行效率:垃圾回收器能够及时回收不再被程序使用的内存空间,从而提高了程序的运行效率。
19. 能保证 GC 执行吗
不能,虽然可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC的执行。
20. Java 中都有哪些引用类型
- 强引用:是指在代码中普遍存在的引用,例如在方法中定义的局部变量、成员变量、静态变量、常量等。当一个对象被强引用时,即使内存不足,垃圾回收器也不会回收该对象,直到该对象的强引用被显式地赋值为 null 或者程序结束。
Object object = new Object();
String str = "StrongReference";
- 软引用:是一种相对强引用更弱的引用类型,它用来描述那些还有用但是不那么重要的对象。当一个对象被软引用时,它并不会被立即回收,而是等到内存不足时,垃圾回收器才会对其进行回收。
SoftReference<String> softRef = new SoftReference<String>(str);
- 弱引用:是一种更弱的引用类型,它用来描述那些几乎没有用处的对象。当一个对象被弱引用时,它也不会被立即回收,但是当垃圾回收器下次进行回收时,如果发现该对象还存在弱引用,那么就会将该对象回收。
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
- 虚引用:是最弱的引用类型,它主要用于跟踪对象被垃圾回收器回收的状态。虚引用不能单独使用,必须和引用队列一起使用,也无法通过虚引用来获取被引用的对象。虚引用在创建时必须提供一个引用队列作为参数,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序对象的回收情况。
ReferenceQueue phantomQueue1 = new ReferenceQueue();
PhantomReference<Object> sf1 = new PhantomReference<>(new Object(), phantomQueue);
21. 如何判断对象的存活性
- 引用计数器法:是一种比较简单的垃圾回收算法,它的基本思想是为每个对象维护一个引用计数器,当一个对象被引用时,计数器加1,当一个对象不再被引用时,计数器减1。当一个对象的计数器为0时,说明这个对象已经不再被引用,可以被垃圾回收器回收。引用计数器法实现简单,但是消耗内存空间,且无法解决循环引用的问题,容易导致内存泄漏。
- 可达性分析算法:是一种更为高效的垃圾回收算法,它的基本思想是从GC Roots出发,遍历所有引用链条,把能过访问到的对象进行标记,最终会把没有标记的对象进行回收。GC Roots是一种特殊的对象,是程序运行过程中必须存在的对象,只有找到这种对象作为根对象,往下的搜索可达的对象才有意义,毕竟被程序运行中必须存在的对象所依赖的对象那肯定也不能被回收的。可达性分析算法能够有效解决循环引用的问题,但是实现相对复杂,且可能会产生内存碎片。
22. 什么情况下会触发GC
在Java中,GC的触发时机通常由垃圾回收器自行决定,JVM会根据当前的内存使用情况和垃圾回收器的策略,动态地调整垃圾回收器的触发时机。一般来说,垃圾回收器会在以下情况下触发GC:
- 当堆内存空间不足时,垃圾回收器会触发GC,回收一些无用的对象,释放空间。
- 当新生成的对象无法分配到堆内存空间时,垃圾回收器会触发GC,回收一些无用的对象,释放空间。
- 当垃圾回收器检测到某个对象没有被引用时,也会触发GC,回收这个对象。
需要注意的是,垃圾回收器的触发时机对于程序的性能和稳定性都有着重要的影响。如果垃圾回收器的触发时机不当,可能会导致程序出现频繁的Full GC,甚至导致程序崩溃。因此,在实际开发中,需要根据具体情况选择合适的垃圾回收器,并对垃圾回收器的参数进行适当的调整,以达到最佳的性能和稳定性。
23. 什么是Young GC
Young GC(Young Generation Garbage Collection)也称为Minor GC,是指对Java堆中新生代(Young Generation)中的垃圾对象进行回收的垃圾回收过程。当应用程序向堆中申请内存时,如果没有足够的空间,就会触发一次Young GC。Young GC会扫描新生代中的所有对象,将那些已经不再被引用的对象回收掉,并将还存活的对象复制到Survivor区或晋升到老年代中。
Young GC相对于Full GC来说,执行速度更快,因为它只需要扫描和回收新生代中的垃圾对象,而不需要扫描和回收整个堆内存。但是,Young GC的回收效率可能会受到一些因素的影响,例如新生代的大小、Survivor区的大小、对象的年龄等等。如果Young GC的频率过高,就可能会导致应用程序的性能下降,因为它会消耗大量的CPU资源和内存带宽。
为了优化Young GC的效果,可以通过调整JVM参数来改变新生代的大小、Survivor区的大小、对象的年龄阈值等等,以达到更好的垃圾回收效果。
24. 什么是Full GC
Full GC是指Full Garbage Collection的缩写,也称为全局垃圾回收。在Full GC过程中,Java虚拟机会对整个Java堆中的对象进行垃圾回收,包括新生代和老年代中的所有对象,以确保对整个Java堆中的对象进行一次全面的扫描和清理。
Full GC的执行通常会伴随着较长的停顿时间,因为它需要对整个Java堆内存区域进行扫描和清除。为了减少Full GC的执行频率和停顿时间,可以采用一些优化技术,如调整Java堆内存大小、调整垃圾回收器的参数等。
25. 什么情况下会触发Full GC
Full GC通常是在Young GC无法完成对所有垃圾对象的回收时触发的。以下是可能会触发Full GC的情况:
- 当堆中的垃圾已经达到一定量(默认是90%)时,会触发Full GC。
- 手动调用System.gc()方法。
- 内存分配失败时,也有可能进行Full GC。
26. 垃圾收集算法有哪些类型
常见的垃圾收集算法有:复制算法、标记 -清除算法、标记-整理算法、分代算法,我们常用的垃圾回收器一般都采用分代算法。
- 复制算法:复制算法将堆内存分为两个区域,一部分称为from区域,另一部分称为to区域。当from区域中的对象被垃圾回收时,将存活的对象复制到to区域中,同时清空from区域。这个过程会不断重复,以保证to区域中始终有一定数量的存活对象。
- 缺点:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。
- 优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。
- 标记-清除算法:分为两个阶段:标记阶段和清除阶段。标记阶段会遍历整个堆内存,标记出所有还在使用的对象,并将未标记的对象视为垃圾对象。清除阶段会遍历堆内存,清除所有未被标记的对象。
- 缺点:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。
- 优点:实现简单,不需要对象进行移动。
- 标记-整理算法:标记-整理算法是标记-清除算法的改进版。它在清除阶段时,将存活的对象移动到堆的一端,然后将这个端点移动到下一个空闲的位置。这样可以减少内存碎片,提高内存利用率。
- 缺点:仍需要进行局部对象移动,一定程度上降低了效率。
- 优点:解决了标记-清理算法存在的内存碎片问题。
- 分代算法:分代算法将堆内存分为年轻代和老年代两个区域,分别采用不同的垃圾回收策略。年轻代采用复制算法或标记-整理算法,老年代采用标记-清除算法或标记-整理算法。这样可以根据对象的生命周期来选择合适的垃圾回收策略,提高垃圾回收的效率。
27. JVM有哪些垃圾收集器
- Serial收集器:Serial收集器是一种单线程的垃圾收集器,它采用标记-清除算法或标记-整理算法进行垃圾回收。Serial收集器适用于小型的应用程序或系统,因为它只能在一个线程上执行垃圾回收,无法利用多核处理器的优势。
- Parallel收集器:Parallel收集器是一种多线程的垃圾收集器,它采用多个线程并发地进行垃圾回收。Parallel收集器适用于大型的应用程序或系统,因为它可以利用多核处理器的优势,提高垃圾回收的效率。
- CMS收集器:CMS收集器是一种基于标记-清除算法的垃圾收集器,它采用并发的方式进行垃圾回收。CMS收集器可以避免Full GC的停顿时间,但会产生较长时间的Minor GC停顿时间。
- G1收集器:G1收集器是一种基于分代算法的垃圾收集器,它采用区域化的方式进行垃圾回收。G1收集器可以避免Full GC的停顿时间,并且具有更好的内存利用率和吞吐量。
- ZGC收集器:ZGC收集器是一种面向大型应用程序的垃圾收集器,它采用增量式的方式进行垃圾回收。ZGC收集器可以支持更大的堆内存,并且具有更好的响应时间和可伸缩性。
28. JVM常用参数有哪些
- -Xms和-Xmx:设置JVM的最小和最大堆大小,默认值为物理内存的1/4和1/2。可以通过这两个参数来调整JVM的堆大小,以适应不同的应用程序需求。
- -XX:MaxPermSize:设置永久代的最大大小,默认值为64MB。可以通过这个参数来调整永久代的大小,以适应需要大量使用反射或动态代理的应用程序。
- -XX:NewSize和-XX:MaxNewSize:设置新生代的初始大小和最大大小,默认值分别为物理内存的1/4和1/2。可以通过这两个参数来调整新生代的大小,以适应应用程序的内存需求。
- -XX:SurvivorRatio:设置新生代中eden区和survivor区的比例,默认值为8:1。可以通过这个参数来调整新生代中eden区和survivor区的大小比例,以适应应用程序的内存需求。
- -XX:+UseConcMarkSweepGC和-XX:+UseG1GC:分别启用CMS和G1垃圾收集器,默认使用Serial垃圾收集器。可以通过这两个参数来选择不同的垃圾收集器,以适应应用程序的内存需求和性能要求。
- -XX:+PrintGC和-XX:+PrintGCDetails:分别启用垃圾收集的日志输出和详细日志输出,可以通过这两个参数来查看垃圾收集的情况,以便进行优化。
29. linux怎么修改jvm启动参数
- 使用命令行参数:可以在启动JVM时,使用命令行参数来设置JVM启动参数。例如:
java -Xms512m -Xmx1024m MyApp
- 使用环境变量:可以将JVM启动参数设置为环境变量,在JVM启动时,通过读取环境变量来设置JVM启动参数。例如:
export JAVA_OPTS="-Xms512m -Xmx1024m"
java MyApp
- 修改JVM配置文件:可以通过修改JVM的配置文件来设置JVM启动参数。在Linux中,JVM的默认配置文件为$JAVA_HOME/jre/lib/amd64/jvm.cfg。可以通过编辑该文件来设置JVM启动参数。例如:
-Xms512m
-Xmx1024m
无论使用哪种方式,都需要注意JVM启动参数的正确性和合理性。不当的JVM启动参数可能会导致JVM性能下降或者出现其他异常情况。
30. 类加载步骤有哪些
类加载执行过程是Java虚拟机(JVM)中的一个重要概念,它指的是将Java类的字节码文件加载到内存中,并将其转换为Java对象的过程。类加载执行过程主要包括以下几个步骤:
- 加载:类加载器根据类的全限定名,在classpath中查找并加载对应的字节码文件。
- 验证:在加载字节码文件之前,需要对字节码进行验证,以确保其符合Java虚拟机规范。验证的过程包括检查字节码文件是否符合规范、检查字节码文件中的符号引用是否合法、检查字节码文件是否被篡改等。
- 准备:在验证通过后,需要为类的静态变量分配内存,并设置默认初始值。同时,还需要为类中的常量池赋予符号引用,以便在后续的运行过程中使用。注意这里所说的初始值概念,比如一个类变量定义为:public static int v = 80;实际上变量 v 在准备阶段过后的初始值为 0 而不是 80。
- 解析:解析指的是将类中的符号引用转换为直接引用的过程。例如,将类中的方法名转换为实际的方法地址。
- 初始化:初始化指的是执行类构造器中的代码,以及执行静态代码块中的代码。在初始化阶段,可以使用类中的静态变量和方法。
- 使用:类加载完成后,可以通过类的Class对象来获取类的各种信息,并创建该类的实例。
需要注意的是,类加载执行过程是一个递归的过程,每个类都会在其父类加载完成后再进行加载和初始化。同时,类加载器也是一个重要的概念,它负责加载和管理类的加载过程。在实际开发中,需要了解类加载执行过程和类加载器的原理,以便能够更好地进行调试和优化。
31. 类加载器有哪些
Java中的类加载器(ClassLoader)是一个重要的概念,它负责加载和管理Java类的加载过程。常见的Java类加载器包括:
- Bootstrap ClassLoader:Java虚拟机自带的类加载器,主要负责加载JDK自带的核心类库,如:JRE/lib/rt.jar中的类。
- Extension ClassLoader:扩展类加载器,负责加载ext目录或者jre/lib/ext目录下的类。
- Application ClassLoader:应用程序类加载器,负责加载应用程序的类路径中的类和第三方库中的类。
- User-defined ClassLoader:开发人员根据需求创建的自定义类加载器。
32. 什么是双亲委派模型
双亲委派模型指的是当一个类加载器需要加载一个类时,它会首先委派给父类加载器去加载,如果父类加载器可以加载该类,则直接返回;如果父类加载器无法加载该类,则交给自己去加载。在双亲委派模型中,每个类加载器都有一个父类加载器,它们形成了一个类加载器的层次结构。
33. 为什么需要双亲委派模型
- 防止类重复加载:当多个classLoader下存在同名类时,根据双亲委派模型,首先会将类委托给父classLoader去加载,确保每个类只会加载一次。
- 提高性能:当需要加载类的时候,先向上委托给父classLoader去加载,如果已经被加载过了则会直接返回,无需再次加载,从而大大提升性能。
34. JVM 调优的工具
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。第三方有:MAT(MemoryAnalyzerTool)、GChisto。
- jstat:用于监视 JVM 的各种统计信息,如内存使用情况、垃圾回收情况、类加载情况等。
- jmap:用于生成 JVM 的内存转储文件,以便分析 JVM 的内存使用情况和对象分布情况。
- jstack:用于生成 JVM 的线程转储文件,以便分析线程的状态和调用栈信息。
- jconsole:用于监视 JVM 的运行状况和资源使用情况,并提供图形化界面。
- jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的
变化、gc 变化等。 - JProfiler:是一款全面的 Java 应用程序性能分析工具,可以监视 JVM 的运行状况、分析内存泄漏和性能瓶颈等问题,并提供详细的报告和分析结果。
- MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的
Javaheap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。 - GChisto,一款专业分析gc日志的工具。
35. 常用的调优命令
- CPU指标常见的命令
// 显示系统各个进程的资源使用情况
top
// 查看某个进程中的线程占用情况
top -Hp pid
// 查看当前 Java 进程的线程堆栈信息
jstack pid
- JVM 内存指标常见的命令:
// 查看当前的 JVM 参数配置
ps -ef | grep java
// 查看 Java 进程的配置信息,包括系统属性和JVM命令行标志
jinfo pid
// 输出 Java 进程当前的 gc 情况
jstat -gc pid
// 输出 Java 堆详细信息
jmap -heap pid
// 显示堆中对象的统计信息
jmap -histo:live pid
// 生成 Java 堆存储快照dump文件
jmap -F -dump:format=b,file=dumpFile.phrof pid
36. 高CPU占用排查步骤
- top :命令找到占用CPU高的进程PID。
- jstack PID > threadDump.txt :导出当前进程的dump文体,各线程的状态信息;
- top -Hp pid :找出PID的进程占用CPU过高的线程TID。
- printf “%x\n” TID:将10进制的TID转为16进制TID,因为dump文件中的线程ID是16进制的;
- less threadDump.txt:查找转换成为16进制的线程TID,找到对应的线程调用栈分析问题。