https://blog.csdn.net/yanpenglei/article/details/119406377 参考
https://www.cnblogs.com/dolphin0520/p/3613043.html JVM的内存区域划分 写的很好
1. JVM 默认使用的java 虚拟机是HotSpot。
2. Java的双亲委托机制是什么?
它的意思是,除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都会委派给它的父加载器进行加载。这样一层层向上传递,直到祖先们都无法胜任,它才会真正的加载。
Java默认是这种行为。当然Java中也有很多打破双亲行为的骚操作,比如SPI(JDBC驱动加载),OSGI等。
优点:
避免类的重复加载, 确保一个类的全局唯一性
Java 类随着它的类加载器一起具备了一种带有优先级的层级关系, 通过这种
层级关系可以避免类的重复加载, 当父亲已经加载了该类时, 就没有必要子
ClassLoader 再加载一次
2.JVM使用“类”的生命周期是:
2.连接 该阶段又包含了验证、准备和解析3个过程,如下。
(1)验证
校验.class文件的正确性。
(2)准备
2.1给static静态变量分配内存,并初始化static的默认值。
3、GC Roots 有哪些?
1、 GC Roots 是一组必须活跃的引用。用通俗的话来说,就是程序接下来通过直接引用或者间接引用,能够访问到的潜在被使用的对象。
2、 GC Roots 包括:Java 线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值等。也就是与我们栈帧相关的各种引用。所有当前被加载的 Java 类。Java 类的引用类型静态变量。运行时常量池里的引用类型常量(String 或 Class 类型)。JVM 内部数据结构的一些引用,比如 sun.jvm.hotspot.memory.Universe 类。用于同步的监控对象,比如调用了对象的 wait() 方法。JNI handles,包括 global handles 和 local handles。
3、 这些 GC Roots 大体可以分为三大类,下面这种说法更加好记一些:活动线程相关的各种引用。类的静态变量的引用。JNI 引用。
4、 有两个注意点:我们这里说的是活跃的引用,而不是对象,对象是不能作为 GC Roots 的。GC 过程是找出所有活对象,并把其余空间认定为“无用”;而不是找出所有死掉的对象,并回收它们占用的空间。所以,哪怕 JVM 的堆非常的大,基于 tracing 的 GC 方式,回收速度也会非常快。
4.jvm怎么判断哪些对象应该回收
引用计数算法和可达性分析算法。
因为引用计数法的算法是这样的:在对象中添加一个引用计数器,每当一个地方引用它时,计数器就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
但是这样的算法有个问题,是什么呢?
不经意间来一波自问自答。让面试官听的一愣一愣的。
就是不能解决循环依赖的问题。
并拿着自己准备的纸和笔快速的画出下面这样的图:
Object 1和Object 2其实都可以被回收,但是它们之间还有相互引用,所以它们各自的计数器为1,则还是不会被回收。
所以,Java虚拟机没有采用引用计数法。它采用的是可达性分析算法。
可达性分析算法的思路就是通过一系列的“GC Roots”,也就是根对象作为起始节点集合,从根节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为引用链,如果某个对象到GC Roots间没有任何引用链相连。
用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。所以此对象就是可以被回收的对象。
2。垃圾收集算法进行垃圾收集
标记-清除算法
分为标记和清除阶段,首先从每个 GC Roots 出发依次标记有引用关系的对象,最后清除没有标记的对象。
执行效率不稳定,如果堆包含大量对象且大部分需要回收,必须进行大量标记清除,导致效率随对象数量增长而降低。
存在内存空间碎片化问题,会产生大量不连续的内存碎片,导致以后需要分配大对象时容易触发 Full GC。
标记-复制算法
为了解决内存碎片问题,将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当使用的这块空间用完了,就将存活对象复制到另一块,再把已使用过的内存空间一次清理掉。主要用于进行新生代。
实现简单、运行高效,解决了内存碎片问题。代价是可用内存缩小为原来的一半,浪费空间。
HotSpot 把新生代划分为一块较大的 Eden 和两块较小的 Survivor(s0,s1),每次分配内存只使用 Eden 和其中一块 Survivor(s0和s1 永远只有一个有值)。垃圾收集时将 Eden 和 Survivor 中仍然存活的对象一次性复制到另一块 Survivor 上,然后直接清理掉 Eden 和已用过的那块 Survivor。HotSpot 默认Eden 和 Survivor 的大小比例是 8:1,即每次新生代中可用空间为整个新生代的 90%。
标记-整理算法
标记-复制算法在对象存活率高时要进行较多复制操作,效率低。如果不想浪费空间,就需要有额外空间分配担保,应对被使用内存中所有对象都存活的极端情况,所以老年代一般不使用此算法。
老年代使用标记-整理算法,标记过程与标记-清除算法一样,但不直接清理可回收对象,而是让所有存活对象都向内存空间一端移动,然后清理掉边界以外的内存。
标记-清除与标记-整理的差异在于前者是一种非移动式算法而后者是移动式的。如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活的区域,是一种极为负重的操作,而且移动必须全程暂停用户线程。如果不移动对象就会导致空间碎片问题,只能依赖更复杂的内存分配器和访问器解决。
5. GC 的回收流程是怎么样的。
6、说说Java 垃圾回收机制
在 Java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在 JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
7、分代收集算法
当前主流 VM 垃圾收集都采用”分代收集” (Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为几块, 如 JVM 中的新生代、老年代、永久代, 这样就可以根据各年代特点分别采用最适当的 GC 算法。 不同分区,采用不同的GC算法。 具体算法看6.
7、JVM 内存区域
JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA 堆、方法区】、直接内存。
线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 Hotspot VM 内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的生/死对应)。
线程共享区域随虚拟机的启动/关闭而创建/销毁。
直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提供了基于Channel与 Buffer的IO方式, 它可以使用Native函数库直接分配堆外内存, 然后使用DirectByteBuffer 对象作为这块内存的引用进行操作(详见: Java I/O 扩展), 这样就避免了在 Java堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。
8、MinorGC,MajorGC、FullGC都什么时候发生?
MinorGC在年轻代空间不足的时候发生,MajorGC指的是老年代的GC,出现MajorGC一般经常伴有MinorGC。
FullGC有三种情况。
1、 当老年代无法再分配内存的时候
2、 元空间不足的时候
3、 显示调用System.gc的时候。另外,像CMS一类的垃圾回收器,在MinorGC出现promotion failure的时候也会发生FullGC
9、垃圾收集器对比
1.Serial GC Serial 即串行的意思,也就是说它以串行的方式执行,它是单线程的收集器,只会使用一个线程进行垃圾收集工作,GC 线程工作时,其它所有线程都将停止工作会产生 stop the world 。
stop the world 停顿时间会相对比较长。
3.CMS 收集器(只适用老年代)
CMS(Concurrent Mark Sweep),收集器几乎占据着 JVM 老年代收集器的半壁江山,它划时代的意义就在于垃圾回收线程几乎能做到与用户线程同时工作。
使用标记-清除算法收集老年代垃圾。
工作流程主要有如下 4 个步骤:
初始标记: 仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿(Stop-the-world)
并发标记: 进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿
重新标记: 为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿(Stop-the-world)
并发清除: 清理垃圾,不需要停顿
在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。只有在初始标记,重新标记的时候会进行短暂的停顿。
但 CMS 收集器也有如下缺点:
吞吐量低
无法处理浮动垃圾
标记 - 清除算法带来的内存空间碎片问题
显式的使用该垃圾收集器作为老年代垃圾收集器的方式:-XX:+UseConcMarkSweepGC
4.G1垃圾收集器(适用新老年代)
https://blog.csdn.net/iva_brother/article/details/87886525
把G1单独拿出来的原因是其比较复杂,在JDK 1.7确立是项目目标,在JDK 7u2版本之后发布,并在JDK 9中成为了默认的垃圾回收器。通过“-XX:+UseG1GC”启动参数即可指定使用G1 GC。
G1从整体看还是基于标记-清除算法的,但是局部上是基于复制算法的。这样就意味者它空间整合做的比较好,因为不会产生空间碎片。G1还是并发与并行的,它能够充分利用多CPU、多核的硬件环境来缩短“stop the world”的时间。G1还是分代收集的,但是G1不再像上文所述的垃圾收集器,需要分代配合不同的垃圾收集器,因为G1中的垃圾收集区域是“分区”(Region)的,不同的Region配置不同的垃圾回收算法。G1的分代收集和以上垃圾收集器不同的就是除了有年轻代的ygc,全堆扫描的full GC外,还有包含所有年轻代以及部分老年代Region的Mixed GC。G1还可预测停顿,通过调整参数,制定垃圾收集的最大停顿时间。
1分区的概念
G1的堆区在分代的基础上,引入分区的概念。G1将堆分成了若干Region,以下和”分区”代表同一概念。(这些分区不要求是连续的内存空间)Region的大小可以通过G1HeapRegionSize参数进行设置,其必须是2的幂,范围允许为1Mb到32Mb。 JVM的会基于堆内存的初始值和最大值的平均数计算分区的尺寸,平均的堆尺寸会分出约2000个Region。分区大小一旦设置,则启动之后不会再变化。如下图简单画了下G1分区模型。
内存溢出(out of memory),是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露(memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
(2)Young GC会有stw(stop‐the‐world 程序停止