面试_java基础


1.基本数据类型

1.1 String、StringBuilder和StringBuffer的区别

String:不可变类型,字符串常量,拼接时每次都会创建新的对象

StringBuilder:可变类型,字符串变量,线程不安全的,拼接时在原对象上操作

StringBuffer:可变类型,字符串变量,线程安全的,拼接时在原对象上操作

效率:StringBuilder > StringBuffer > String

1.2 String为什么要被设计成常量

性能:

  1. 创建一个String对象时,如果常量池中已经有该字符串,则不会创建一个新的对象,而是直接引用已存在的对象。

  2. 缓存hashcode,Java中String对象的hashcode会被频繁的调用,字符串的不变形保证了hashcode的唯一性,因此不必每次重新计算hashcode。

安全:

  1. String被许多类用来当做参数,例如url、文件路径等,如果是变量,会引起各种安全隐患。

1.3 String.length()的原理

计算Char[]数组的长度

1.2 ==、equals()和hashcode()

==:

1.类型:操作符

2.作用:比较的对象是基本数据类型时,==比较的是值;是引用数据类型时,比较的是对象存储的内存地址;

equals:

1.类型:Object类的方法

2.作用:如果没有重写equals方法,比较的是对象存储的内存地址;如果重写了equals方法,比较的是对象的内容;

hashCode

1.类型:Object类的方法

2.作用:对象存储的内存地址的hash值,用于散列结构中存储位置的分配

规则:

1.equals相等,则hashcode也一定相等;

2.hashcode相等,equals不一定相等;

3.如果重写了equals方法则必须重写hashcode方法;

1.3 如何重写equals方法,为什么还要重写hashcode方法

重写equals方法:

  1. ==比较两个对象存储的内存地址,是否是同一个对象的应用

  2. instanceOf,比较两个对象类型是否相等

  3. 逐一比较两个对象的属性是否相等;

重写了equals方法为什么还要重写hashcode方法:

因为Hash比equals方法的开销要小,速度更快,所以在涉及到hashcode的容器中(比如HashSet),判断自己是否持有该对象时,会先检查hashCode是否相等,如果hashCode不相等,就会直接认为不相等,并存入容器中,不会再调用equals进行比较。

如果不重写hashcode会导致即使该对象已经存在HashSet中,但是因为hashCode不同,还会再次被存入。

因此要重写hashCode保证:如果equals相等,则hashcode也相等。

1.4 深拷贝与浅拷贝

深拷贝:创建一个新对象,值与原对象相同;

浅拷贝:引用指向同一个对象

谈一谈Java成员变量,局部变量和静态变量的创建和回收时机?

局部变量:生命周期跟随方法;

成员变量:生命周期跟随对象;

静态变量:声明周期跟随类,类的首次加载时创建,类被销毁时被回收。

2.面向对象

1.1 抽象类和接口的区别

相同点:

  1. 都不可以被实例化,只有实现了所有抽象方法的抽象类子类或接口实现类才可以被实例化

不同点:

语法层面上:

  1. 成员变量上:接口只能是public static final的静态常量,而抽象类可以是常量也可以是变量;
  2. 构造器上:接口不能含有构造器、静态代码块和静态方法,而抽象类都可以有;
  3. 方法上:jdk8以前,接口只能有抽象方法,而抽象类可以有默认方法;jdk8以后接口也可以用default声明默认方法;
  4. 一个类只能继承一个抽象类,却能实现多个接口;

性能上:

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

设计层面上:

  1. 抽象类是对是事物的抽象,接口更多的是对行为的抽象;抽象类侧重于代码复用(模板模式),接口侧重于解耦(策略模式)。

1.2 多态

定义:函数调用可以根据对象的不同而表现出不同的行为方式。

技术:动态绑定,根据运行时对象变量的实际类型,执行其相应的方法。

作用:解耦

实现方式:接口实现、继承重写实现、方法重载

1.3 final、static和synchronized关键字

final:

修饰类:类不可被继承

修饰方法:不可被重写

修饰变量:不可改变

static:

修饰内部类:不需要外部类实例,当做普通类使用

