【备战秋招冲击大厂】Java面试题系列—JVM及垃圾回收机制

  • 运行时常量池:属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。

  • 直接内存:非虚拟机运行时数据区的部分

2. 虚拟机栈结构

3. Java内存模型(JMM)

Java内存模型(简称JMM),是一种规范。JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。线程对变量的操作只能在各自线程的工作内存操作。

JMM的三大特性:

  • 可见性

  • 原子性

  • 有序性

本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。其关系模型图如下图所示:

4. JVM 调优,查看 JVM 参数默认值

  • jps -v 可以查看 jvm 进程显示指定的参数

  • 使用 -XX:+PrintFlagsFinal 可以看到 JVM 所有参数的值

  • jinfo 可以实时查看和调整虚拟机各项参数

  • jstat -gc 12538 5000即会每5秒一次显示进程号为12538的java进程的GC情况

5. OOM排查方法

1)先查看应用进程号pid:ps -ef | grep 应用名

2)查看pid垃圾回收情况: jstat -gc pid 5000(时间间隔)

3)开启OOM快照:

-XX:+HeapDumpOnOutOfMemoryError(开启堆快照)

-XX:HeapDumpPath=C:/m.hprof(保存文件到哪个目录)

4)dump 查看方法栈信息:jstack -l pid > /home/test/jstack.txt

5)dump 查看JVM内存分配以及使用情况:jmap -heap pid > /home/test/jmapHeap.txt

6)dump jvm二进制的内存详细使用情况

6. 类加载

  • 类加载器:

  • 类加载过程:加载,验证,准备,解析,初始化

  • 加载:加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。

  • 验证:确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

  • 准备:准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。

  • 解析:解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。

  • 初始化:初始化阶段是执行类构造器方法的过程。到了初始阶段,才开始真正执行类中定义的Java程序代码。

  • 双亲委派模型

  • 定义:除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器,这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)的关系实现。

  • 工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器,每一个层次的加载器都是如此,依次递归,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成此加载请求(它搜索范围中没有找到所需类)时,子加载器才会尝试自己加载。

  • 优点:使用双亲委派模型来组织类加载器之间的关系,使得Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放再rt.jar中,无论哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。

  • 打破双亲委派机制的方法:重写loadclass()方法

  • 打破双亲委派机制的例子:

  • Tomcat,应用的类加载器优先自行加载应用目录下的 class,并不是先委派给父加载器,加载不了才委派给父加载器。打破的目的是为了完成应用间的类隔离。

  • JDK 9,Extension ClassLoader 被 Platform ClassLoader 取代,当平台及应用程序类加载器收到类加载请求,在委派给父加载器加载前,要先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载。打破的原因,是为了添加模块化的特性。

  • 沙箱安全机制:防止恶意代码污染java源代码。

7. JVM加载class文件的原理

JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件的类。

Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。

类装载方式,有两种

  • 隐式装载,程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中

  • 显式装载,通过class.forname()等方法,显式加载需要的类,隐式加载与显式加载的区别:两者本质是一样的。

Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

8. GC Roots

作为GCRoots的对象包括下面几种:

1)虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。

2)方法区中的类静态属性引用的对象。

3)方法区中常量引用的对象。

4)本地方法栈中JNI(Native方法)引用的对象。

9. 对象可达性分析

  • 如果对象在进行可达性分析后发现没有与GCRoots相连的引用链,则该对象被第一次标记并进行一次筛选,筛选条件为是否有必要执行该对象的finalize方法,若对象没有覆盖finalize方法或者该finalize方法是否已经被虚拟机执行过了,则均视作不必要执行该对象的finalize方法,即该对象将会被回收。反之,若对象覆盖了finalize方法并且该finalize方法并没有被执行过,那么,这个对象会被放置在一个叫F-Queue的队列中,之后会由虚拟机自动建立的、优先级低的Finalizer线程去执行,而虚拟机不必要等待该线程执行结束,即虚拟机只负责建立线程,其他的事情交给此线程去处理。

  • 对F-Queue中对象进行第二次标记,如果对象在finalize方法中拯救了自己,即关联上了GCRoots引用链,如把this关键字赋值给其他变量,那么在第二次标记的时候该对象将从“即将回收”的集合中移除,如果对象还是没有
    拯救自己,那就会被回收。

10. 垃圾回收机制

堆分为新生代和老年代,新生代默认占总空间的 1/3,老年代默认占 2/3。新生代使用复制算法,有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1。

当新生代中的 Eden 区内存不足时,就会触发 Minor GC,过程如下:

1)在 Eden 区执行了第一次 GC 之后,存活的对象会被移动到其中一个 Survivor 分区;

