Java语言面试-虚拟机(JVM、ART)面试

一、垃圾回收机制?有哪些对象可以作为GC roots?
1、
引用计数法
可达性分析算法
下面便具体讲解下两种方法:

. 引用计数法

这种方法是在对象的头处维护一个计数器Counter,当有一个引用指向对象的时候counter就加一,当不在引用此对象时就让counter减一。所以,当counter等于零的时候虚拟机就认为此对象时可以被回收的。看起来好像有点道理,但是这种方法存在一个致命的问题:

在这里插入图片描述
如上图所示:外部对对象A有一个引用,对象A持有对象B,而对象B也持有一个对象C,对象C又持有对象A。如果对于对象A的引用r失效,按照引用计数方法,GC永远无法回收上面的三个对象。所以基于上面的存在内存泄漏的巨大缺陷,Java虚拟机(应该是大多数虚拟机)不采用此方法进行回收内存。

. 可达性分析算法

Java就是使用此方法作为判断对象是否可被回收的。虚拟机会先将一些对象定义为GC Roots,从GC Roots出发一直沿着引用链向下寻找,如果某个对象不能通过GC Roots寻找到,那么虚拟机就认为该对象可以被回收。我们举个例子,如下图:
在这里插入图片描述

当对象D不在引用对象A时,尽管A、B、C互相还持有引用,GC依然会回收ABC所占用的内存。

2、虚拟机栈(栈帧中的本地变量表)中引用的对象;本地方法栈中JNI(即一般说的Native方法)引用的对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中JNI(Native方法)的引用对象;
二、跟Art、Dalvik对比
Dalvik与ART的区别
(1)在Dalvik下,应用每次运行都需要通过即时编译器(JIT)将字节码转换为机器码,即每次都要编译加运行,这虽然会使安装过程比较快,但是会拖慢应用以后每次启动的效率。而在ART 环境中,应用在第一次安装的时候,字节码就会预编译(AOT)成机器码,这样的话,虽然设备和应用的首次启动(安装慢了)会变慢,但是以后每次启动执行的时候,都可以直接运行,因此运行效率会提高。
(2)ART占用空间比Dalvik大(字节码变为机器码之后,可能会增加10%-20%),这也是著名的“空间换时间大法"。
(3)预编译也可以明显改善电池续航,因为应用程序每次运行时不用重复编译了,从而减少了 CPU 的使用频率,降低了能耗。
ART优点:

  1. 系统性能的显著提升
  2. 应用启动更快、运行更快、体验更流畅、触感反馈更及时
  3. 更长的电池续航能力
  4. 支持更低的硬件
    ART缺点:
  5. 更大的存储空间占用,可能会增加10%-20%
  6. 更长的应用安装时间
    三、Java内存模型?
    java内存模型是java虚拟机内存如何与计算机内存(RAM)一起工作。java虚拟机是是整个计算机的模型,所以这个模型自然包含一个内存模型。也可以说JMM是java虚拟机内存使用规范。
    参考答案:
    https://www.cnblogs.com/yanlong300/p/9009687.html
    https://blog.csdn.net/javazejian/article/details/72772461#%E7%90%86%E8%A7%A3java%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F%E4%B8%8Ejava%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B

四、类加载机制?双亲委托模型?
1、类加载机制

在这里插入图片描述

应用程序在使用类的时候,这个类的生命周期其实包括了如上图所示的七个阶段。
**使用:**就是我们平时在编码过程中用new关键字去创建一个类的实例去使用这个类。
**卸载:**虚拟机通过垃圾回收机制将这个类的信息和这个类相关的实例从虚拟机内存区域中移除。
一个虚拟机加载类的全过程就包括了:加载–>验证–>准备–>解析–>初始化

