注册

Android 面试准备进行曲-Java基础篇

虚拟机 基础

jvm 参考文章

JVM内存管理

JVM执行Java程序的过程:Java源代码文件(.java)会被Java编译器编译为字节码文件(.class),然后JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。

1.webp

运行时数据区被分为 线程私有数据区 和 线程共享数据区 两大类:

线程私有数据区包含:程序计数器、虚拟机栈、本地方法栈 线程共享数据区包含:Java堆、方法区(内部包含常量池)

线程私有数据区包含:

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

线程共享数据区包含:

  • Java堆:用于存放几乎所有的对象实例和数组;是垃圾收集器管理的主要区域,也被称做“GC堆”;是Java虚拟机所管理的内存中最大的一块

  • 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放

Java堆和栈的区别

  • 堆内存 用于存储Java中的对象和数组,当我们new一个对象或者创建一个数组的时候,就会在堆内存中开辟一段空间给它,用于存放。特点: 先进先出,后进后出。堆可以动态地分配内存大小,由于要在运行时动态分配内存,存取速度较慢。

  • 栈内存

主要是用来执行程序用的,比如:基本类型的变量和对象的引用变量。特点:先进后出,后进先出,存取速度比堆要快,仅次于寄存器,栈数据可以共享,但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性

垃圾回收机制/ 回收算法

判定对象可回收有两种方法:

  • 引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。然而在主流的Java虚拟机里未选用引用计数算法来管理内存,主要原因是它难以解决对象之间相互循环引用的问题,所以出现了另一种对象存活判定算法。

  • 可达性分析法:通过一系列被称为『GC Roots』的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。其中可作为GC Roots的对象:虚拟机栈中引用的对象,主要是指栈帧中的本地变量、本地方法栈中Native方法引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象

回收算法

分代收集算法:是当前商业虚拟机都采用的一种算法,根据对象存活周期的不同,将Java堆划分为新生代和老年代,并根据各个年代的特点采用最适当的收集算法。

  • 新生代:多数对象死去,少量存活。使用『复制算法』,只需复制少量存活对象即可。

    • 复制算法:把可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用尽后,把还存活着的对象『复制』到另外一块上面,再将这一块内存空间一次清理掉。
  • 老年代:对象存活率高。使用『标记—清理算法』或者『标记—整理算法』,只需标记较少的回收对象即可。

    • 标记-清除算法:首先『标记』出所有需要回收的对象,然后统一『清除』所有被标记的对象。
    • 标记-整理算法:首先『标记』出所有需要回收的对象,然后进行『整理』,使得存活的对象都向一端移动,最后直接清理掉端边界以外的内存。

参考文章

Java基础

Java 引用类型

  • 强引用(StrongReference):具有强引用的对象不会被GC;即便内存空间不足,JVM宁愿抛出OutOfMemoryError使程序异常终止,也不会随意回收具有强引用的对象。

  • 软引用(SoftReference):具有软引用的对象,会在内存空间不足的时候被GC;软引用常用来实现内存敏感的高速缓存。

  • 弱引用(WeakReference):被弱引用关联的对象,无论当前内存是否足够都会被GC;强度比软引用更弱,常用于描述非必需对象;常用于解决内存泄漏的问题(Handle 中Context 部分)

  • 虚引用(PhantomReference):仅持有虚引用的对象,在任何时候都可能被GC;常用于跟踪对象被GC回收的活动;必须和引用队列 (ReferenceQueue)联合使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

类加载机制

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

  • 加载(Loading):通过类的全限定名来获取定义此类的二进制字节流;将该二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构,该数据存储数据结构由虚拟机实现自行定义;在内存中生成一个代表这个类的java.lang.Class对象,它将作为程序访问方法区中的这些类型数据的外部接口

  • 验证(Verification):确保Class文件的字节流中包含的信息符合当前虚拟机的要求,包括文件格式验证、元数据验证、字节码验证和符号引用验证

  • 准备(Preparation):为类变量分配内存,因为这里的变量是由方法区分配内存的,所以仅包括类变量而不包括实例变量,后者将会在对象实例化时随着对象一起分配在Java堆中;设置类变量初始值,通常情况下零值

  • 解析(Resolution):虚拟机将常量池内的符号引用替换为直接引用的过程

  • 初始化(Initialization):是类加载过程的最后一步,会开始真正执行类中定义的Java字节码。而之前的类加载过程中,除了在『加载』阶段用户应用程序可通过自定义类加载器参与之外,其余阶段均由虚拟机主导和控制

内存模型

22.webp 主内存是所有变量的存储位置,每条线程都有自己的工作内存,用于保存被该线程使用到的变量的主内存副本拷贝。为了获取更好的运行速度,虚拟机可能会让工作内存优先存储于寄存器和高速缓存中。

