写点什么

一篇文章丢给你,让你一文了解 JVM,2021Java 最新大厂面试真题总结

用户头像
极客good
关注
发布于: 刚刚

方法区是各线程共享的内存区域,它用于存储已被 JVM 加载的类信息、常量、静态变量、运行时常量池等数据。


3.2.2 Java 堆(Java Heap)




Java 堆是各线程共享的内存区域,在 JVM 启动时创建,这块区域是 JVM 中最大的, 用于存储应用的对象和数组,也是 GC 主要的回收区,一个 JVM 实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:新生代、老年代、永久代。


说明:


  • Jdk1.6 及之前:常量池分配在永久代 。

  • Jdk1.7:有,但已经逐步“去永久代” 。

  • Jdk1.8 及之后:无永久代,改用元空间代替(java.lang.OutOfMemoryError: PermGen space,这种错误将不会出现在 JDK1.8 中)。


3.2.3 Java 栈(JVM Stack)




  1. 栈是什么


Java 栈是线程私有的,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就 Over,生命周期和线程一致。基本类型的变量和对象的引用变量都是在函数的栈内存中分配。


  1. 栈存储什么


每个方法执行的时候都会创建一个栈帧,栈帧中主要存储 3 类数据:


  • 局部变量表:输入参数和输出参数以及方法内的变量;

  • 栈操作:记录出栈和入栈的操作;

  • 栈帧数据:包括类文件、方法等等。


  1. 栈运行原理


栈中的数据都是以栈帧的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法和运行期数据的数据集。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在栈中从入栈到出栈的过程。



  1. 本地方法栈(Native Method Stack)


本地方法栈和 JVM 栈发挥的作用非常相似,也是线程私有的,区别是 JVM 栈为 JVM 执行 Java 方法(也就是字节码)服务,而本地方法栈为 JVM 使用到的 Native 方法服务。它的具体做法是在本地方法栈中登记 native 方法,在执行引擎执行时加载 Native Liberies.有的虚拟机(比如 Sun Hotpot)直接把两者合二为一。


  1. 程序计数器(Program Counter Register)


程序计数器是一块非常小的内存空间,几乎可以忽略不计,每个线程都有一个程序计算器,是线程私有的,可以看作是当前线程所执行的字节码的行号指示器,指向方法区中的方法字节码(下一个将要执行的指令代码),由执行引擎读取下一条指令。


  1. 运行时常量池


运行时常量池是方法区的一部分,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。相较于 Class 文件常量池,运行时常量池更具动态性,在运行期间也可以将新的变量放入常量池中,而不是一定要在编译时确定的常量才能放入。最主要的运用便是 String 类的 intern()方法。



3.3 执行引擎(Execution Engine)




执行引擎执行包在装载类的方法中的指令,也就是方法。执行引擎以指令为单位读取 Java 字节码。它就像一个 CPU 一样,一条一条地执行机器指令。每个字节码指令都由一个 1 字节的操作码和附加的操作数组成。执行引擎取得一个操作码,然后根据操作数来执行任务,完成后就继续执行下一条操作码。


不过 Java 字节码是用一种人类可以读懂的语言编写的,而不是用机器可以直接执行的语言。因此,执行引擎必须把字节码转换成可以直接被 JVM 执行的语言。字节码可以通过以下两种方式转换成合适的语言:


  • 解释器: 一条一条地读取,解释并执行字节码执行,所以它可以很快地解释字节码,但是执行起来会比较慢。这是解释执行语言的一个缺点。

  • 即时编译器:用来弥补解释器的缺点,执行引擎首先按照解释执行的方式来执行,然后在合适的时候,即时编译器把整段字节码编译成本地代码。然后,执行引擎就没有必要再去解释执行方法了,它可以直接通过本地代码去执行。执行本地代码比一条一条进行解释执行的速度快很多,编译后的代码可以执行的很快,因为本地代码是保存在缓存里的。



3


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


.4 垃圾收集(Garbage Collection, GC)




3.4.1 什么是垃圾收集




