面试相关之 JVM &设计模式

Posted code小生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试相关之 JVM &设计模式相关的知识,希望对你有一定的参考价值。

code小生,一个专注 android 领域的技术平台

JVM

1. JVM内存是如何划分的?

参考回答:
JVM会用一段空间来存储执行程序期间需要用到的数据和相关信息,这段空间就是运行时数据区(Runtime Data Area),也就是常说的JVM内存。JVM会将它所管理的内存划分为线程私有数据区和线程共享数据区两大类:

线程私有数据区包含:
程序计数器:是当前线程所执行的字节码的行号指示器
虚拟机栈:是Java方法执行的内存模型
本地方法栈:是虚拟机使用到的Native方法服务

线程共享数据区包含:
Java堆:用于存放几乎所有的对象实例和数组;是垃圾收集器管理的主要区域,也被称做“GC堆”;是Java虚拟机所管理的内存中最大的一块
方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放

2. 谈谈垃圾回收机制?为什么引用计数器判定对象是否回收不可行?知道哪些垃圾回收算法?

参考回答:
(1)判定对象可回收有两种方法:
引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。然而在主流的Java虚拟机里未选用引用计数算法来管理内存,主要原因是它难以解决对象之间相互循环引用的问题,所以出现了另一种对象存活判定算法。
可达性分析法:通过一系列被称为『GC Roots』的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。其中可作为GC Roots的对象:虚拟机栈中引用的对象,主要是指栈帧中的本地变量、本地方法栈中Native方法引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象

(2)回收算法有以下四种:
分代收集算法:是当前商业虚拟机都采用的一种算法,根据对象存活周期的不同,将Java堆划分为新生代和老年代,并根据各个年代的特点采用最适当的收集算法。
新生代:大批对象死去,只有少量存活。使用『复制算法』,只需复制少量存活对象即可。
复制算法:把可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用尽后,把还存活着的对象『复制』到另外一块上面,再将这一块内存空间一次清理掉。
老年代:对象存活率高。使用『标记—清理算法』或者『标记—整理算法』,只需标记较少的回收对象即可。
标记-清除算法:首先『标记』出所有需要回收的对象,然后统一『清除』所有被标记的对象。
标记-整理算法:首先『标记』出所有需要回收的对象,然后进行『整理』,使得存活的对象都向一端移动,最后直接清理掉端边界以外的内存。

3. Java中引用有几种类型?在Android中常用于什么情景?

参考回答:
引用的四种类型:
强引用(StrongReference):具有强引用的对象不会被GC;即便内存空间不足,JVM宁愿抛出OutOfMemoryError使程序异常终止,也不会随意回收具有强引用的对象。
软引用(SoftReference):只具有软引用的对象,会在内存空间不足的时候被GC;软引用常用来实现内存敏感的高速缓存。
弱引用(WeakReference):只被弱引用关联的对象,无论当前内存是否足够都会被GC;强度比软引用更弱,常用于描述非必需对象;常用于解决内存泄漏的问题
虚引用(PhantomReference):仅持有虚引用的对象,在任何时候都可能被GC;常用于跟踪对象被GC回收的活动;必须和引用队列 (ReferenceQueue)联合使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

4. 类加载的全过程是怎样的?什么是双亲委派模型?

参考回答:
(1)类加载机制:是虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型的过程。另外,类型的加载、连接和初始化过程都是在程序运行期完成的,从而通过牺牲一些性能开销来换取Java程序的高度灵活性。下面介绍类加载每个阶段的任务:

加载(Loading):通过类的全限定名来获取定义此类的二进制字节流;将该二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构,该数据存储数据结构由虚拟机实现自行定义;在内存中生成一个代表这个类的java.lang.Class对象,它将作为程序访问方法区中的这些类型数据的外部接口
验证(Verification):确保Class文件的字节流中包含的信息符合当前虚拟机的要求,包括文件格式验证、元数据验证、字节码验证和符号引用验证
准备(Preparation):为类变量分配内存,因为这里的变量是由方法区分配内存的,所以仅包括类变量而不包括实例变量,后者将会在对象实例化时随着对象一起分配在Java堆中;设置类变量初始值,通常情况下零值
解析(Resolution):虚拟机将常量池内的符号引用替换为直接引用的过程
初始化(Initialization):是类加载过程的最后一步,会开始真正执行类中定义的Java字节码。而之前的类加载过程中,除了在『加载』阶段用户应用程序可通过自定义类加载器参与之外,其余阶段均由虚拟机主导和控制

(2)类加载器:不仅用于加载类,还和这个类本身一起作为在JVM中的唯一标识。常见类加载器类型有:
启动类加载器:是虚拟机自身的一部分
扩展类加载器、应用程序类加载器、自定义类加载器:独立于虚拟机外部

(3)双亲委派模型:表示类加载器之间的层次关系。
前提:除了顶层启动类加载器外,其余类加载器都应当有自己的父类加载器,且它们之间关系一般不会以继承(Inheritance)关系来实现,而是通过组合(Composition)关系来复用父加载器的代码。
工作过程:若一个类加载器收到了类加载的请求,它先会把这个请求委派给父类加载器,并向上传递,最终请求都传送到顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