2)Eden 区再次 GC,这时会采用复制算法,将 Eden 和 from 区一起清理,存活的对象会被复制到 to 区;

3)移动一次,对象年龄加 1,对象年龄大于一定阀值会直接移动到老年代

4)Survivor 区相同年龄所有对象大小的总和 > (Survivor 区内存大小 * 目标使用率)时,大于或等于该年龄的对象直接进入老年代。其中这个使用率通过 -XX:TargetSurvivorRatio 指定,默认为 50%

5)Survivor 区内存不足会发生担保分配

6)年龄超过指定大小的对象可以直接进入老年代

7)Major GC,指的是老年代的垃圾清理,但并未找到明确说明何时在进行Major GC

8)FullGC,整个堆的垃圾收集,触发条件:

  • 每次晋升到老年代的对象平均大小>老年代剩余空间

  • MinorGC后存活的对象超过了老年代剩余空间

  • 元空间不足

  • System.gc()

  • CMS GC异常,promotion failed:MinorGC时,survivor空间放不下,对象只能放入老年代,而老年代也放不下造成;concurrent mode failure:GC时,同时有对象要放入老年代,而老年代空间不足造成

  • 堆内存分配很大的对象

11. 垃圾回收算法

  • 引用计数法:给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。

  • 缺点:(1)每次给对象赋值时都要维护引用计数器,且计数器本身也有一定的消耗;(2)较难处理循环引用

  • 复制算法:(年轻代)

  • 优点:(1)不产生内存碎片;(2)速度快

  • 缺点:浪费10%的内存空间

  • 标记清除算法(老年代):先标记出要回收的对象,然后统一回收这些对象

  • 优点:节省空间

  • 缺点:产生内存碎片;

  • 标记整理算法(老年代):先标记清除,再次扫描并往一端滑动存活对象。

  • 优点:不产生内存碎片;

  • 缺点:需要移动对象的成本,耗时严重

12. 垃圾收集器

1)Serial收集器:新生代收集器,使用停止复制算法,使用一个线程进行GC,其它工作线程暂停。使用-XX:+UseSerialGC可以使用Serial+Serial Old模式运行进行内存回收(这也是虚拟机在Client模式下运行的默认值)

2)Serial Old收集器:老年代收集器,单线程收集器,使用标记整理(整理的方法是Sweep(清理)和Compact(压缩),清理是将废弃的对象干掉,只留幸存的对象,压缩是将移动对象,将空间填满保证内存分为2块,一块全是对象,一块空闲)算法,使用单线程进行GC,其它工作线程暂停(注意,在老年代中进行标 记整理算法清理,也需要暂停其它线程),在JDK1.5之前,Serial Old收集器与ParallelScavenge搭配使用。

3)ParNew收集器:新生代收集器,使用停止复制算法,Serial收集器的多线程版,用多个线程进行GC,其它工作线程暂停,关注缩短垃圾收集时间。使用-XX:+UseParNewGC开关来控制使用ParNew+Serial Old收集器组合收集内存;使用-XX:ParallelGCThreads来设置执行内存回收的线程数。

4)Parallel Scavenge 收集器:新生代收集器,使用停止复制算法,关注CPU吞吐量,即运行用户代码的时间/总时间,比如:JVM运行100分钟,其中运行用户代码99分钟,垃 圾收集1分钟,则吞吐量是99%,这种收集器能最高效率的利用CPU,适合运行后台运算(关注缩短垃圾收集时间的收集器,如CMS,等待时间很少,所以适 合用户交互,提高用户体验)。使用-XX:+UseParallelGC开关控制使用 Parallel Scavenge+Serial Old收集器组合回收垃圾(这也是在Server模式下的默认值);使用-XX:GCTimeRatio来设置用户执行时间占总时间的比例,默认99,即 1%的时间用来进行垃圾回收。使用-XX:MaxGCPauseMillis设置GC的最大停顿时间(这个参数只对Parallel Scavenge有效)

5)Parallel Old收集器:老年代收集器,多线程,多线程机制与Parallel Scavenge差不错,使用标记整理(与Serial Old不同,这里的整理是Summary(汇总)和Compact(压缩),汇总的意思就是将幸存的对象复制到预先准备好的区域,而不是像Sweep(清 理)那样清理废弃的对象)算法,在Parallel Old执行时,仍然需要暂停其它线程。Parallel Old在多核计算中很有用。Parallel Old出现后(JDK 1.6),与Parallel Scavenge配合有很好的效果,充分体现Parallel Scavenge收集器吞吐量优先的效果。使用-XX:+UseParallelOldGC开关控制使用Parallel Scavenge +Parallel Old组合收集器进行收集。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值