垃圾收集即垃圾回收,简单的说垃圾回收就是回收内存中不再使用的对象。所谓使用中的对象(已引用对象),指的是程序中有指针指向的对象;而未使用中的对象(未引用对象),则没有被任何指针给指向,因此占用的内存也可以被回收掉。


垃圾回收的基本步骤分两步:


  • 查找内存中不再使用的对象(GC 判断策略)

  • 释放这些对象占用的内存(GC 收集算法)


3.4.2 GC 判断策略




  1. 引用计数算法


引用计数算法是给对象添加一个引用计数器,每当有一个引用它时,计数器值就加 1;当引用失效时,计数器值就减 1;任何时刻计数器都为 0 的对象就是不可能再被使用的对象。缺点:很难解决对象之间相互循环引用的问题。


  1. 根搜索算法


根搜索算法的基本思路就是通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连(也就是说从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的。


在 Java 语言里,可作为 GC Roots 的对象包括以下几种:


  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;

  • 方法区中类静态属性引用的对象;

  • 方法区中常量应用的对象;

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



注:在根搜索算法中不可达的对象,也并非是“非死不可”的,因为要真正宣告一个对象死亡,至少要经历两次标记过程:第一次是标记没有与 GC Roots 相连接的引用链;第二次是 GC 对在 F-Queue 执行队列中的对象进行的小规模标记(对象需要覆盖 finalize()方法且没被调用过)。


3.4.3 GC 收集算法




  1. 标记-清除算法(Mark-Sweep)


标记-清楚算法采用从根集合(GC Roots)进行扫描,首先标记出所有需要回收的对象(根搜索算法),标记完成后统一回收掉所有被标记的对象。



该算法有两个问题:


  • 效率问题:标记和清除过程的效率都不高;

  • 空间问题:标记清除后会产生大量不连续的内存碎片, 空间碎片太多可能会导致在运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集。


  1. 复制算法(Copying)


复制算法是将可用内存按容量划分为大小相等的两块, 每次只用其中一块, 当这一块的内存用完, 就将还存活的对象复制到另外一块上面, 然后把已使用过的内存空间一次清理掉。



  1. 标记-整理算法(Mark-Compact)


标记整理算法的标记过程与标记清除算法相同, 但后续步骤不再对可回收对象直接清理, 而是让所有存活的对象都向一端移动,然后清理掉端边界以外的内存。



  1. 分代收集算法(Generational Collection)


分代收集算法是目前大部分 JVM 的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。



新生代(Young Generation)的回收算法(以复制算法为主)


  • 所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。

  • 新生代内存按照 8:1:1 的比例分为一个 eden 区和两个 survivor(survivor0,survivor1)区。一个 Eden 区,两个 Survivor 区(一般而言)。大部分对象在 Eden 区中生成。回收时先将 eden 区存活对象复制到一个 survivor0 区,然后清空 eden 区,当这个 survivor0 区也存放满了时,则将 eden 区和 survivor0 区存活对象复制到另一个 survivor1 区,然后清空 eden 和这个 survivor0 区,此时 survivor0 区是空的,然后将 survivor0 区和 survivor1 区交换,即保持 survivor1 区为空, 如此往复。

  • 当 survivor1 区不足以存放 eden 和 survivor0 的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次 Full GC(Major GC),也就是新生代、老年代都进行回收。

  • 新生代发生的 GC 也叫做 Minor GC,MinorGC 发生频率比较高(不一定等 Eden 区满了才触发)。


老年代(Tenured Generation)的回收算法(以标记-清除、标记-整理为主)


  • 在年轻代中经历了 N 次垃圾回收后仍然存活的对象,就会被放到老年代中。因此,可以认为老年代中存放的都是一些生命周期较长的对象。

  • 内存比新生代也大很多(大概比例是 1:2),当老年代内存满时触发 Major GC 即 Full GC,Full GC 发生频率比较低,老年代对象存活时间比较长,存活率标记高。


永久代(Permanet Generation)的回收算法

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
一篇文章丢给你,让你一文了解JVM,2021Java最新大厂面试真题总结