修饰方法:静态方法,属于类而不属于具体的对象

修饰代码块:静态代码块,属于类而不属于具体的对象

修饰变量:静态变量,属于类而不属于具体的对象,类的首次加载而初始化,内存中只会存在一个副本

synchronized:

多线程互斥问题

修饰方法:同步方法,解决类成员变量的访问冲突

修饰代码块:同步代码块,锁对象是this,当只有部分地方需要加锁时,就使用同步代码块。

1.4 方法的重写和重载

重写:子类重写父类的方法

权限修饰符:重写方法的权限修饰符不小于被重写的方法;

返回值:父类void子类也必须是void,父类是基本类型子类也必须是基本类型,父类是引用类型子类必须是引用类或其子类;

方法名:必须相同

参数列表:必须相同

异常:子类的异常类型不大于父类

子类不能重写父类的静态方法

重载:同一个类中允许一个以上的同名方法,参数列表不同(两同一不同)

重载跟权限修饰符、返回值类型没有关系

1.5 静态内部类和非静态内部类的区别

  1. 创建方式不同; A a = new A(); A.B b = new A.B(); A.C c = a.new C();
  2. 静态内部类可以用静态成员和方法,非静态内部类不能有静态成员和方法;在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。
  3. 静态内部类只能访问外部类的静态成员,非静态内部类可以访问外部类的所有成员(方法和属性);

3.集合

3.1 谈谈List(列表)、Set(集合)和Map(映射)的区别

List:有序可重复的,分为ArrayList、LinkedList(按照插入顺序访问);

Set:无序不可重复的,主要实现为HashSet,基于hashcode决定存储位置;

Map:键值对存储,key的底层实现是Set,无序不可重复的,value的底层实现是Collection,可重复的。主要实现是HashMap,如果按照插入顺序遍历是LinkedHashMap。

3.2 谈谈ArrayList和LinkedList的区别

ArrayList:基于数组的数据结构,查询时间复杂度是O(1),因此适用于频繁查询的场景;

LinkedList:基于链表的数据结构,插入和删除的时间复杂度是O(1),因此适用于频繁插入和删除的场景;

3.3 说一说ArrayList的扩容机制

jdk7以前,创建一个ArrayList对象时,底层就创建了一个长度为10的Object[]数组,添加的元素超过10时,新建一个数组长度为原来的1.5倍,同时将原数组中的内容复制到新数组中。

jdk8开始,创建一个ArrayList对象时,创建了一个空数组,并没有创建长度为10的数组,第一次添加元素时才创建了长度为10的数组,后续扩容操作一样。

3.4 HashMap的实现原理

jdk7以前,底层用Entry[]数组实现,首次创建HashMap对象时构造器就创建了一个长度为16的数组,扩容时长度为新数组的2倍,将原数组的内容拷贝到新数组中。添加Entry时首先调用key所在类的hashcode()方法计算哈希值,再通过特定算法((n - 1)& hash)计算出在待插入数组中的位置。

? 如果该位置上没有元素,则直接插入到数组中;

? 如果该位置上有元素,则与该位置上的元素或链表中的每个元素进行hashcode比较:

? 如果hashcode不同,则直接插入至该链表中(七上八下,七是插到链表头部,八是插到链表尾部)

? 如果hashcode相同,则继续调用key所在类的equals方法:

? 如果equals不同,则插入至链表中;

? 如果equals相同,则替换原Entry的value值;

jdh8的区别:

  1. 底层数组类型为Node[];
  2. 数组只有在首个Entry put的时候才创建;
  3. 为了提高查找遍历的效率,当链表长度超过8且数组长度超过64时将链表改为红黑树。

开放寻址法

1.. 扩容算法, 时间复杂度是多少, 你了解到它实现的原理吗?

2.. 是先扩容还是优先树化;

大于8,链表-》红黑树;

小于6,红黑树-》链表;

先扩容,数组长度大于64时才树形化

因为链表过长而数组过短,会经常发生hash冲突,这个时候树形化其实是治标不治本,因为引起链表过长的根本原因是数组过短。执行树形化之前,会先检查数组长度,如果长度小于 64,则对数组进行扩容,而不是进行树形化。

