- JVM垃圾回收中的并行和并发的概念与操作系统中的并行和并发有什么区别?
- JVM垃圾回收中的并行指的是多条垃圾收集线程并行工作,用户线程处于等待状态;并发指的是用户线程与垃圾收集线程同时执行,此时不一定是并行的,也可能是交替执行
- 操作系统中的并行只会发生在多CPU或者是单CPU多核的情况下。并发指的多个操作,在同一时间段内发生了;并行指的是多个操作,在同一时间点上发生了。并发执行的多个操作之间是互相抢占资源的;并行执行的多个操作之间是不互相抢占资源的。
- 说说CMS垃圾回收算法的四个阶段?
- CMS的含义是什么?Concurrent Mark Sweep,并发标记清除。
- CMS开启了垃圾回收线程和工作线程同时工作的新思路,具有里程碑的意义,但是CMS算法本身是有很多的问题的,所以没有一个版本的JDK把CMS作为默认的垃圾回收算法。
- 初始标记:标记最根上的对象,其他对象不标记,会有短暂的STW
- 并发标记:垃圾线程与用户线程同时执行,用户可能会感觉响应变慢,但是还是有反应的,没有完全卡死
- 重新标记:会有STW,时间也不长
- 并发清除:并发清理过程中会产生浮动垃圾,这些浮动垃圾只能等下次CMS运行的时候给清理掉
- CMS垃圾回收算法的问题是什么?
- 并发就是垃圾回收线程跟用户线程同时执行,所以垃圾回收线程会抢占用户线程的资源,拉低用户线程的执行效率,如果CPU核数多的话还好,CPU核数少的话,问题就会明显。为此,CMS算法提供了 incremental mode增量模式,让GC线程与用户线程交替运行,尽量减少GC线程独占CPU资源的时间,以图解决这个问题,但是在实践中的效果并不佳,此方案已经被废弃。为什么效果不佳呢?我觉得这个方案并没有从本质上改变GC线程与用户线程抢占共有资源这个事实。
- 浮动垃圾:GC线程与用户线程并发执行,因此在垃圾回收的过程中,用户线程还会同时产生一些垃圾,这些垃圾只能等下次GC的时候才能回收,被称之为浮动垃圾。
- CMSInitiatingOccupancyFraction参数的设置,需要看具体情况,设置得太高和太低都不行。CMS设计了动态检查机制,根据历史记录,预测老年代还需要多久填满及进行一次回收所需要的时间,以便自动执行垃圾回收。
- 内存碎片:由标记-清除算法产生的问题,内存碎片过多,给大对象的分配带来不便。可能出现的问题是老年代明明有很多内存碎片,但是却分配不下大对象,被迫执行FGC。为了解决这个问题,CMS使用了UseCMSCompactAtFullCollection,对碎片空间进行压缩,但是这个过程需要STW,导致停顿时间过长。
- 总结:没有银弹,需要权衡、选择和取舍,根据需求来选择合适的。
- 各种java版本默认的垃圾回收器是什么?
- java8 : PS + PO, Parallel Scavenge + Parallel Old
- java9 :G1
- java11:java11中加入了实验性的ZGC
- G1垃圾回收算法有什么优点?
- Garbage First。
- 与CMS相比,G1在整体上是基于标记整理算法,但是局部是基于复制的算法。
- 可以实现可预测的停顿。如何实现可预测的停顿?G1每次并不会回收整代内存,到底回收多少内存取决于用户配置的暂停时间。
- 分而治之、化整为零。
- java中类加载器有哪几种?为什么要设计这么多种?
- 根类加载器,Bootstrap Class Loader,用来加载jdk中最核心的部分,如rt.jar、charset.jar
- 扩展类加载器, Extension Class Loader,用来加载扩展包里面的各种文件,在jdk安装目录jre/lib/ext下的jar包
- 应用类加载器,Application Class Loader,用于加载classpath指定的内容
- 自定义类加载器,用来自己定制化类加载器
- 为什么要设计这么多种类加载器?为了安全。
- 观察ByteCode的方法有哪些?
- javap,java自带的命令
- JBE,除了可以看,还可以修改
- JClassLib,IDEA插件
- class文件主要包含哪些内容?
- Magic Number 魔法数字
- Minor Version 小版本
- Major Version 大版本
- constant_pool_count 常量池的个数
- constant_pool 常量池的具体类型
- access_flags 类的访问权限标识,如public、private这种
- this_class 当前这个类是谁
- super_class 当前类的父类是谁
- interface_count 实现了哪些接口
- interfaces 接口的索引
- fields 有哪些属性, access_flags、name_index、descriptor_index、attributes
- methods 方法的各种结构
- attributes 附加属性
- JVM有哪些基本的原子性的指令?
- lock 锁定
- read 读取
- load 载入
- use 使用
- assign 赋值
- store存储
- write 写入
- unlock 解锁
- class文件从硬盘是如何进入到内存并做好运行的准备的?
- 加载loading
- 链接linking,链接又分为验证verification、准备preparation和解析resolution。验证:校验加载进来的class文件是否符合class文件的标准;准备:把class文件静态变量赋默认值,注意这里不是赋初始值;解析:把class文件里面用到的符号引用,转换成直接内存地址。
- 初始化 initialzing ,给静态变量赋初始值
- 双亲委派机制是什么?为什么要采取这种机制来实现类的加载?
- 双亲委派指的是一个孩子向父亲方向和父亲向孩子方向双向委派机制。当一个类要被加载到内存中时,会先去缓存中找有没有已经加载过这个,如果有直接用;如果没有,向上逐级寻找有没有加载,这是向上委派过程。到根加载器后,向下逐步看能否加载所需要加载的类,这是向下委派的过程。
- 父加载器不是类加载器的加载器,也不是类加载器的父类加载器
- 为什么要用双亲委派机制?为了安全
- 双亲委派机制是可以被破坏的
- 如何自定义类加载器?
- 继承ClassLoader
- 重写模板方法findClass,调用defineClass方法,模板方法设计模式
- 自定义类加载器加载自加密的class,防止反编译,防止篡改
- 自定义类加载器的应用场景:tomcat中加载自定义的那些类,jRebel热部署,spring动态代理
- java是解释型语言还是编译型语言?
- 既可以解释执行,也可以编译执行
- 默认的情况下,是混合模式,混合使用解释器 + 热点代码编译
- 如何打破双亲委托机制?
- 重写loadClass方法
- 何时打破过?JDK1.2以前,自定义ClassLoader都必须重写loadClass;ThreadContextClassLoader的setContextClassLoader;热启动,热部署。
- 双重检测锁单例设计模式为什么要加volatile关键字?
- 对象实例化到一半的时候,就已经有初始值了,不为空了,此时如果有另一个线程来了,就会取到不正确的对象。加volatile关键字可以使变量对所有线程可见。
- volatile关键字两个作用:保证变量的可见性,防止指令重排
- JVM级别内存屏障有哪些?
- LoadLoad屏障
- StoreLoad屏障
- StoreStore屏障
- LoadStore屏障
- synchronized关键字语义在计算机内部是怎么实现的?
- 字节码层面:monitorenter和monitorexit
- JVM层面:调用操作系统的同步机制
- 操作系统和硬件层面:x86 lock cmpxchg x
- happens-before原则有哪些?
- 程序次序规则
- 管程锁定规则
- volatile变量规则
- 线程启动规则
- 线程终止规则
- 线程中断规则
- 对象终结规则
- 传递性
- java中对象的创建过程是怎么样的?
- class 文件加载
- class文件链接,包括验证、准备和解析阶段
- class类初始化
- 申请对象内存
- 成员变量赋默认值
- 调用构造方法,成员变量赋初始值,执行构造方法
- 对象在内存中是怎么布局的呢?
- 就布局来讲,对象分为两种:普通对象和数组对象
- 普通对象有对象头,hotspot中为markword,长度8个字节;有ClassPointer指针,开启了-XX:+UseCompressedClassPointers时为4个字节,不开启的时候是8个字节;有实例数据,对于引用类型而言,开启了-XX:+UseCompressedOops时为4个字节,不开启的时候是8个字节;有Padding对齐,为8的倍数。
- 数组对象在普通对象的基础上,还有一个是数组长度占4个字节
- 内存是不是越大越好?
- 不是,拿64位hotspot虚拟机为例,会有开启内存压缩的机制
- 4G以下,直接砍掉高32位
- 4G - 32G,默认开启内存压缩ClassPointers Oops
- 32G以上,压缩无效,使用64位
- 对象头包含哪些内容?
- 以64位hotspot机为例,根据对象的不同的锁状态,存储的内容不一样。无锁态有对象的HashCode值、分代年龄、是否偏向锁标识和锁标识位;轻量级锁有指向栈中锁记录的指针和锁标识位;重量级锁有指向互斥量的指针和锁标识位;处在GC标记时,就只有一个锁状态;偏向锁时,有线程ID、Epoch、分代年龄、是否偏向锁标识和锁标识位。
- 为什么GC年龄默认为15?
- 因为对象头上用来表示对象分代年龄的容量只有4bit,能够表示到最大的数为15。
- 对象定位有哪些方式?
- 句柄池和直接指针
- 句柄池GC效率高,直接指针找对象效率高。
- 对象的分配流程是怎么样的?
- new出一个对象,首先试图往栈上放,放得下就放,栈一弹出对象就没了;栈上放不下,如果是大对象,直接放堆内存老年代;如果不大,试图线程本地分配,还是放得下就放;如果线程本地放不下,放伊甸区。
- GC的时候伊甸区的对象到了年龄,会进入老年代。
- JVM运行时数据区有哪些区域?
- 栈、程序计数器、方法区、本地方法栈、直接内存、堆。
- 方法区中包含常量池
- 程序计数器PC:线程私有,记录当前线程执行到哪一步指令,下一步指令是什么
- 栈:线程私有,里面放的是栈帧。当方法启动后,一个方法对应一个栈帧。每个栈帧里面包含局部变量表、operand stacks 操作数栈、Dynamic Linking动态链接和return address返回地址。
- 堆:各线程共享,里面放new出来的对象
- 方法区:各线程共享,放class结构的元数据
- 本地方法栈:线程私有,本地方法指的是用C++写的方法
- 直接内存:用户空间直接去访问内核空间的一块内存
- JVM中如何确定一个对象是垃圾?
- 根可达算法,根对象可达的对象不是垃圾。
- 什么叫垃圾?没有引用指向的对象叫做垃圾。
- 为什么采用根可达算法来确定垃圾?引用计数法有个缺陷,不能解决循环依赖的问题。
- 什么样的对象可以被认为是根对象?JVM栈里面的对象、本地方法栈里面的对象、运行时常量池里面的对象、方法区里面的静态引用和Clazz等一个程序马上启动的时候需要的对象叫做根对象。
- 常见的垃圾回收算法有哪些?
- 标记清除 Mark Sweep,两遍扫描,执行效率偏低,容易产生碎片,适用于存活对象较多的场景。
- 拷贝 Copying,某内存区域分成两等分,每次有一份为新,将旧的存活对象拷贝到新的一份,然后旧的一份清除。执行效率高,没有碎片,但是耗空间,移动复制对象,需要调整对象引用,只扫描一次,适用于存活对象较少的情况。
- Mark Compact 标记压缩,扫描两次,移动对象,效率低,不会产生碎片,不会出现拷贝算法内存空间折半的问题。
- 垃圾回收器的分代模型
- 最新的垃圾回收器,如ZGC、Epsilon等不分代
- G1逻辑分代,物理不分代
- 其他更早的垃圾收集器不仅是逻辑分代的,也是物理上分代的
- 分代模型分为年轻代和老年代。
- 年轻代包括eden区、两个相等大小的survivor区
- 老年代包括tenured终身区
- minorGC、majorGC和fullGC之间有什么区别?
- minorGC指的是年轻代的GC
- majorGC指的是老年代的GC
- fullGC指的是年轻代和老年代都进行GC
- 栈上分配和线程本地分配的区别是什么?
- 常见的垃圾回收器有哪些?
- Serial 停顿时间长,现在很少用
- Serial Old
- Parallel Scavenge
- Parallel Old
- ParNew
- CMS
- G1
- ZGC
- Shenandoah
- G1算法有什么特征?
- 把内存分成小块小块的,从1M2M到32M。Old区放老对象;Suivivor区放存活的;Eden区放新生对象的;Humongous区放巨型对象。
- 并发收集,与CMS一样
- 压缩空闲空间不会延长GC的暂停时间
- 更加容易预测的GC暂停时间
- 适用于不需要很高吞吐量的场景
- 内存区域不是固定地是E或者是O,比较灵活
- CSet:Collection Set ,有哪些对象需要被回收,会记录在这个表格里面
- RSet:Remembered Set,记录了其他Region中的对象到本Region的引用,使得垃圾收集器不需要扫描整个堆就能找到谁引用了当前分区中的对象,只需要扫描RSet即可。
- CardTable:如果在O区中有一个CardTable指向了Y区,就设置为Dirty。扫描时,只需要扫描Dirty Card。Card Table用bitmap实现。
- 三色标记,与颜色指针对比一下。ZGC用的是颜色指针算法。
- GC什么时候触发?
- Eden空间不足
- Old空间不足
- System.gc()
- G1如果发生FGC,应该怎么办?
- 扩内存
- 提高CPU性能
- 降低MixedGC触发的阈值,让MixedGC提早发生
- 三色标记算法有什么问题?为什么会产生这个问题?可以怎样解决?
- 三色标记:黑、灰、白
- 问题点:漏标
- 原因:并发标记的同时,对象的引用关系正在同时发生变化
- 解决方案:CMS用的是incremental update增量更新;G1用的是snapshot at the beginning起始快照。增量更新效率低。SATB配合RSet效率高。
- 系统CPU经常100%,如何调优?
- top命令找出哪个进程CPU占比高
- 用top -Hp命令找出该进程中哪个线程CPU占比高
- jstack导出该线程中的堆栈信息
- 查找哪个方法消耗时间多
- 查看是工作线程占比高还是垃圾回收线程占比高
- 系统内存飙高,如何查找问题?
- 用jmap命令导出堆内存
- 用jstat、jvisualvm、mat、jprofiler、jhat等命令进行分析
- 阿里有个好用的工具arthas
- 有过哪些JVM调优的实践经验?
- ThreadLocal内存泄露问题
- Prims模块包含哪些子模块?
- JNI模块:java本地接口,允许JDK或者是外部程序调用由C或者是C++实现的库函数。
- JVM模块:以“JVM_”为前缀命名,是JNI的补充。JVM模块的函数包含三个部分:用来支持一些需要访问本地库的Java API;一些函数和常量定义,用来支持字节码验证和Class文件格式校验;一些用来支持IO和网络操作,用来支持Java IO和网络API。
- JVMTI模块: java虚拟机工具接口,允许程序员创建代理以监视和控制java应用程序。JVMTI代理常用于对应用程序进行监控、调试或者是调优。
- Perf模块:由外部程序调用,以监控虚拟机内部的Perf Data计数器。
- HotSpot的顶层模块包含哪些模块?
- Service、Prims、Runtime、Classfile、Interpreter、Code、Memory、Compiler、Oops、C1/Opto/Shark和GC。
- Services模块的作用是什么?
- 为JVM提供了JMX等功能。
- Servies模块包含9个主要子模块:Management、Memory Service、Thread Service、Runtime Service、Memory Manager、HeapDumper、ClassLoadingService、MemoryPool、AttachListener。