1.JVM虚拟机组成部分有哪些? Java运行时数据区包含哪些部分?各部分作用?是否存在GC及OOM问题
-
jvm组成部分:类加载器、运行时数据区、执行引擎
-
运行时数据区:方法区、堆、栈、程序计数器、本地方法栈
-
方法区:共享,存储由字节码文件解析出来的类型信息、常量、静态变量,
-
堆:共享,存储实例变量和数组,空间区域分为新生代和老年代,通过垃圾回收机制来进行管理,存在GC,存在OOM
-
栈:私有,存储的是对象的引用,每个线程会有独立的栈,每一个方法执行都会被放在栈中(先进后出),cpu在不同线程之间运行时,各自栈都会通过各自的栈帧来记录方法执行的步骤,存在OOM
栈帧:用于存储局部变量表、操作栈、动态链接、方法出口等信息
-
程序计数器:私有,记录当前线程执行到了哪一步
-
执行引擎:Execution Engine执行引擎负责解释命令,提交操作系统执行。
-
本地方法栈:私有,支持native方法的执行
2.类加载器有哪些?什么是双亲委派机制?有什么好处?
类加载器负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
类加载器的分类:
-
启动类加载器:负责加载Java核心类库(如java.lang、java.util等)。C++
-
扩展类加载器:负责加载Java的扩展类库(如javax包中的类)。
-
应用程序类加载器:负责加载应用程序的类,通常是通过加载classpath中指定的jar包及目录中class来加载的。
-
自定义加载器:通过继承
java.lang.ClassLoader
类的方式定制类的加载方式,可以根据需要加载特定的类文件。
双亲委派机制:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。
-
1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
-
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
-
3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
-
4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载
-
5、如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException
好处:
-
这种机制保证了类的唯一性和安全性,避免了类的重复加载和版本冲突问题。
3.类加载过程?
Java的类加载过程是指将Java类的字节码文件(.class文件)从文件系统中读取到JVM(Java虚拟机)中,并将其转换为JVM可以直接识别的数据结构的过程。整个过程可以大致分为七个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。
4.堆内存细分为哪些区域,各区域存放对象特点?
-
可以分为新生代和老年代
-
新生代:生命周期短,比较活跃
-
老年代:生命周期长,内存大
5.对象直接进入老年代的情况有哪些?
新到老(三种模式):
-
根据垃圾处理时的存活轮数,存活一轮年龄值加一,若年龄值>15则变为老年代
-
新new的对象会被放入新生代区,若新生代中内存满了,就会触发新生代的垃圾回收,进行垃圾回收,清理了垃圾,有内存了,则将对象放入新生代中,内存仍不足,则会把新new的对象放入老年代中。
-
新new的对象会被放入新生代区,若新new的对象太大,新生区内存不足,此时会触发新生代的垃圾回收,进行垃圾回收,清理后仍不够,则会直接把新new的对象放入老年代中。
-
若往老年代存放时,老年代内存不足,则会触发老年代进行垃圾回收,清理了垃圾,有内存了,则将对象放入老年代中,内存仍不足,则抛异常OOM堆溢出。
-
故老年代若触发了垃圾回收,新生代一定触发过;新生代触发了,老年代不一定触发。
-
6.如何判断一个对象是否可回收?可达性分析法工作原理,哪些是GCRoots根?
-
可达性分析算法:以栈中的变量为根节点向下进行遍历,若对堆中的对象有引用关系,则会对对象进行标记,没有被标记的对象就会视为垃圾。
没有被标记的不一定会被清除回收,在回收对象时,会考虑其他因素,比如对象的finalize()方法是否被调用过,如果未执行,则会先执行finalize()方法。在finalize()方法中,对象可以通过与GC Roots建立引用关系来逃脱垃圾回收。但需要注意的是,finalize()方法只会被执行一次。
在Java语言中,可以作为GC Roots的对象包括下面几种:
-
虚拟机栈(栈帧中的本地变量表)中的引用对象。
-
方法区中的类静态属性引用的对象。
-
方法区中的常量引用的对象。
-
本地方法栈中JNI(Native方法)的引用对象
7.垃圾回收算法及优缺点?为什么要分代垃圾回收?
-
标记清除:
遍历堆中的对象,找出没有被标记的对象,进行统一清理,会产生碎片
优点:速度比较快
缺点:会产生内存碎片,碎片过多,仍会使得连续空间少
标记和清除过程需要暂停应用程序,对性能有一定影响。
-
标记整理:
遍历堆中的对象,找出没有被标记的对象,边清理边移动,避免了碎片的产生,但是在移动对象时会触发"stop the world STW"(停止所有用户线程),效率较低
优点:无内存碎片
缺点:标记和整理过程也需要暂停应用程序,效率较低
-
复制算法:
在堆里面开辟两份大小相等空间(from区和to区),一份空间始终空着,垃圾回收时,将存活对象拷贝进入空闲空间;边清理边拷贝
优点:无内存碎片
缺点:占用空间多,内存利用率低
当存活对象较多时,复制过程会消耗大量时间。
-
年轻代特点是区域相对老年代较小,对象存活率低,生命周期短。
这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因而很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。
老年代的特点是区域较大,对象存活率高,生命周期较长。
这种情况,存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记清除或者是标记清除与标记整理的混合实现。
8.标记对象三色标记法是什么?
三色标记法基于对象的可达性分析,将对象标记为三种颜色之一:白色、灰色、黑色,以此来表示垃圾回收过程中的不同状态。
-
白色:没有进行可达性分析之前都时白色
-
灰色:在进行可达性分析时,被引用的对象会标记为灰色
-
黑色:在进行可达性分析时,若灰色节点下的子节点全变为灰色时,则此灰色节点就会变为黑色
9.怎么排查堆OOM问题?
①错误名称
java.lang.OutOfMemoryError,也往往简称为 OOM。 这个错误指的是:“内存溢出”,并不是专门指“堆溢出”这么一个情况。
②错误信息
-
Java heap space:针对整个堆进行Full GC后,内存空间还是放不下新产生的对象,且无法申请更多的空间
-
PermGen space:永久代溢出。方法区中加载的类太多了(典型情况是框架创建的动态类太多,导致方法区溢出)
-
Metaspace:元空间溢出。方法区中加载的类太多了(典型情况是框架创建的动态类太多,导致方法区溢出)
-
Direct Memory space:直接内存溢出
可以采取以下步骤:
1、收集错误信息:首先查看应用程序的日志文件,寻找与内存相关的错误信息。通常OOM异常会在日志中有相应的记录,包括堆栈跟踪和错误消息。
2、分析堆转储文件:当应用程序发生OOM异常时,JVM通常会生成一个堆转储文件(Heap Dump),其中包含了应用程序在发生异常时的内存快照。
3、使用内存分析工具:将生成的堆转储文件导入到内存分析工具中,如Eclipse Memory Analyzer(MAT)、YourKit Java Profiler、VisualVM Heap Dump
Analyzer等。这些工具可以帮助分析堆转储文件,查找内存泄漏、大对象、对象引用关系等问题。
4、检查内存使用情况:通过内存分析工具,查看应用程序的内存使用情况,包括堆内存、非堆内存、对象数量、对象大小等指标。特别关注内存占用较高的对象或数据结
构,以及是否存在异常的内存增长趋势。
5、查找内存泄漏:使用内存分析工具,检查堆转储文件中的对象引用关系,查找可能导致内存泄漏的对象。特别关注长时间存活的对象、被遗忘的引用、缓存对象等。
6、优化内存使用:根据分析结果,进行相应的优化措施。例如,减少对象的创建和销毁、优化数据结构、调整缓存策略、增加堆内存大小等。
7、监控和测试:在优化后,持续监控应用程序的内存使用情况,并进行性能测试,以确保问题已经解决或得到改善。
需要注意的是,OOM异常可能有多种原因,如内存泄漏、内存溢出、过度使用第三方库等。因此,在定位OOM异常时,需要综合考虑多个因素,并进行适当的分析和测试。