目录
一、说一说JVM的内存模型
二、JAVA类加载的全过程是怎么样的?什么是双亲委派机制?有什么作用?
1、双亲委派机制
Java的类加载器有以下几种:AppClassLoader、ExtClassLoader、BootStrap ClassLoader。
类加载器的继承关系:AppClassLoader,ExtClasLoader -》URLClassLoader -》SecureClassLoader -》ClassLoader
以上三种类加载器,每一种类加载都有对应的加载目录;对加载过的类,都是有一个缓存的
双亲委派:类加载的时候,向上委托类的查找,向下委托类的加载;作用:保护JAVA的层的类不会被应用程序覆盖
2、类加载的全过程
1、加载:将Java的字节码数据加载到JVM内存中,并映射成JVM认可的数据结构
2、连接:分为三步:
验证:检查加载到的字节信息是否符合JVM的规范
准备:在JVM堆中创建类或者接口的静态变量,并赋默认值
解析:将JVM栈中的引用变量指向JVM堆中的数据,变成直接引用
3、进行类的初始化
三、一个对象从加载到JVM,再被GC清除,都经历了什么过程?
1、用户创建一个对象的时候,先要根据类的ClassPointer去方法区找到对应对象的类型,然后再创建对象
2、JVM要实例化一个对象,首先要在堆中先创建一个对象,栈中的引用变量指向该对象
3、对象首先会分配到堆中的Eden中,在执行MinorGC()方法的时候,如果对象存活则被移入Survivor区;如果对象一直存活则在Survivor的两个区来回移动,并且年龄+1;
4、对象的markword有4bits记录了对象的年龄;如果年龄超过15,则对象被移入到老年代
5、方法结束后,栈中的引用变量将被移除
6、堆中的对象,经过FullGC(),就会标记为垃圾,然后被GC线程清除
四、如何确定一个对象是不是垃圾?什么是GC Root?
有两种定位垃圾的方式:
1、引用计数:给堆内存中的每个对象记录一个引用个数,为0证明是垃圾。这是早期JDK使用的方式,缺陷:没法解决循环引用的问题,该回收的内存没办法回收,从而导致内存泄露的问题
2、根可达算法:从GC Root开始一直向下查找引用,找不到的就是垃圾
哪些是GC Root?
栈中:JVM栈,原生Native栈,class类,runtime constant pool,static reference静态变量
五、JVM有哪些垃圾回收算法?
MarkSweep 标记清除:
将垃圾内存标记出来,然后进行内存的回收;
这种算法简单,但是有一个缺点,就是会产生大量的内存碎片
Copying 拷贝算法:
将内存分为两半,每次只使用其中一半;将一半内存中所有存活对象复制到另一半,从而自己这一半的内存就可以直接被全部回收了。
这种算法能避免内存碎片,但是内存利用率低,而且算法运行效率与存活对象的数量有关;需要被复制的存活对象越多时,算法的运行效率越低
MarkCompact标记压缩算法:
首先将垃圾对象进行标记,然后将所有存活对象移动到内存的一端,从而可将边界以外未使用的其他内存全部进行回收和清除。
这种算法看起来效果最好,但是算法复杂度也比较高
三种算法各有各的优缺点和适用场景
六、JVM有哪些垃圾回收器?他们都是怎么工作的?什么是STW?他们都发生在哪些阶段?什么是三色标记?如何解决错标记和漏标记的问题?为什么需要设计这么多的垃圾回收器?
1、STW: Stop The World
将JVM内存进行冻结,在冻结的状态下,Java的所有线程(GC线程除外)都是停止的。Native方法可以执行,但他们不能与JVM进行交互。GC各种算法优化的重点,就是减少STW;这也是我们进行JVM调优的重点。
2、JVM的垃圾回收器
3、分代算法的分类:
Serial串行:
线程并行执行时,需要垃圾回收时启动STW,回收完之后再继续执行之前的线程。
只有一个线程执行GC,这种方法在多CPU架构下性能下降很严重
Parallel并行:
在串行的基础上,使用的STW多线程执行GC的机制;PS+PO这种组合是JDK1.8默认的垃圾回收器。在多CPU的架构下,性能比Serial高很多。
CMS: Concurrent Mark Sweep并发标记清除
核心思想:将STW打散,允许一部分GC线程与用户线程并发执行。整个GC过程分为四个阶段
初始标记:STW只标记出GC Root直接引用的对象
并发标记:允许部分GC线程在程序执行的过程中标记其他对象
重新标记:STW在上一个阶段程序并发执行的过程中的对象进行重新标记。
并发清除:并行;将产生的垃圾清除。并发清除的过程中,应用程序又会不断产生新的垃圾,这些垃圾被称为浮动垃圾。这些垃圾要留到下一次GC过程中清除
G1: Garbage First垃圾优先
他的内存模型是实际上不分代,但是逻辑上市分代的。在内存模型中,堆内存不再被划分为老年代和新生代,而是被分为一个个的Region,每一个Region可以属于不同的年代。
G1可以分为四个阶段:
第一:初始标记 标记出GC Root直接引用的对象
第二:标记Region 通过RSet标记出上一个阶段标记的Region引用到的Old区的Region
第三:并发标记阶段 跟CMS差不多 只不过只扫描第二步被标记出来的Old区的Region
第四:重新标记 和CMS重新标记类似
第五:垃圾清理 与CMS不同,G1采用的拷贝算法;直接将整个Region的对象拷贝到另一个Region中,清除原来的Region;并且G1只选择垃圾较多的Region进行清理,并不会完全清理所有Region中的垃圾空间。
4、三色标记
CMS的核心算法就是三色标记
三色标记是一种逻辑上的抽象
黑色:表明对象本身和成员变量都已经被标记
灰色:表明只有对象本身被标记,成员变量没有被标记
白色:表明对象和成员变量都没有被标记
漏标记:
原来的对象引用关系如下:
经过CMS的并发标记阶段之后,引用关系可能发生改变:
这时候,A为黑色,不会去寻找C;B为灰色,但是没有成员变量了。那么对象C则为根不可达,会被GC回收。程序中A的引用关系就乱套了。
解决办法:CMS通过增量标记increment update来解决漏标的问题
七、如何进行JVM的调优?JVM的参数有哪些?如果一个Java程序,每运行一段时间后就会变得非常卡顿,你如何解决?
通过定制JVM运行参数来提高Java的运行速度
JVM参数大致分为3类:
标准指令:-开头,所有HotSpot都支持的参数。可用java -help打印
非标准指令:-X开头,和不同的Hotspot版本对应,用java -X打印
不稳定参数:-XX开头,和不同的Hotspot版本对应,而且变化非常大,详细的文档资料少。
例如:java -XX:PrintCommandLineFlags:打印当前命令的不稳定参数
java -XX: PrintFlagsInitial: 打印当前命令不稳定参数的初始化值
java -XX: PrintFlagsFinal: 打印当前命令不稳定参数最终生效的实际值
可以使用java -jar arthas-boot.jar查看当前线程的内存执行情况。通过help命令可以查看其他指令;比如:thread -b查看当前进程中是否存在死锁的线程。