并发过程中的原子性 时序性

  • 原子性

可直接保证的原子性变量操作有:read、load、assign、use、store和write,因此可认为基本数据类型的访问读写是具备原子性的。

若需要保证更大范围的原子性,可通过更高层次的字节码指令monitorenter和monitorexit来隐式地使用lock和unlock这两个操作,反映到Java代码中就是同步代码块synchronized关键字。

  • 可见性(一个线程修改了共享变量的值,其他线程能够立即得知这个修改)

通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现。

提供三个关键字保证可见性:volatile能保证新值能立即同步到主内存,且每次使用前立即从主内存刷新;synchronized对一个变量执行unlock操作之前可以先把此变量同步回主内存中;被final修饰的字段在构造器中一旦初始化完成且构造器没有把this的引用传递出去,就可以在其他线程中就能看见final字段的值。

  • 有序性(按照指令顺序执行)

如果在本线程内观察,所有的操作都是有序的,指“线程内表现为串行的语义”;如果在一个线程中观察另一个线程,所有的操作都是无序的,指“指令重排序”现象和“工作内存与主内存同步延迟”现象。

提供两个关键字保证有序性:volatile 本身就包含了禁止指令重排序的语义;synchronized保证一个变量在同一个时刻只允许一条线程对其进行lock操作,使得持有同一个锁的两个同步块只能串行地进入。

设计模式 (使用过的)

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

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

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

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

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

延伸:Android 中源码使用的设计模式,自己使用过的设计模式

View事件分发: 责任链模式 BitmapFactory加载图片: 工厂模式

ListAdapter: 适配器模式
DialogBuilder: 建造者模式 Adapter.notifyDataSetChanged(): 观察者模式 Binder机制: 代理模式

平时经常使用的 设计模式

单例模式

初级版

public class Singleton {
private static Singleton instance;
private Singleton (){}

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
复制代码

进阶版

public class Singleton {
private volatile static Singleton singleton;//代码1
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {//代码2
synchronized (Singleton.class) {
if (singleton == null) {//代码3
singleton = new Singleton();//代码4
}
}
}
return singleton;
}
}
复制代码

  在多线程中 两个线程可能同时进入代码2, synchronize保证只有一个线程能进入下面的代码,   此时一个线程A进入一个线程B在外等待, 当线程A完成代码3 和代码4之后, 线程B进入synchronized下面的方法, 线程B在代码3的时候判断不过,从而保证了多线程下 单例模式的线程安全,   另外要慎用单例模式,因为单例模式一旦初始化后 只有进程退出才有可能被回收,如果一个对象不经常被使用,尽量不要使用单例,否则为了几次使用,一直让单例存在占用内存

优点:

  • 内存中只存在一个对象,节省了系统资源。
  • 避免对资源的多重占用,例如一个文件操作,由于只有一个实例存在内存中,避免对同一资源文件的同时操作。

缺点:

  • 单例对象如果持有Context,那么很容易引发内存泄露。
  • 单例模式一般没有接口,扩展很困难,若要扩展,只能修改代码来实现。

Builder 模式

参考文章

33.webp 具体的产品类

public class Computer {
private String mCPU;
private String mMemory;
private String mHD;

public void setCPU(String CPU) {
mCPU = CPU;
}

public void setMemory(String memory) {
mMemory = memory;
}

public void setHD(String HD) {
mHD = HD;
}
}
复制代码

抽象建造者

public abstract class Builder {
public abstract void buildCPU(String cpu);//组装CPU

public abstract void buildMemory(String memory);//组装内存

public abstract void buildHD(String hd);//组装硬盘

public abstract Computer create();//返回组装好的电脑
}
复制代码

创建者实现类

public class ConcreteBuilder extends Builder {
//创建产品实例
private Computer mComputer = new Computer();

@Override
public void buildCPU(String cpu) {//组装CPU
mComputer.setCPU(cpu);
}

@Override
public void buildMemory(String memory) {//组装内存
mComputer.setMemory(memory);
}

@Override
public void buildHD(String hd) {//组装硬盘
mComputer.setHD(hd);
}

@Override
public Computer create() {//返回组装好的电脑
return mComputer;
}
}
复制代码

调用者

public class Director {
private Builder mBuild = null;

public Director(Builder build) {
this.mBuild = build;
}

//指挥装机人员组装电脑
public void Construct(String cpu, String memory, String hd) {
mBuild.buildCPU(cpu);
mBuild.buildMemory(memory);
mBuild.buildHD(hd);
}
}
复制代码

调用


Director direcror = new Director(new ConcreteBuilder());//创建指挥者实例,并分配相应的建造者,(老板分配任务)
direcror.Construct("i7-6700", "三星DDR4", "希捷1T");//组装电脑
复制代码

