JVM知识点及面试题补充

JVM从软件层面屏蔽了不同操作系统的底层硬件与指令上的区别(所谓的Java跨平台能力

java中JRE(java运行时环境)包括java各种Libraries类库以及Java Virtual Machine(Java虚拟机)。

类加载子系统:

可以根据指定的权限定名来载入类或者接口

字节码执行引擎:

执行那些包含在被载入类的方法中的指令。

运行时数据区:

存储字节码,对象,参数,返回值,局部变量,运行的中间结果,常量这些

java执行总体:先通过javac命令,将java程序转化为字节码文件,再通过java命令进行运行字节码文件,然后JVM开始运行,先经过第一部分类装载子系统,将java的字节码文件装在到java运行时数据区(内存区)中(左边的一块),然后第二部分,通过字节码执行引擎,来执行内存模型中的代码。

堆:

new出的对象优先放在Eden区,Eden区放满之后进行minor gc(垃圾收集),java底层垃圾收集的原理是由字节码执行引擎运行,在后台开启一个垃圾收集线程,从GC Roots的根节点(即线程栈本地变量,静态变量,本地方法栈变量等),垃圾收集是针对整个年轻代进行,不只eden,如果收集了15次,还仍然存在的话就会进入老年代

如果老年代放满了,再放就会进行full gc,对整个堆内存区域进行gc操作,如果年轻代还有空间就接着用,如果老年代和年轻代都没有空间就会发生OOM,内存溢出

是否从年轻代放入到老年代中(有很多种可能)

1.长期存活的对象将进入老年代如果对象在eden存在并经过第一次minorgc后仍然能够存活,并且能够被servivor容纳的话,将被移动到survivor空间中,并将对象年龄设置为1,对象在survivor中每熬过一次minorgc年龄就增加一岁,当它的年龄增加到一定程度(默认为15,cms收集器默认为6岁,不同的垃圾收集器会略微有所不同),就会被晋升到老年代中,对象晋升到老年代的年龄阈值也可以通过参数 -XX:MaxTenuringThreshold来设置

2.大对象会直接进入老年代大对象就是需要大量连续内存空间的对象(比如字符串,数组这些),JVM参数 -XX:PretenureSizeThreshold可以设置大对象的大小,如果对象超过设置大熊啊就会直接进入老年代,不会进入年轻代,这个参数旨在Serial和ParNew两个收集器下有效(这样也是为了避免为大对象分配内存时的复制操作而降低了效率)

3.老年代空间分配担保机制如果上一步的结果是小于或者之前说的参数没有设置,那么就会触发一次Fullgc,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够的空间存放新的对象就会发生OOM。当然,如果minorgc之后剩余存活的需要挪用到老年化年代的对象大小还是大于老年代可用空间,那么也会触发fullgc,fullgc之后如果还是没有空间存放minorgc之后存活的对象,那么也将发生OOM。年轻代每次minorgc之前JVM都会计算下老年代剩余可用空间,如果中国可用空间小于年轻代里现有的所有对象大熊啊之和(包括垃圾对象),就会看一个 -XX:-HandlePromotionFailure的参数来设置了。如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每次minorgc后进入老年代的对象的平均大小。

4.对象动态年龄判断当前放对象的Survivor区域中(其中一块区域,放对象的那块s区),一批对象的总大小大于这块survivor区域内存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了,例如Servivor区域中有一批对象,年龄1+年龄2+年龄n的多个年龄对象的总和超过了servivor的50%,此时就会把年龄n以上的对象都放入老年代,这个规则其实是希望哪些可能是长期存活的对象,尽早进入老年代,对象和动态年龄判断机制一般是minorgc之后触发的

栈(每个线程运行时所需要的内存)(又叫线程栈(因为每个线程都分配空间存储局部变量,和方法调用)):放置局部变量等,(只要开始运行,就会从栈中挖一小块运行,放置该线程中的局部变量,每个线程中,运行到main方法时就也会挖下一块栈,给main方法下的局部变量,用到其中的方法,也会挖一小块空间,放置方法内的局部变量,而方法中挖的这一块空间就叫做一块栈帧内存区域(一个方法对应一块栈帧内存区域),方法结束之后,直接把占用的空间直接划去(直接把这块内存删了))

执行main方法,就给main分配一些空间,执行到方法的时候就又会给方法分配空间(这两块空间不相同,相互独立,没有关系),然后方法先结束,内存先销毁,栈帧内存这种先进后出的方式,因为这跟方法的嵌套调用的先后顺序是相吻合的。

  • 栈帧内存区域中包含了(其实都是内存空间)
    • 局部变量表
      • 存储局部变量的数据存储结构
    • 操作数栈
      • 一块临时的空间,用于进行各种数的运算
    • 动态链接
      • 将符号引用转为直接引用(这里的符号包括着各种名词甚至是字符),如math.compute()运行时会去方法区找到该方法的位置
    • 方法出口
      • 规定好运行完某个方法后回到main方法的某个位置
  • 栈与堆的区别是什么?
    • 1.栈内存一般会用来存储局部变量和方法调用,但是堆内存是用来存储Java对象和数组的,堆会GC垃圾回收,而栈不会
    • 2.栈内存是线程私有的,堆内存是线程共有的
    • 3.两者异常错误不同,如果栈内存或者堆内存不足都会抛出异常.栈空间不足:java.lang.StackOverFlowError。 堆空间不足:java.lang.OutOfMemoryError

  • 程序计数器:线程私有的,记录下一行即将运行的代码的内存地址,程序计数器是每一块线程中都有的用来存储即将运行的代码的内存地址的空间(设置这块的原因是多线程,如果有一个CPU实现更高的线程,那么就会转变为运行另一个线程,而因此,刚才运行的代码就会在运行完那一行之后挂起,等到新线程运行完之后运行,但却不能重新运行,因此设计程序计数器,只要根据程序计数器中的东西继续运行就可以了)
    • javap -v xx.class打印堆栈大小,局部变量的数量和方法的参数
    • !!!字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。
  • 本地方法栈(用的不多其实)
    • 是一些native的方法,用到本地方法时本地方法栈会挖取一部分的内存运行本地方法

  • 方法区元空间
    • 其中放置的是常量,静态变量,类信息这些
      • public static User user = new User();
        • 静态变量user放在方法区元空间中,而new User()是放在堆中的
    • 在元空间中包含class{类信息}(,classloader{加载类},)运行时常量池
      • 常量池可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息

  • 你听说过直接内存吗?
  • 直接内存并不属于内存结构,并不由jvm进行管理,是虚拟机的系统内存,常见于NIO操作时,用于数据缓冲区,它分配回收成本较高,但是读写性能高

  • JVM调优工具
    • JVM调优的目的:减少gc操作,本质上是减少STW的发生
      • STW:停止用户发起的所有进程。每次进行gc都可能会导致业务卡顿,如果gc比较慢的话,业务会直接卡在那里,gc对业务的运行性能有影响
      • 面试问题:为什么会设计STW,如果没有STW,那么在进行gc的过程中,程序依旧在进行,这样原本的非垃圾对象可能也会变成垃圾对象,正因为有STW停止了进程,这样哪些是垃圾对象也就定形了,如果是变化的gc就很难进行垃圾收集了
    • Jmap
      • 此命令可以用来查看内存信息,实例个数以及占用内存大小
        • 命令jmap -histo 14660#查看历史生成的实例
        • jmap -histo:live 14660#查看当前存活的实例,执行过程中可能触发的一此fullgc
    • Jstack
      • 可以加进行id查找死锁
    • Jinfo
      • 查看正在运行的Java应用程序的扩展参数

  • CPU多核并发缓存架构
    • 数据从硬盘中拿到主内存中,再通过主内存到CPU
    • 早期一般CPU会直接从主内存中拿数据(不经过CPU缓存),因为主内存和CPU的运行速度差距过大,这样会导致CPU发挥不出其速度优势,因此引入了CPU高速缓存(速度跟CPU也差不多),这样也可以提高整个计算机的运行速度
  • JMM(java内存模型)内存模型
    • JAVA多线程内存模型跟CPU缓存模型类似,是基于cpu缓存模型建立的,Java线程内存模型是标准化的,屏蔽掉了底层计算机的不同

  • 直接内存
    • 直接内存是一种特殊的内存缓冲区,并不在 Java 堆或方法区中分配的,而是通过 JNI 的方式在本地内存上分配的。
    • 直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。
    • JDK1.4 中新加入的 NIO(Non-Blocking I/O,也被称为 New I/O),引入了一种基于通道(Channel)与缓存区(Buffer)的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。
    • 直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
    • 类似的概念还有 堆外内存 。在一些文章中将直接内存等价于堆外内存,个人觉得不是特别准确。
    • 堆外内存就是把内存对象分配在堆外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。

  • 常见面试题:
    • 如何判断对象是否死亡(两种方法)。
    • 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。
    • 如何判断一个常量是废弃常量
    • 如何判断一个类是无用的类
    • 垃圾收集有哪些算法,各自的特点?
    • HotSpot 为什么要分为新生代和老年代?
    • 常见的垃圾回收器有哪些?
    • 介绍一下 CMS,G1 收集器。
    • Minor Gc 和 Full GC 有什么不同呢?

死亡对象判断方法:

  • 引用计数法
    • 给对象中添加一个引用计数器:
    • 每当有一个地方引用它,计数器就加 1;
    • 当引用失效,计数器就减 1;
    • 任何时候计数器为 0 的对象就是不可能再被使用的。
    • 这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间循环引用的问题
    • 所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。
  • 可达性分析算法:
  •  
    • 这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
    • 哪些对象可以作为 GC Roots 呢?
      • 虚拟机栈(栈帧中的局部变量表)中引用的对象
      • 本地方法栈(Native 方法)中引用的对象
      • 方法区中类静态属性引用的对象
      • 方法区中常量引用的对象
      • 所有被同步锁持有的对象
      • JNI(Java Native Interface)引用的对象
    • 对象可以被回收,就代表一定会被回收吗?
      • 即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
      • 被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收

  • 如何判断一个常量是废弃常量:
    • 假如在字符串常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池了。

  • 如何判断一个类是无用的类?
    • 方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
    • 判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类”:
    • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
    • 加载该类的 ClassLoader 已经被回收。
    • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
    • 虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。

  • 垃圾收集算法
    • 标记清除算法
      • 标记-清除(Mark-and-Sweep)算法分为“标记(Mark)”和“清除(Sweep)”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。
      • 它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:
        • 效率问题:标记和清除两个过程效率都不高。
        • 空间问题:标记清除后会产生大量不连续的内存碎片
      • 关于具体是标记可回收对象还是不可回收对象,众说纷纭,两种说法其实都没问题,我个人更倾向于是前者。
      • 如果按照前者的理解,整个标记-清除过程大致是这样的:
      • 当一个对象被创建时,给一个标记位,假设为 0 (false);
      • 在标记阶段,我们将所有可达对象(或用户可以引用的对象)的标记位设置为 1 (true);
      • 扫描阶段清除的就是标记位为 0 (false)的对象。
    • 复制算法
      • 为了解决标记-清除算法的效率和内存碎片问题,复制(Copying)收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
      • 虽然改进了标记-清除算法,但依然存在下面这些问题:
      • 可用内存变小:可用内存缩小为原来的一半。
      • 不适合老年代:如果存活对象数量比较大,复制性能会变得很差。
    • 标记整理算法
      • 标记-整理(Mark-and-Compact)算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
      • 由于多了整理这一步,因此效率也不高,适合老年代这种垃圾回收频率不是很高的场景。
    • 分代收集算法
      • 当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 Java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
      • 比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
      • 延伸面试问题: HotSpot 为什么要分为新生代和老年代?
      • 根据上面的对分代收集算法的介绍回答。
  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然,我可以为您介绍一些Java基础知识面试。以下是一些常见的Java基础知识面试及其答案: 1. 什么是Java? Java是一种面向对象的编程语言,具有跨平台特性,可以在不同的操作系统上运行。 2. Java的特点有哪些? Java具有以下特点: - 简单易学:Java语法相对简单,与C++相比更易于学习和使用。 - 面向对象:Java支持面向对象的编程范式,具有封装、继承和多态等特性。 - 跨平台性:Java通过虚拟机(JVM)实现跨平台,一次编写,到处运行。 - 安全性:Java提供了安全机制,如内存管理、异常处理和安全检查等。 - 多线程:Java支持多线程编程,可以实现并发操作。 3. 什么是面向对象编程? 面向对象编程(OOP)是一种编程范式,将数据和操作数据的方法封装在一起,形成对象。对象可以通过定义类来创建,类定义了对象的属性和行为。 4. Java中的基本数据类型有哪些? Java中的基本数据类型包括: - 整数类型:byte、short、int、long - 浮点数类型:float、double - 字符类型:char - 布尔类型:boolean 5. Java中的包是什么? 包(Package)是Java中用于组织类和接口的一种机制。它可以将相关的类和接口放在同一个包中,方便管理和使用。 6. 什么是Java的访问修饰符? Java的访问修饰符用于控制类、方法和变量的访问权限。常用的访问修饰符有public、protected、private和默认(没有修饰符)。 7. Java中的异常处理机制是什么? Java中的异常处理机制通过try-catch-finally语句块来实现。当代码可能抛出异常时,可以使用try块来捕获异常,并在catch块中处理异常。finally块中的代码无论是否发生异常都会执行。 8. 什么是Java的多线程? 多线程是指在一个程序中同时执行多个线程,每个线程都是独立的执行流。Java通过Thread类和Runnable接口来实现多线程编程。 9. Java中的垃圾回收是什么? Java中的垃圾回收是自动内存管理的一种机制,通过垃圾回收器自动释放不再使用的内存。开发人员无需手动释放内存,可以专注于业务逻辑的实现。 10. 什么是Java的反射机制? Java的反射机制是指在运行时动态地获取类的信息并操作类的属性和方法。通过反射机制,可以在运行时创建对象、调用方法和访问属性等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值