**加载:**通过这个类的全限定名找到这个类所在的位置,把它从一个文件或者一个字节流转化为虚拟机内存中的一个确确实实的对象。
**验证:**验证是为了检查每个java文件所对应的class文件所形成的字节流中包含的信息符不符合虚拟机的要求,有没有危害虚拟机自身安全的代码。
**准备:**准备阶段就是为类变量(static修饰)分配内存,并设置初始值(例如int为0)。
**解析:**解析就是将常量值的引用替换为实际值的过程
**初始化:**初始化是类加载的最后一步,在初始化阶段才开始真正执行类中所定义的java代码,注意这个初始化要和我们平时构造方法初始化(构造方法是在“使用”阶段用new关键字创建实例的时候才会调用)区分开来,这个初始化动作会把类中所有用了static修饰的变量以及静态语句块执行一遍,按照我们的意愿把类变量赋给我们所定义的变量。

在类加载过程的这5个阶段中,我们在开发java程序的时候能影响的只有“加载”阶段,负责“加载”动作的东西叫“类加载器”,可以使用系统为我们提供的类加载器,也可以使用自定义的类加载器。

自定义的类加载器常常用于部署时的热替换,还有类的加密和解密。在部署程序的时候可能会把我们的class文件进行加密,在实际运行的时候首先要把加密后的class文件通过自定义的类加载器进行解密然后交给虚拟机去执行。
2、类加载器

在这里插入图片描述

启动类加载器Bootstrap ClassLoader,负责加载jdk安装目录<JAVA_HOME>下的lib目录下的jdk自身所带的java类,这个类加载器是我们的应用程序无法调用的。
除了上图三个类加载器我们还可以自定义类加载器,这些加载器之间存在什么样的关系呢?或者说虚拟机是如何组织这些类加载器进行工作的?这就要用到“双亲委派模型”了
3、双亲委派模型
在这里插入图片描述

当一个加载器不管是应用程序类加载器还是我们自定义的类加载器在进行类加载的时候它首先不会自己去加载,它首先会把加载任务委派给自己的父类加载器,比如现在有个类需要我们的自定义类加载器来加载,其实它首先会把它交给应用程序类加载器,应用程序类加载器又会把任务交给扩展类加载器,一直往上提交,直到启动类加载器。启动类加载器如果在自己的扫描范围内能找到类,它就会去加载,如果它找不到,它就会交给它的下一级子加载器去加载,以此类推,这就是双亲委派模型。
为什么jdk里要提出双亲委派模型?
可以保证我们的类有一个合适的优先级,例如Object类,它是我们系统中所有类的根类,采用双亲委派模型以后,不管是哪个类加载器来加载Object类,哪怕这个加载器是自定义类加载器,通过双亲委派模型,最终都是由启动类加载器去加载的,这样就可以保证Object这个类在程序的各个类加载器环境中都是同一个类。在虚拟机里觉得一个类是不是唯一有两个因素,第一个就是这个类本身,第二个就是加载这个类的类加载器,如果同一个类由不同的类加载器去加载,在虚拟机看来,这两个类是不同的类。
参考答案:https://blog.csdn.net/aimashi620/article/details/84836245