3.. 有听说过 ArrayMap吗, 为什么ArrayMap会节省内存; (TIPS 稀疏数组, 包装类占据内存);

4.. 和LinkedHashMap有什么区别, Linked怎么做到的访问顺序问题;

HashMap:无序的

LinkedHashMap:访问顺序=添加顺序

5.. LRUCache了解多少?

最近最少使用的缓存算法,使用LinkedHashMap实现,每get了一次就把他添加到队尾,当超出size时就从队头移除。

BitmapLruCache

6.. 对 ConcurrentHashMap 了解吗?

线程安全的HashMap

public class BitmapLruCache extends LruCache {
    public BitmapLruCache(int size) {
        super(size);
    }
    
    // 计算当前节点的内存大小
    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getByteCount() / 1024;
    }
    
    // 当节点移除时该方法会回调,根据业务逻辑判断是否需要重写该方法。
    @Override
    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
        super.entryRemoved(evicted, key, oldValue, newValue);
    }
}

3.5 HashMap和HashTable的区别

HashMap:线程不安全的,key和value可以为null;

HashTable:线程安全的,key和value不能为null。

3.6 LinkedHashMap的实现原理及使用场景

使用双向链表保证遍历顺序=插入顺序

LRUCache

3.7 谈谈对于ConcurrentHashMap的理解

线程安全的,采用分段锁实现。

4.Exception

4.1 Error和Exception

Error:

不可预料的错误,出现这种错误后会导致JVM不可用或不可恢复的情况。比如OutOfMemoryError(内存不足)、StackOverflowError(栈溢出)等;

Exception:

可预料的异常,根据编译期是否可检查出来又分为编译异常(CheckedException)和运行时(RunTimeException)异常,常见的编译异常有IOException(FileNotFoundException)、ClassNotFoundException,常见的运行时异常有NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException、NumberFormatException等。

5.反射

5.1 简述反射机制及应用场景

反射机制是指在运行态时,对于任意一个类,都可以知道这个类的属性和方法,对于任意一个对象,都能够调用它的任意一个属性和方法。这种动态获取的信息和动态调用对象方法的功能被称为反射机制。

应用场景:

反编译、与注解相结合的框架(Retrofit)、动态生成类框架(标记语言转换成类Gson)、单纯的反射机制应用框架(EventBus)

常用API:

Class class = Class.forName("ClassName");

Field[] fields = class.getFields;

Constructor<>[] constructors = class.getConstructors();

Method method = class.getMethod("methodName");/getDeclaredMethod("methodName", 参数类名);

method.invoke(对象,参数);

6.IO

6.1 IO流分为哪几种,说说BIO、NIO和AIO

根据方向分为:输入流(InputStream)和输出流(OutputStream);

根据操作单元分为:字节流和字符流(Reader和Writer);

根据角色分为:节点流和处理流

BIO:传统IO流,同步阻塞流(Block IO),实现模式是一个连接一个线程,即客户端有连接请求时服务端就需要启动一个线程并进行处理,适用于并发小场景;

NIO:同步非阻塞流(Nonblocl IO),实现模式是一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮训到连接有I/O请求时才启动一个线程进行处理。适用于并发较高但连接短的场景,例如聊天服务器。

AIO:异步非阻塞,实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成流再通知服务器应用去启动线程进行处理。适用于并发高且连接长的场景,例如相册服务器。

7.泛型

7.1 泛型擦除及其局限性

定义:使用泛型时加上的类型参数,会在编译器编译的时候去掉。这个过程就称为类型擦除。

局限性:

  1. 泛型的类型变量不能是基本数据类型;

  2. 不能在静态变量和静态方法中使用泛型;

  3. 无法在运行时检查其类型;例如object instanceOf ArrayList;

8.注解

8.1 对java注解的理解

作用:对代码加标识

主要分为三类:

  • Source:仅存在于.java文件中,编译成.class文件就显示了,作用是提示开发者按照注解规范写代码。例如@OverRide

  • Class:在前期编译器的流程中会被处理成.class内容,作用是自动生成.class文件,例如GreenDao;

  • Runtime:编译成.class文件后依然以注解的方式存在,作用是在运行期通过反射做一些辅助性工作,例如xUtils

