Java四种引用,Java堆和栈,热修复,ANR,设计模式
码仔,今天就给大家带来了《每日一道面试题》的第十一期:
01
JAVA的四种引用以及应用场景
GC在收集一个对象的时候会判断是否有引用指向对象,在JAVA中的引用主要有四种:
强引用(Strong Reference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
软引用(Soft Reference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
下面举个例子,假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题。
设计思路是:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了内存溢出的问题。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用(Weak Reference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用(Phantom Reference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用于检测对象是否已经从内存中删除,跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
虚引用的唯一目的是当对象被回收时收到一个系统通知。
02
谈谈热修复的原理
我们知道Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件, 而他们加载类的时候都需要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个数组——DexPathList,是用来存放dex文件,当BaseDexClassLoader通过调用findClass方法时,实际上就是遍历数组,找到相应的dex文件,找到,则直接将它return。而热修复的解决方法就是将新的dex添加到该集合中,并且是在旧的dex的前面,所以就会优先被取出来并且return返回。
03
ANR异常的产生条件及解决方案
ANR是什么? ANR全称:Application Not Responding,也就是应用程序无响应. 简单来说,就是应用跑着跑着,突然duang,界面卡住了,无法响应用户的操作如触摸事件等.
原因 Android系统中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会检测App的响应时间. 如果App在特定时间无法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR.
ANR的产生需要同时满足三个条件
主线程:只有应用程序进程的主线程(UI线程)响应超时才会产生ANR
超时时间:产生ANR的上下文不同,超时时间也不同,但只要超过这个时间上限没有响应就会产生ANR
输入事件/特定操作:输入事件是指按键、触屏等设备输入事件,特定操作是指BroadcastReceiver和Service的生命周期中的各个函数调用
解决方案: 总结为一句话,即不要在主线程(UI线程)里面做繁重的操作
04
Java中堆和栈有什么区别
堆与栈的区别很明显:
栈内存存储的是局部变量而堆内存存储的是实体;
栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。
堆、栈说明:
栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。
堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。
05
谈谈对生成器模式的理解
生成器模式:将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。
何时使用
当系统准备为用户提供一个内部结构复杂的对象,而且在构造方法中编写创建该对象的代码无法满足用户需求时,就可以使用生成器模式老构造这样的对象。
当某些系统要求对象的构造过程必须独立于创建该对象的类时。
优点
生成器模式将对象的构造过程封装在具体的生成器中,用户使用不同的具体生成器就可以得到该对象的不同表示。
生成器模式将对象的构造过程从创建该对象的类中分离出来,使用户无须了解该对象的具体组件。
可以更加精细有效的控制对象的构造过程。生成器将对象的构造过程分解成若干步骤,这就是程序可以更加精细,有效的控制整个对象的构造。
生成器模式将对象的构造过程与创建该对象类解耦,是对象的创建更加灵活有弹性。
当增加新的具体的生成器是,不必修改指挥者的代码,即该模式满足开-闭原则。
模式的重心在于分离构建算法和具体的构造实现,从而使构建算法可以重用。
比如我们要得到一个日期,可以有不同的格式,然后我们就使用不同的生成器来实现。
首先是这个类(产品):
//产品
public class MyDate {
String date;
}
然后就是抽象生成器,描述生成器的行为:
//抽象生成器
public interface IDateBuilder {
IDateBuilder buildDate(int y,int m,int d);
String date();
}
接下来是具体生成器,一个以“-”分割年月日,另一个使用空格:
/具体生成器
public class DateBuilder1 implements IDateBuilder{
private MyDate myDate;
public DateBuilder1(MyDate myDate){
this.myDate = myDate;
}
public IDateBuilder buildDate(int y, int m, int d) {
myDate.date = y+"-"+m+"-"+d;
return this;
}
public String date() {
return myDate.date;
}
}
//具体生成器
public class DateBuilder2 implements IDateBuilder{
private MyDate myDate;
public DateBuilder2(MyDate myDate){
this.myDate = myDate;
}
public IDateBuilder buildDate(int y, int m, int d) {
myDate.date = y+" "+m+" "+d;
return this;
}
public String date() {
return myDate.date;
}
}
接下来是指挥官,向用户提供具体的生成器:
//指挥者
public class Derector {
private IDateBuilder builder;
public Derector(IDateBuilder builder){
this.builder = builder;
}
public String getDate(int y,int m,int d){
builder.buildDate(y, m, d);
return builder.date();
}
}
最后使用
public class MainGenerate {
public static void main(String args[]){
MyDate date = new MyDate();
IDateBuilder builder;
builder = new DateBuilder1(date).buildDate(2066, 3, 5);
System.out.println(builder.date());
builder = new DateBuilder2(date).buildDate(2066, 3, 5);
System.out.println(builder.date());
}
}
使用不同生成器,可以使原有产品表现得有点不一样。
06
结束语
如果你有好的答案可以提交至:
https://github.com/codeegginterviewgroup/CodeEggDailyInterview
往期文章:
专属社群:
今日问题:
杭州上海的小伙伴有被台风吹懵吗?