五.、内存模型以及分区,需要详细到每个区放什么。
1、Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念。java内存模型中分为主内存和工作内存。主内存里面存储着所有变量,主内存是共享内存区域,所有线程都可以访问。每一个线程都私有一个工作内存,工作内存里面保存着主内存里面变量值的副本,线程对变量的操作都是在工作内存中完成,操作结束后再放回主内存。主内存可粗略认为是堆,工作内存认为是栈。操作系统中,一般CPU都会从内存取数据到寄存器,然后进行处理,但由于内存的处理速度远远低于CPU,导致CPU在处理指令时往往花费很多时间在等待内存做准备工作,于是在寄存器和主内存间添加了CPU缓存,CPU缓存比较小,但访问速度比主内存快得多。Java虚拟机在程序执行过程会把jvm的内存分为若干个不同的数据区域来管理,这些区域有自己的用途,以及创建和销毁时间。
2、 JVM 分为堆区和栈区,还有方法区,初始化的对象放在堆里面,引用放在栈里面,class类信息常量池(static常量和static变量)等放在方法区
new:
方法区:主要是存储类信息,常量池(static常量和static变量),编译后的代码(字节码)等数据
堆:初始化的对象,成员变量 (那种非static的变量),所有的对象实例和数组都要在堆上分配
栈:栈的结构是栈帧组成的,调用一个方法就压入一帧,帧上面存储局部变量表,操作数栈,方法出口等信息,局部变量表存放的是8大基础类型加上一个应用类型,所以还是一个指向地址的指针
本地方法栈:主要为Native方法服务
程序计数器:记录当前线程执行的行号
参考答案:https://blog.csdn.net/qiuchaoxi/article/details/79889097
六、 堆里面的分区:Eden,survival from to,老年代,各自的特点。
堆里面分为新生代和老生代(java8取消了永久代,采用了Metaspace),新生代包含Eden+Survivor区,survivor区里面分为from和to区,内存回收时,如果用的是复制算法,从from复制到to,当经过一次或者多次GC之后,存活下来的对象会被移动到老年区,当JVM内存不够用的时候,会触发Full GC,清理JVM老年区当新生区满了之后会触发YGC,先把存活的对象放到其中一个Survice区,然后进行垃圾清理。因为如果仅仅清理需要删除的对象,这样会导致内存碎片,因此一般会把Eden 进行完全的清理,然后整理内存。那么下次GC 的时候,就会使用下一个Survive,这样循环使用。如果有特别大的对象,新生代放不下,就会使用老年代的担保,直接放到老年代里面。因为JVM 认为,一般大对象的存活时间一般比较久远。
七、对象创建方法,对象的内存分配,对象的访问定位。
八、GC的两种判定方法:引用计数与引用链。
引用计数法:指的是如果某个地方引用了这个对象就+1,如果失效了就-1,当为0就会回收但是JVM没有用这种方式,因为无法判定相互循环引用(A引用B,B引用A)的情况
引用链法: 通过一种GC ROOT的对象(方法区中静态变量引用的对象等-static变量)来判断,如果有一条链能够到达GC ROOT就说明,不能到达GC ROOT就说明可以回收
九、 GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?

先标记,标记完毕之后再清除,效率不高,会产生碎片
复制算法:分为8:1的Eden区和survivor区,就是上面谈到的YGC
标记整理:标记完毕之后,让所有存活的对象向一端移动

十、 GC收集器有哪些?CMS收集器与G1收集器的特点。
并行收集器:串行收集器使用一个单独的线程进行收集,GC时服务有停顿时间
串行收集器:次要回收中使用多线程来执行
CMS收集器是基于“标记—清除”算法实现的,经过多次标记才会被清除
G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的
[GC收集器]: http://www.jianshu.com/p/50d5c88b272d
十一、Minor GC与Full GC分别在什么时候发生?
新生代内存不够用时候发生MGC也叫YGC,JVM内存不够的时候发生FGC
十二、几种常用的内存调试工具:jmap、jstack、jconsole。
jstack可以看当前栈的情况,jmap查看内存,jhat 进行dump堆的信息 mat(eclipse的也要了解一下)

十三、 分派:静态分派与动态分派。
十四、java泛型类型擦除发生在什么时候,通配符有什么需要注意的。
十五、java类加载过程:
1.加载时机:创建实例、访问静态变量或方法、反射、加载子类之前
2.验证:验证文件格式、元数据、字节码、符号引用的正确性
3.加载:根据全类名获取文件字节流、将字节流转化为静态储存结构放入方法区、生成class对象
4.准备:在堆上为静态变量划分内存
5.解析:将常量池中的符号引用转换为直接引用
6.初始化:初始化静态变量
参考答案:https://www.jianshu.com/p/bc6d1770d92c

十六、java虚拟机的特性
十七、谈谈对jvm的理解
十八、JVM内存区域,开线程影响哪块内存
十九、对Dalvik、ART虚拟机有什么了解?
二十、Art和Dalvik对比
二十一、虚拟机原理,如何自己设计一个虚拟机(内存管理,类加载,双亲委派)
二十二、谈谈你对双亲委派模型理解
二十三、JVM内存模型,内存区域
谈谈对ClassLoader(类加载器)的理解
谈谈对动态加载(OSGI)的理解
内存对象的循环引用及避免
内存回收机制、GC回收策略、GC原理时机以及GC对象
垃圾回收机制与调用System.gc()区别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值