9.多线程

9.1 java使用多线程的方式

  1. 继承Thread类,实现run()方法;
  2. 实现Runnable接口,实现run()方法;
  3. 实现Callable接口,实现call()方法,通过new FutureTask(Callable callable)、new Thread(FutureTask futuretask)来创建,带返回值;
  4. 使用ExecuteService线程池来创建,ExecuteService service = Executors.newFixedThreadPool;service.execute(Thread thread)/service.submit(Callable callable); service.shutdown()

9.2 线程的几种状态

NEW:创建新线程,但还没调用start方法;

RUNNABLE:运行态,java中的就绪状态和运行状态统称为运行态,调用start()方法 就绪后或获得了CPU的执行权;

BLOCKED:阻塞于锁的状态,例如:object.wait();

WAITING:等待状态,调用:Object#wait() Object.wait with no timeout、join() Thread.join with no timeout

TIMED_WAITING:超时等待态,例如调用:sleep(long time)/wait(long time)/join(long time)

TERMINATED:销毁态,线程执行完毕后

image-20220307095317163

9.3 实现线程同步的方式

  1. volatile变量;

  2. synchronized关键字,同步方法或同步代码块;

  3. ReentrantLock:手动获取或释放锁;

  4. Semaphore:信号量-通知同时访问的线程数量

  5. BlockingQueue:阻塞队列,ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue

9.4 死锁

定义:多个线程因竞争资源而造成的一种互相等待的现象,若无外力作用,这种线程都将无法向前推进。

产生的必要条件:互斥条件(某一资源同一时间只能被同一个线程占有)、不可剥夺条件、请求和保持(请求其他资源时,已占有的原有资源不放弃)、循环等待

避免死锁:加锁顺序(保持同样的请求锁顺序)、超时放弃、死锁检测

9.5 线程阻塞的原因

线程组赛状态:线程因为某种原因放弃了CPU执行权,暂时停止运行的状态;

原因:

  1. 线程中等待其他锁;
  2. 自身调用了Object.wait()方法,线程执行了一个对象的wait()方法,进入阻塞状态,只有等到其他线程执行了该对象的notify()或notifyAll()方法,才可能将其唤醒;
  3. 自身调用sleep()方法;
  4. 线程执行I/O操作或进行远程通信时,会因为等待相关的资源而进入阻塞状态。;

9.6 start()和run()方法的区别

start():启动线程时调用,调用了start方法后线程即进入就绪态;

run():线程体,线程要执行的内容,执行完run方法后线程终止。

9.7 Synchronized和volatile的区别

相同点:都可以实现线程同步

不同点:

  1. volatile保证了的可见性和有序性,禁止了指令的重排序,synchronized保证了的可见性、有序性和原子性;
  2. volatile只能修饰变量,synchronized可以修饰方法和代码块;
  3. volatile不会造成线程阻塞,synchronized可能造成线程阻塞;

可见性:可见性是指当一个线程修改了共享变量后,其他线程能够立即得知这个修改。

原子性:原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败

有序性:指令的操作有序

9.8 如何保证线程安全?

线程安全的根本原因是:进程间内存隔离、线程间内存共享,如何保证主要有两个方面:

线程间内存共享:线程同步,问题回到了如何实现线程同步,

线程间隔离:ThreadLocal

9.9 ThreadLocal用法和原理

作用:保存线程的共享数据,对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

用法:ThreadLocal.get、ThreadLocal.set、ThreadLocal.remove

原理:底层使用ThreadLocalMap来实现,key是ThreadLocal对象,value为数据。

private static final ThreadLocal threadLocal = new treadLocal(){
    /**
     * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
     */
    @Override
    protected Object initialValue()
    {
      System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!");
      return null;
    }
};

我们调用ThreadLocal.get方法时,实际上是从当前线程中获取ThreadLocalMap<ThreadLocal, Object>,然后根据当前ThreadLocal获取当前线程共享变量Object。

ThreadLocalMap对ThreadLocal弱引用的原因?