5. 工作内存和主内存的关系?在Java内存模型有哪些可以保证并发过程的原子性、可见性和有序性的措施?

参考回答:
理解JVM之内存模型&线程
https://www.jianshu.com/p/90a036212cb4

6. JVM、Dalvik、ART的区别?

参考回答:

异同

ART:代替Dalvik,应用无需每次运行都要先编译,而是在安装时就预编译字节码到机器语言,提升运行时效率;预先编译也使得ART占用空间比Dalvik大,即用空间换时间;由于减少运行时重复编译,可明显改善电池续航,降低了能耗。

7. Java中堆和栈的区别?

参考回答:
在java中,堆和栈都是内存中存放数据的地方,具题区别是:
栈内存:主要用来存放基本数据类型和局部变量;当在代码块定义一个变量时会在栈中为这个变量分配内存空间,当超过变量的作用域后这块空间就会被自动释放掉。
堆内存:用来存放运行时创建的对象,比如通过new关键字创建出来的对象和数组;需要由Java虚拟机的自动垃圾回收器来管理。

设计模式

8. 谈谈MVC、MVP和MVVM,好在哪里,不好在哪里?

参考回答:
MVP的含义
Model:数据层,负责存储、检索、操纵数据。
View:UI层,显示数据,并向Presenter报告用户行为。
Presenter:作为View与Model交互的中间纽带,从Model拿数据,应用到UI层,管理UI的状态,响应用户的行为。
MVP相比于MVC的优势
分离了视图逻辑和业务逻辑,降低了耦合。
Activity只处理生命周期的任务,代码变得更加简洁。
视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性。
Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试。
把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM。
MVVM的含义:与MVP类似,利用数据绑定(Data Binding)、依赖属性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一个更加灵活高效的架构。
MVVM相比于MVP的优势:在常规的开发模式中,数据变化需要更新UI的时候,需要先获取UI控件的引用,然后再更新UI,获取用户的输入和操作也需要通过UI控件的引用,但在MVVM中,这些都是通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层,数据成为主导因素。这样MVVM层在业务逻辑处理中只要关心数据,不需要直接和UI打交道,在业务处理过程中简单方便很多。

9. 如何理解生产者消费者模型?

参考回答:
生产者消费者模型通过一个缓存队列,既解决了生产者和消费者之间强耦合的问题,又平衡了生产者和消费者的处理能力。

具体规则:生产者只在缓存区未满时进行生产,缓存区满时生产者进程被阻塞;消费者只在缓存区非空时进行消费,缓存区为空时消费者进程被阻塞;当消费者发现缓存区为空时会通知生产者生产;当生产者发现缓存区满时会通知消费者消费。

实现关键:synchronized保证对象只能被一个线程占用;wait()让当前线程进入等待状态,并释放它所持有的锁;notify()&notifyAll()唤醒一个(所有)正处于等待状态的线程

10. 是否能从Android中举几个例子说说用到了什么设计模式?

参考回答:
View事件分发:责任链模式
BitmapFactory加载图片:工厂模式
Adapter:适配器模式
Builder:建造者模式
Adpter.notifyDataSetChanged():观察者模式
Binder机制:代理模式

11. 装饰模式和代理模式有哪些区别?

参考回答:
使用目的不同:代理模式是给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用;装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能

构造不同:代理模式内部保持对目标对象的引用;装饰模式是通过构造函数传参的方式得到目标对象

12. 实现单例模式有几种方法?懒汉式中双层锁的目的是什么?两次判空的目的又是什么?

参考回答:
实现单例模式常见的两种方式:
(1)懒汉式:延迟加载,同时也要保证多线程环境下会产生多个single对象

public class Singleton {

    private Singleton() {}
    pprivate volatile static Singleton instance;//第一层锁:保证变量可见性

    public static Singleton getInstance() {
        if (single == null) {//第一次判空:无需每次都加锁,提高性能
            synchronized (Singleton.class) {//第二层锁:保证线程同步
                if (single == null) {//第二次判空:避免多线程同时执行getInstance()产生多个single对象
                    single = new Singleton();
                }
            }
        }
        return single;
    }
}

(2)饿汉式:在类加载初始化时就创建好一个静态的对象供外部使用

public class Singleton {

    private Singleton() {}
    private static Singleton single = new Singleton();

    public static Singleton getInstance() {
        return single;
    }
}

13. 谈谈了解的设计模式原则?

参考回答:

  • 单一职责原则:一个类只负责一个功能领域中的相应职责

  • 开放封闭原则:对扩展开放,对修改关闭

  • 依赖倒置原则:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程

  • 迪米特法则:应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用

  • 合成/聚合复用原则:要尽量使用合成/聚合,尽量不要使用继承


智能推荐



技术广度是深度的附属品

以上是关于面试相关之 JVM &设计模式的主要内容,如果未能解决你的问题,请参考以下文章

面向面试编程代码片段之GC

Day489&490&491.尚硅谷之高频重点面试题③ -面经

Day489&490&491.尚硅谷之高频重点面试题③ -面经

全面&详细的面试指南:Java虚拟机(JVM)篇 (附答案)

全面&详细的面试指南:Java虚拟机(JVM)篇 (附答案)

全面&详细的面试指南:Java虚拟机(JVM)篇 (附答案)