ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

JVM 基础

JVM 基础 内存模型JVM 内存模型是什么1JVM 内存模型共分为5个区Java虚拟机栈、本地方法栈、堆、程序计数器、方法区元空间2各个区各自的作用a.程序计数器它是一块很小的内存空间主要用来记录当前线程执行的字节码的地址也就是记录当前线程执行的位置。 是虚拟机中没有规定会OutofMemoryError的区域。b.Java 虚拟机栈每个方法执行时会在栈中创建一个栈帧执行完成被销毁用于存储局部变量表、操作数栈、动态链接、方法出口等信息。栈里面存的是地址引用实际指向的是堆里面的对象c.本地方法栈与Java虚拟机栈类似用于管理本地方法的调用。d.方法区Java8叫元空间用于存放已被虚拟机加载的类信息常量静态变量等全局数据信息。e.堆Java 虚拟机中内存最大的一块是被所有线程共享的几乎所有的对象实例都在这里分配内存3线程私有、公有a.线程私有每个线程在开辟、运行的过程中会单独创建这样的一份内存有多少个线程可能有多少个内存 Java虚拟机栈、本地方法栈、程序计数器是线程私有的b.线程全局共享的 堆和方法区4栈虽然方法运行完毕了之后被清空了但是堆上面的还没有被清空所以引出了GC垃圾回收不能立马删除因为不知道是否还有其它的也是引用了当前的地址来访问的强引用、软引用、弱引用、虚引用强引用实现最常见的引用当我们新建一个对象时就创建了一个具有强引用的对象 例如Object obj new Object();回收时机只要一个对象具有强引用垃圾收集器就不会回收它即使抛出OutOfMemoryError也不会回收用途绝大多数普通对象的引用软引用实现java.lang.ref.SoftReference回收时机一个对象只具有软引用当内存不够的时候就会回收这些对象的内存回收后内存依然不够就会抛出OutOfMemoryError用途实现内存敏感的缓存如图片缓存、大量数据缓存既能利用缓存提升性能又能在内存紧张时自动释放。SoftReferenceObject softRef new SoftReference(new Object()); Object obj softRef.get(); // 可能返回 null若已被回收弱引用实现java.lang.ref.WeakReference回收时机比软引用具有更短的生命周期在垃圾回收时垃圾收集器一旦发现只具有弱引用的对象不管当前内存是否足够都会回收它的内存。用途弱引用通常用于实现对象缓存但不希望缓存的对象影响垃圾回收的情况。WeakReferenceObject weakRef new WeakReference(new Object()); Object obj weakRef.get(); // 下次 GC 后极可能返回 null虚引用实现java.lang.ref.PhantomReference回收时机是最弱的引用类型如果一个对象仅持有虚引用这就和没有任何引用一样在任何时候都可能被垃圾收集器回收。但在对象被回收前虚引用会被放入一个队列之中供程序员进行处理。用途虚引用主要用于跟踪对象被垃圾回收的时机进行一些必要的清理或记录ReferenceQueueObject queue new ReferenceQueue(); PhantomReferenceObject phantomRef new PhantomReference(new Object(), queue); // phantomRef.get() 永远为 null // 当对象被回收后phantomRef 会被加入 queue可以从中取出并执行清理可能追问的问题什么是引用队列ReferenceQueue软、弱、虚引用可以关联一个ReferenceQueue。当引用的对象被回收后引用本身会被 JVM 添加到该队列允许我们执行后续清理例如从缓存中移除无效的键值对。类加载机制JVM 类加载的生命周期包括加载、连接、初始化、使用和卸载其中连接又分为验证、准备和解析。加载阶段类加载器根据类的全限定名获取字节码加载到内存将其转换为 JVM 方法区中的运行时数据结构并在堆中生成对应的 Class 对象。验证阶段检查 Class 文件格式、元数据和字节码是否合法。防止恶意或者错误的字节码危害 JVM。准备阶段为 static 类变量分配内存并赋默认值例如static int age 18准备阶段通常先赋值为 0真正赋值为 18 要等到初始化阶段。编译期可确定的 static final 常量比较特殊可能直接赋常量值。解析阶段把常量池中的符号引用转换成直接引用。初始化阶段执行编译器生成的 类构造器clinit方法主要包括静态变量的显式赋值和静态代码块。类构造器是构造类信息的不是构造该类对象的构造器它会按顺序收集类中所有类变量的赋值动作和静态代码块中的语句然后合并产生初始化子类前JVM会先初始化父类而且同一个类在一个类加载器下通常只初始化一次。虚拟机会保证一个类的clinit方法在多线程环境中被正确加锁和同步非重点例如执行new Child()时JVM会先完成 Child 的加载、验证、准备和解析然后先初始化 Parent再初始化 Child最后才创建对象并调用 Child 的构造方法。什么时候会发生类初始化加载、链接、初始化类的主动引用一定会发生类的加载、链接、初始化虚拟机启动时会先初始化main方法所在的类new 对象调用类的静态成员除了final常量和静态方法用反射初始化子类时父类会先被加载类的被动引用可能会加载但不会触发初始化访问 final 常量System.out.println(MyClass.CONST);如果是staticfinalintCONST10;不会触发类初始化可能连加载都不会发生2. 通过数组定义类引用不会触发此类的初始化MyClass[]arrnewMyClass[10];只会加载[LMyClass;数组类不会初始化 MyClass3. 引用类但不使用MyClassm;不会触发加载final 常量什么时候初始化编译器常量满足以下条件的static final变量基本类型 / String直接赋值常量表达式 即字面量不需要运行期计算调用方法、new对象 就是运行期计算staticfinalintA10;staticfinalStringShello;在编译期就确定值 即.java 编译为.class文件时就确认运行时不参与初始化运行期final常量不是编译期常量例如staticfinalintAnewRandom().nextInt();staticfinalIntegerBInteger.valueOf(10);staticfinalStringSnewString(hello);准备阶段赋予默认值类初始化阶段Initialization执行clinit方法时初始化什么是Java里的垃圾回收如何触发垃圾回收垃圾回收Garbage Collection, GC是自动管理内存的一种机制它负责自动释放不再被程序引用的对象所占用的内存这种机制减少了内存泄漏和内存管理错误的可能性。垃圾回收可以通过多种方式触发具体如下内存不足时当JVM检测到堆内存不足无法为新的对象分配内存时会自动触发垃圾回收。手动请求虽然垃圾回收是自动的开发者可以通过调用System.gc()或Runtime.getRuntime().gc()建议 JVM 进行垃圾回收。不过这只是一个建议并不能保证立即执行。JVM参数启动 Java 应用时可以通过 JVM 参数来调整垃圾回收的行为比如-Xmx最大堆大小、-Xms初始堆大小等。对象数量或内存使用达到阈值垃圾收集器内部实现了一些策略以监控对象的创建和内存使用达到某个阈值时触发垃圾回收。内存泄漏内存泄漏是指在运行过程中不再使用的对象仍然被引用而无法被垃圾收集器回收从而导致内存组件减少的现象。虽然在Java中垃圾回收机制会自动回收不再使用的对象但如果有对象仍被不再使用的引用持有垃圾收集器无法回收这些内存最终可能导致程序的内存使用不断增加。内存溢出程序向虚拟机申请内存的时候虚拟机剩余内存不够满足申请需求的时候导致抛出异常或程序崩溃。判断垃圾的方法有哪些在 Java 中判断对象是否为垃圾即不再被使用可以被垃圾回收器回收主要依据两种主流的垃圾回收算法来实现引用计数法和可达性分析算法。引用计数法Reference Counting原理为每个对象分配一个引用计数器每当有一个地方引用它时计数器加 1当引用失效时计数器减 1。当计数器为 0 时表示对象不再被任何变量引用可以被回收。缺点不能解决循环引用的问题即两个对象相互引用但不再被其他任何对象引用这时引用计数器不会为 0导致对象无法被回收。可达性分析算法Reachability AnalysisJava 虚拟机主要采用此算法来判断对象是否为垃圾。原理从一组称为 GC Roots垃圾收集根的对象出发向下追溯它们引用的对象以及这些对象引用的其他对象以此类推。如果一个对象到 GC Roots 没有任何引用链相连即从 GC Roots 到这个对象不可达那么这个对象就被认为是不可达的可以被回收。GC Roots 对象包括虚拟机栈栈帧中的本地变量表中引用的对象、方法区中类静态属性引用的对象、本地方法栈中 JNIJava Native Interface引用的对象、活跃线程的引用等。垃圾回收算法是什么是为了解决了什么问题JVM有垃圾回收机制的原因是为了解决内存管理的问题。在传统的编程语言中开发人员需要手动分配和释放内存这可能导致内存泄漏、内存溢出等问题。而Java作为一种高级语言旨在提供更简单、更安全的编程环境因此引入了垃圾回收机制来自动管理内存。垃圾回收机制的主要目标是自动检测和回收不再使用的对象从而释放它们所占用的内存空间。这样可以避免内存泄漏一些对象被分配了内存却无法被释放导致内存资源的浪费。同时垃圾回收机制还可以防止内存溢出即程序需要的内存超过了可用内存的情况。通过垃圾回收机制JVM可以在程序运行时自动识别和清理不再使用的对象使得开发人员无需手动管理内存。这样可以提高开发效率、减少错误并且使程序更加可靠和稳定。垃圾回收算法有哪些?标记-清除算法标记-清除算法分为“标记”和“清除”两个阶段首先通过可达性分析标记出所有需要回收的对象然后统一回收所有被标记的对象。标记-清除算法有两个缺陷一个是效率问题标记和清除的过程效率都不高另外一个就是清除结束后会造成大量的碎片空间。有可能会造成在申请大块内存的时候因为没有足够的连续空间导致再次 GC。复制算法为了解决碎片空间的问题出现了“复制算法”。复制算法的原理是将内存分成两块每次申请内存时都使用其中的一块当GC时将这一块内存中所有存活的复制到另一块上。然后将然后再把已使用的内存整个清理掉。复制算法解决了空间碎片的问题。但是也带来了新的问题。因为每次在申请内存时都只能使用一半的内存空间。内存利用率严重不足。标记-整理算法复制算法在 GC 之后存活对象较少的情况下效率比较高但如果存活对象比较多时会执行较多的复制操作效率就会下降。而老年代的对象在 GC 之后的存活率就比较高所以就有人提出了“标记-整理算法”。标记-整理算法的“标记”过程与“标记-清除算法”的标记过程一致但标记之后不会直接清理。而是将所有存活对象都前移。移动结束后直接清理掉剩余部分。这样使得内存紧凑避免了内存碎片的产生。分代回收算法分代回收算法是根据对象的生命周期把堆内存划分为新生代和老年代并采用不同的垃圾回收策略。新创建的对象通常先进入新生代因为大多数对象存活时间较短所以新生代一般使用复制算法回收速度较快新生代回收称为Minor GC。经过多次垃圾回收仍然存活的对象会晋升到老年代。老年代中的对象生命周期较长通常使用标记清除或标记整理算法回收老年代通常称为 Major GC。分代回收的核心思想是针对不同生命周期的对象采用不同算法从而减少每次垃圾回收需要扫描的内存范围提高垃圾回收效率。堆的分区堆从逻辑上划分为新生代和老年代两大区域新生代:包含 Eden 区、From Survivor、To Survivor默认占比 8:1:1用于存放短期存活对象。其复制算法流程1. Eden From Survivor 存活对象复制到 To Survivor然后清空 Eden 和 From2. 交换 From/To 角色。即 To 空间 作为 一个 中转站老年代:存放长期存活、年龄达到阈值的对象也会存放大对象直接进入老年代避免大量复制。字符串常量池是堆里独立的一块逻辑区域不属于新生代也不属于老年代。转换多数对象先在 Eden 创建Minor GC 存活的对象在两块 Survivor 之间来回转移。存活达到年龄阈值的对象、大对象会直接进入老年代长期保存。涉及对象移动或复制的垃圾回收算法都需要更新对象引用。JVM 通常会在对象旧地址保存一个转发指针记录对象复制后的新地址并通过 GC Roots、Remembered Set 等结构找到并更新相关引用。有哪些垃圾回收器垃圾回收器是实现垃圾回收算法、负责自动查找并回收堆中死亡对象的执行引擎是 GC 算法的具体落地实现。1. 新生代垃圾收集器Serial收集器复制算法是新生代单线程收集器优点是简单高效算是最基本、发展历史最悠久的收集器。它在进行垃圾收集时必须暂停其他所有的工作线程直到它收集完成。ParNew收集器复制算法是新生代并行收集器其实就是Serial收集器的多线程版本。Parallel Scavenge收集器复制算法是新生代并行收集器追求高吞吐量高效利用 CPU。2. 老年代垃圾收集器Serial Old标记-整理算法是Serial收集器的老年代版本它同样是一个单线程(串行)收集器。Parallel Old标记-整理算法是Parallel Scavenge收集器的老年代版本使用多线程。CMS收集器标记-清楚算法是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上这类应用尤其重视服务器的响应速度希望系统停顿时间最短以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。CMS收集器是基于“标记-清除”算法实现的它的运作过程相对前面几种收集器来说更复杂一些整个过程分为4个步骤初始标记Initial Mark【STW 】只标记GC Roots 直接关联的对象速度很快停顿短。并发标记Concurrent Mark【并发不 STW】从初始标记存活对象遍历整个老年代追踪所有存活对象用户线程和 GC 线程同时运行这段期间会产生浮动垃圾。重新标记Remark【STW】修正并发标记期间用户线程新产生 / 引用变动的对象停顿时间一般长于初始标记远低于串行收集器。并发清除Concurrent Sweep【并发不 STW】清理标记出来的垃圾对象回收内存和用户线程并发执行。STW停止所有用户应用线程只保留 GC 线程运行GC 工作完成后再恢复业务线程。3. 新生代和老年代垃圾收集器G1收集器标记整理算法Java堆并行收集器G1收集器是JDK1.7提供的一个新收集器G1收集器基于“标记-整理”算法实现也就是说不会产生内存碎片。此外G1收集器不同于之前的收集器的一个重要特点是G1回收的范围是整个Java堆(包括新生代老年代)而前六种收集器回收的范围仅限于新生代或老年代细节G1Garbage First是一款面向大堆内存、低停顿需求的垃圾收集器。它不再把堆固定划分为连续的新生代和老年代而是将堆划分为多个大小相等的Region每个 Region 可以动态承担 Eden、Survivor 或老年代的角色。G1 会统计各 Region 的垃圾数量和回收收益并优先回收垃圾最多、收益最高的区域因此称为 Garbage First。它整体采用标记—整理算法局部 Region 之间采用复制算法所以回收后不会产生大量内存碎片。G1 的回收过程主要包括初始标记、并发标记、最终标记和筛选回收其中并发标记可以与用户线程同时执行。它还支持通过-XX:MaxGCPauseMillis设置期望的停顿时间但只能尽量满足不能保证绝对停顿时间。面试总结G1 通过 Region 化管理、并发标记和按收益优先回收在可控停顿时间内完成垃圾回收适合大内存和低延迟场景。G1收集器的优势独特的分代垃圾回收器分代GC分代收集器同时兼顾年轻代和老年代不再将堆物理划分为连续新生代、老年代把整个 Java 堆切分成多个大小一致的 Region。Region 逻辑上分为Eden、Survivor、Old、Humongous大对象区域Region 角色动态变换。并行性回收期间可由多个线程同时工作有效利用多核cpu资源空间整理回收过程中会进行适当对象移动减少空间碎片可预见性G1可选取部分区域进行回收可以缩小回收范围减少全局停顿G1收集器的阶段分以下几个步骤初始标记它标记了从GC Root开始直接可达的对象【STW】并发标记从GC Roots开始对堆中对象进行可达性分析找出存活对象【并发】最终标记标记那些在并发标记阶段发生变化的对象,即垃圾将被回收【STW】筛选回收首先对各个Region的回收价值垃圾占Region比例和成本回收时间包括扫描存活对象复制对象和更新引用进行排序根据用户所期待的GC停顿时间指定回收计划回收一部分Region【STW】
返回列表