Builder 模式 优缺点

优点

  • 封装性良好,隐藏内部构建细节。
  • 易于解耦,将产品本身与产品创建过程进行解耦,可以使用相同的创建过程来得到不同的产品。也就说细节依赖抽象。
  • 易于扩展,具体的建造者类之间相互独立,增加新的具体建造者无需修改原有类库的代码。
  • 易于精确控制对象的创建,由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。

缺点

  • 产生多余的Build对象以及Dirextor类。
  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制

源码中使用的 比如: Dialog.Builder

工厂模式

44.webp

抽象产品类

    public abstract class Product {
public abstract void show();
}
复制代码

具体产品类

    public class ProductA extends Product {
@Override
public void show() {
System.out.println("product A");
}
}
//具体产品类B
public class ProductB extends Product {
@Override
public void show() {
System.out.println("product B");
}
}
复制代码

创建抽象工厂类

	 //抽象工厂类
public abstract class Factory {
public abstract Product create();
}
复制代码

创建具体工厂类,继承抽象工厂类

	public class FactoryA extends Factory {
@Override
public Product create() {
return new ProductA();//创建ProductA
}
}
//具体工厂类B
public class FactoryB extends Factory {
@Override
public Product create() {
return new ProductB();//创建ProductB
}
}
复制代码

测试代码

		Factory factoryA = new FactoryA();
Product productA = factoryA.create();
productA.show();
//产品B
Factory factoryB = new FactoryB();
Product productB = factoryB.create();
productB.show();
复制代码

优点:

  • 符合开放封闭原则。新增产品时,只需增加相应的具体产品类和相应的工厂子类即可。
  • 符合单一职责原则。每个具体工厂类只负责创建对应的产品。

缺点:

  • 增加新产品时,还需增加相应的工厂类,系统类的个数将成对增加,增加了系统的复杂度和性能开销。

源码中 使用的 比如 ThreadFactory

观察者模式

参考文章

含义: 定义对象间的一种一个对多的依赖关系,当一个对象的状态发送改变时,所以依赖于它的对象都得到通知并被自动更新。

55.webp

备注:

  • Subject(抽象主题):又叫抽象被观察者,把所有观察者对象的引用保存到一个集合里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

  • ConcreteSubject(具体主题):又叫具体被观察者,将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。

  • Observer (抽象观察者):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。

  • ConcrereObserver(具体观察者):实现抽象观察者定义的更新接口,当得到主题更改通知时更新自身的状态。

代码实现

抽象观察者

	public interface Observer {//抽象观察者
public void update(String message);//更新方法
}
复制代码

具体观察者

public class Boy implements Observer {

private String name;//名字
public Boy(String name) {
this.name = name;
}
@Override
public void update(String message) {//男孩的具体反应
System.out.println(name + ",收到了信息:" + message+"屁颠颠的去取快递.");
}
}
复制代码

创建抽象主题

	public interface  Observable {//抽象被观察者
void add(Observer observer);//添加观察者

void remove(Observer observer);//删除观察者

void notify(String message);//通知观察者
}
复制代码

创建具体主题

	public class Postman implements  Observable{//快递员

private List<Observer> personList = new ArrayList<Observer>();//保存收件人(观察者)的信息
@Override
public void add(Observer observer) {//添加收件人
personList.add(observer);
}

@Override
public void remove(Observer observer) {//移除收件人
personList.remove(observer);

}

@Override
public void notify(String message) {//逐一通知收件人(观察者)
for (Observer observer : personList) {
observer.update(message);
}
}
}
复制代码

测试代码

	Observable postman=new Postman();

Observer boy1=new Boy("路飞");
Observer boy2=new Boy("乔巴");
postman.notify("快递到了,请下楼领取.");
复制代码

优点:

  • 解除观察者与主题之间的耦合。让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
  • 易于扩展,对同一主题新增观察者时无需修改原有代码。

缺点:

  • 使用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。
  • 可能会引起多余的数据通知。

Android 源码中的观察者模式:Listener

其他设计模式

由于本人涉猎较少,有些只能说简单了解。这里分享一个 不错的 系列博客,感谢前人栽树。

设计模式系列教程 !!!

源码设计

接口和抽象类

  • 抽象类可以提供成员方法的实现细节,而接口中只能存在 public 抽象方法,没有实现,(JDK8以后可以有)
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的;
  • 接口的成员变量只能是静态常量,没有构造函数,也没有代码块,但抽象类都可以有。
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口;

抽象类访问速度比接口速度要快,因为接口需要时间去寻找在类中具体实现的方法;

  • 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。

所以:抽象类强调的是重用,接口强调的是解耦。

0 个评论

要回复文章请先登录注册