线程没有结束,但是ThreadLocal对象被置为null,这时候ThreadLocalMap中就会存在一条key为null的脏entry,如果使用强应用则脏entry不会被gc回收,但是value永远无法被访问到,因此造成内存泄漏。改为弱应用后尽管也会出现内存泄漏问题,但是ThreadLocal的生命周期里(set/get/remove方法里)会对key为null的脏entry进行处理,尽可能的保证不出现内存泄漏问题

9.10 notify()和notifyAll()方法

共同点:都是从锁对象的等待池中唤醒线程进入锁池,竞争锁对象,获得执行权。

notify():从等待池中唤醒仍一线程;

notifyAll():唤醒等待池的全部线程。

等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁。

锁池:只有获取了对象的锁,线程才能执行对象的 synchronized 代码,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待

9.11 什么是线程池,如何创建线程池?

定义:一组线程的集合

作用:避免了频繁创建和销毁线程的开销

如何创建:

ExecuteService service = new Executors.newFixedThreadPool (int threadNums);

service.excute(Thread thread)/service.submit(Callable callable)

  • newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
  • newSingleThreadExecutor:创建一个单线程的线程池,可保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
  • newScheduledThreadPool:创建一个周期性的线程池,支持定时及周期性执行任务。
  • newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

9.12 谈一谈java线程常见的几种锁

  • synchronized 控制多个线程对共享数据的访问,只有拿到锁的线程才能访问共享数据

  • ReentrantLock 可重入锁,这个锁可以被线程多次重复进入进行获取操作,高并发场景下使用ReentrantLock

  • Semaphore

  • AtomicInteger

9.13 谈谈sleep()和wait()方法

  1. sleep()是Thread类的方法,wait()是Object类的方法;
  2. wait()方法只能在synchronized修饰的方法或代码块中使用,sleep不需要;
  3. wait()进入等待期会释放锁,sleep()不会释放锁;
  4. wait()进入WAITING态后可以被Object.notify()和notifyAll()方法唤醒,sleep()则不能被唤醒,只能等睡眠时间到。

9.14 什么是悲观锁和乐观锁

悲观锁:默认为某线程在操作共享数据时,一定会出现同一时刻其他线程也在修改此数据。因此在能够访问到这个数据时就加锁,不让其他线程来操作。加锁在逻辑处理之前,典型就是synchronized关键字;

乐观锁:默认为某线程在操作共享数据时,不会出现同一时刻其他线程也在修改此数据。只在逻辑处理完写入内存时,检测此资源是否被修改。典型就是Atomic之类的原子操作类。

9.15 BlockingQueue

作用:解决并发场景下的线程同步问题

场景:适用生产者-消费者模型,不需要自己去处理线程阻塞和唤醒逻辑。

原理:生产者线程和消费者线程都从同一个队列中取数据,当队列满时,所有生产者线程都会被阻塞挂起,当队列空时,所有消费者线程都会被阻塞挂起。

子类:

  • ArrayBlockingQueue:基于数组的阻塞队列实现,生产者和消费者使用同一把锁,不可同时存取;
  • LinkedBlockingQueue:基于链表的阻塞队列实现,生产者和消费者使用不同的锁,可同时存取;
  • PriorityBlockingQueue:基于数组的阻塞队列实现,可根据存入的优先级进行排序,保证按照优先级来消费。

9.16 谈一谈java线程安全的集合

  1. List中的Vector:全部方法加 synchronized 的 ArrayList

  2. Map中的HashTable(全部方法加 synchronized 的 HashMap)、ConcurrentHashMap(java.util.concurrent)

  3. Collections.SynchronizedXXX类

  4. CopyOnWriteXXXX:加了写锁,写的时候锁住的整个对象,读则可以并发执行。// CopyOnWriteArrayList

9.17 Java中为什么会出现Atomic类?试分析它的原理和缺点?

原因:synchronize是重量级锁,属于悲观锁类型,效率低,Atomic类是轻量级锁,属于乐观锁,在并发量不大的场景下效率比较高。

原理:CAS(比较和交换),涉及到(V,O,N),V是内存中真正的值,O是加载到线程中预期值,N是计算后的目标结果值,当计算出目标结果值时比较V和O是否相等,不相等则代表V被其他线程改过,那么将V重新赋值给O,然后重新计算目标值,再次重复以上步骤,这种称为自旋操作。

缺点:

  • 存在ABA问题,即V如果被多次修改,最终还是之前的V,那么仅仅比较V和O是无法知道V被其他线程修改过;
  • 只能针对一个共享变量进行原子操作;
  • 在并发比较大的场景下,自旋操作等待的时间比较长,性能会大幅降低。

9.18 说说ThreadLocal的使用场景?与Synchronized相比有什么特性?

ThreadLocal:线程间内存隔离,每个线程只能访问自己的内存变量,线程间互不影响,底层使用ThreadLocalMap实现,key为ThreadLocal的弱应用,v为值;

synchronize:线程间内存共享,使用锁实现,对于共享数据同一时刻只能由一个线程来操作

10.JVM

10.1 什么是JMM

JMM:java内存模型,定义了共享内存系统中多线程读写操作的规范。

主要用于解决多线程对共享数据的读写一致性问题。

10.2 请简要谈一谈java的内存回收机制

  1. 什么是java内存回收?

    没有引用关联的内存会被垃圾回收器回收

  2. 什么时候回收/如何判断对象是垃圾

    引用计数算法和可达性分析算法,当对象的引用计数为0时或从GC ROOT到对象无可达性路径时会被标记为垃圾,等待GC回收。

  3. 需要GC的区域

    堆(Heap)和方法区(Method Area)

  4. 常用的GC算法/如何进行垃圾回收

    标记-清除算法:优点简单快速,缺点会产生内存碎片

    img

    标记-复制算法:优点避免了内存碎片,缺点只使用了一般内存

    img

    标记-整理算法:在标记-清除的基础上增加了内存碎片整理的过程,缺点整理过程中会降低GC效率。

    分代-收集算法:以jdk7 HotSpot为例将内存分为青年代、老年代和永久代

    img

    img

    img

    当老年代也放不下时会触发一次Full GC(标记-整理算法),Full GC的耗时通常会比较长,会让应用出现卡顿,这也是为什么很多应用需要优化,以避免或减少Full GC的原因。

    如果分配的新对象比较大,eden 区放不下,但是 old 区可以放下时,会直接分配到 old 区。

  5. 经典的垃圾回收器

java内存分配:堆、方法区、虚拟机栈、本地方法栈和程序计数器,其中虚拟机栈、本地方法栈和程序计数器是线程独有的,跟随线程的生命周期不需要GC,而堆和方法区是线程共享的,是GC的主战场。

img

10.3 谈谈java 强、软、弱和虚应用

  • 强引用:使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

  • 软引用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。

  • 弱引用:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

  • 虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。

10.4 java类的加载过程

  1. java代码的执行过程

image.png

  1. 类的加载过程

image.png

  • 加载:通过类加载器将字节码文件(.class文件)转换为内存中的java.lang.Class对象;
  • 链接:包含验证(确保被加载的类的正确性)、准备(为类的静态变量分配内存,并将其初始化为默认值)和解析(把类中的符号引用转换为直接引用)三个阶段,
  • 初始化:对类的静态变量,静态代码块执行初始化操作
  1. 类加载器的分类

image.png

  1. 双亲委派机制

image.png

(1)如果一个类加载器接收到了类加载的请求,它自己不会先去加载,会把这个请求委托给父类加载器去执行。

(2)如果父类还存在父类加载器,则继续向上委托,一直委托到启动类加载器:Bootstrap ClassLoader

(3)如果父类加载器可以完成加载任务,就返回成功结果,如果父类加载失败,就由子类自己去尝试加载,如果子类加载失败就会抛出ClassNotFoundException异常,这就是双亲委派模式

  • 全盘委托:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
  • 父类委托:先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
  • 缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。
  • 双亲委派

10.5 JVM、Dalvik、ART三者的原理和区别?

JVM:java vitual machine,java虚拟机规范

Dalvik:Google的一个用于Android的虚拟机

ART:Android Runtime