Android面试Java中级试题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Android面试Java中级试题
本文是Android面试题整理中的一篇,结合右下角目录食用更佳,包括:
•线程
•线程中的关键字
•线程池
•多线程中的工具类
•进程
•类加载
•范型和反射
线程
1. 什么是线程
线程是操作系统能够进行调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,可以使用多线程对进行运算提速。
2. 编写多线程的几种方式
1.一种是继承Thread类;
2.另一种是实现Runnable接口。
两种方式都要通过重写run()
方法来定义线程的行为,推荐使用后者,因为Java中的继
承是单继承,一个类有一个父类,如果继承了Thread类就
无法再继承其他类了,显然使用Runnable接口更为灵活。
3.实现Callable接口,该接口中的call方法可以在线程执行
结束时产生一个返回值
3. 什么是FutureTask
FutureTask实现了Future接口和Runnable接口,可以对任务进
行取消和获取返回值等操作。
4. 如何强制启动一个线程
做不到,和gc一样,只能通知系统,具体何时启动有系统控制
5. 启用一个线程是调用run()还是start()方法
启动一个线程是调用start()方法,使线程所代表的虚拟处理机处
于可运行状态,这意味着它可以由JVM 调度并执行,这并不意味
着线程就会立即运行
6. 说出线程调度和线程同步的方法
线程调度
1.wait( ):Object方法,必须在同步代码块或同步方法中使
用,使当前线程处于等待状态,释放锁
2.notify ( ):Object方法,和wait方法联合使用,通知一
个线程,具体通知哪个由jvm决定,使用不当可能发生死锁
3.notifyAll ( ):Object方法,和wait方法联合使用,通知
所有线程,具体哪个线程获得运行权jvm决定
4.sleep( ):使一个正在运行的线程处于睡眠状态,是一个静
态方法,调用此方法要处理InterruptedException异常7. 线程同步
1.Synchronized修饰方法
2.Synchronized修饰代码块
3.Lock/ReadWriteLock
4.ThreadLocal:每个线程都有一个局部变量的副本,互不干
扰。
一种以空间换时间的方式
5.java中有很多线程安全的容器和方法,可以帮助我们实现
线程同步:如Collections.synchronizedList()方法将
List转为线程同步;用ConurrentHashMap 实现hashmap
的线程同步。
BlockingQueue阻塞队列也是线程同步的,非
常适用于生产者消费者模式
6.扩展:volatile(volatile修饰的变量不会缓存在寄存器
中,每次使用都会从主存中读取):保证可见性,不保证原
子性,因此不是线程安全。
在一写多读/状态标志的场景中
使用
8. 什么是可重入锁
所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的
9. Java中如何停止一个线程
1.Java提供了很丰富的API但没有为停止线程提供API
2.可以用volatile 布尔变量来退出run()方法的循环或者是
取消任务来中断线程
10. 一个线程运行时发生异常会怎样
1.如果异常没有被捕获该线程将会停止执行
2.可以用UncaughtExceptionHandler来捕获这种异常
11. 多线程共享数据
1.使用同一个runnable对象
2.使用不同的runnable对象,将同一共享数据实例传给不同
的runnable
3.使用不同的runnable对象,将这些Runnable对象作为一个
内部类,将共享数据作为成员变量
12. 多线程的最佳实践/好习惯
1.给线程起个有意义的名字
2.避免使用锁和缩小锁的范围
3.多用同步辅助类(CountDownLatch、CyclicBarrier、
Semaphore)少用wait、notify
4.多用并发集合少用同步集合
13. ThreadLocal的设计理念与作用
1.供线程内的局部变量,线程独有,不与其他线程共享
2.适用场景:多线程情况下某一变量不需要线程间共享,需要
各个线程间相互独立
14. ThreadLocal原理,用的时候需要注意什么
1.ThreadLocal通过获得Thread实例内部的ThreadLocalMap
来存取数据
2.ThreadLocal实例本身作为key值
3.如果使用线程池,Threadlocal可能是上一个线程的值,需
要我们显示的控制
4.ThreadLocal的key虽然采用弱引用,但是仍然可能造成内
存泄漏(key为null,value还有值)
扩展:Android中的ThreadLocal实现略有不同,使用Thread
实例中的是数组存值,通过ThreadLocal实例计算一个唯一
的hash确定下标。
15. 线程的基本状态及状态之间的关系
16. 如果同步块内的线程抛出异常会发生什么
1.线程内的异常可以捕获,如果没有捕获,该线程会停止运行
退出
2.不论是正常退出还是异常退出,同步块中的锁都会释放17. 什么是死锁(deadlock)
两个线程互相等待对方释放资源才能继续执行下去,这个时候就形成了死锁,谁都无法继续执行(或者多个线程循环等待)
18. N个线程访问N个资源,如何避免死锁
以同样的顺序加锁和释放锁
19. 为什么应该在循环中检查等待条件
处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出
20. Java中的同步集合与并发集合有什么区别
1.同步集合与并发集合都为多线程和并发提供了合适的线程
安全的集合
2.并发集合性能更高
21. Java中活锁和死锁有什么区别
这是上题的扩展,活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。
一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避
让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不
能通过走廊。
简单的说就是,活锁和死锁的主要区别是前者进程的
状态可以改变但是却不能继续执行
22. 怎么检测一个线程是否拥有锁
ng.Thread中有一个方法叫holdsLock(),它返回true如
果当且仅当当前线程拥有某个具体对象的锁
23. Java中ConcurrentHashMap的并发度是什么
ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展
性和线程安全。
这种划分是使用并发度获得的,它是
ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这
样在多线程情况下就能避免争用
24. 什么是阻塞式方法
阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,
ServerSocket的accept()方法就是一直等待客户端连接。
这里的
阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果
之后才会返回。
此外,还有异步和非阻塞式方法在任务完成前就返
回。
25. 多线程中的忙循环是什么
忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃
CPU,它就是在运行一个空循环。
这么做的目的是为了保留CPU缓
存,在多核系统中,一个等待线程醒来的时候可能会在另一个内
核运行,这样会重建缓存。
为了避免重建缓存和减少等待重建的时
间就可以使用它了。
26. 如何保证多线程下 i++ 结果正确
可以使用synchronized保证原子性,也可以使用AtomicInteger
类
扩展:volatile只能保证可见性,不能保证原子性,因此不行27. 简述Java中具有哪几种粒度的锁
Java中可以对类、对象、方法或是代码块上锁
同步方法和同步代码块的对比
1.同步代码块可以指定更小的粒度
2.同步代码块可以给指定实例加锁
28. 类锁和对象锁
类锁其实时一种特殊的对象锁,它锁的其实时类对应的class对象
线程中的关键字和类
0. sleep和wait方法的对比
1.两个方法都是暂停线程,释放cpu资源给其他线程
2.sleep是Thread的静态方法,wait是Object的方法。
3.sleep使线程进入阻塞状态;wait使线程进入等待状态,靠
其他线程notify或者notifyAll来改变状态
4.sleep可以在任何地方使用,必须捕获异常;而wait必须
在同步方法或者同步块中使用,否则会抛出运行时异常
5.最重要的:sleep继续持用锁,wait释放锁扩展:yield
停止当前线程,让同优先级或者优先级高的线程先执行(但
不会释放锁);join方法在某一个线程的执行过程中调用
另一个线程执行,等到被调用的线程执行结束后,再继续执
行当前线程
1. 线程的sleep()方法和yield()方法有什么区别
1.sleep方法使当前线程阻塞指定时间,随后进入就绪状态
2.yield方法使当前线程进入就绪状态,让同优先级或者更高
优先级的线程先执行
3.sleep方法会抛出interruptedException
2. 为什么wait, notify 和 notifyAll这些方法不在thread类里面
JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通
过线程获得。
如果线程需要等待某些锁那么调用对象中的wait()
方法就有意义了。
如果wait()方法定义在Thread类中,线程正在
等待的是哪个锁就不明显了
3. 为什么wait和notify方法要在同步块中调用
1.java规定必须在同步块中,不在同步块中会抛出异常
2.如果不在同步块中,有可能notify在执行的时候,wait没
有收到陷入死锁
4. synchronized关键字的用法
synchronized 用于线程同步
1.可以修饰方法
2.可以修饰代码块
3.当持有的锁是类时,那么所有实例对象调用该方法或者代码
块都会被锁
5. synchronized 在静态方法和普通方法的区别
1.synchronized修饰静态方法时,锁是类,所有的对象实例
用同一把锁
2.修饰普通方法时,锁是类的实例
6. 当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?
不能。
其它线程只能访问该对象的非同步方法。
第一个线程持有了
对象锁,第二个线程的同步方法也需要该对象的锁才能运行,只能
在锁池中等待了。
7. Java中的volatile 变量是什么
1.volatile是一个修饰符,只能修饰成员变量
2.volatile保证了变量的可见性(A线程的改变,B线程马上
可以获取到)
3.volatile禁止进行指令重排序
8. 写一个双检锁的单例
private static volatile Singleton instance;
private Singleton(){}
public Singleton getInstance(if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}return sinlgeton;
)
9. 单例的DCL方式下,那个单例的私有变量要不要加volatile关键字,这个关键字有什么用
1.要加
2.两个线程同时访问双检锁,有可能指令重排序,线程1初始
化一半,切换到线程2;因为初始化不是一个原子操作,此
时线程2读到不为null直接使用,但是因为还没有初始化
完成引起崩溃
10. Synchronized 和Lock\ReadWriteLock的区别
1.Synchronized时java关键字,Lock/ReadWriteLock接口,
它们都是可重入锁
2.Synchronized由虚拟机控制,不需要用户去手动释放锁,
执行完毕后自动释放;而Lock是用户显示控制的,要用户
去手动释放锁,如果没有主动释放锁,就有可能导致出现死
锁现象。
3.Lock可以用更多的方法,比如tryLock()拿到锁返回true,
否则false;tryLock(long time, TimeUnit unit)方法和
tryLock()方法是类似的,只不过区别在于这个方法在拿不
到锁时会等待一定的时间;Lock有lockInterruptibly()
方法,是可中断锁
4.ReentrantLock可以实现公平锁(等得久的先执行)
5.ReadWriteLock是一个接口,ReentrantReadWriteLock是它
的一个实现,将对一个资源(比如文件)的访问分成了2
个锁,一个读锁和一个写锁,提高了读写效率。
11. LockSupport
LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语
park 方法获取许可。
许可默认是被占用的,调用park()时获取不
到许可,所以进入阻塞状态 unpark 方法颁发许可
12. ReadWriteLock
1.读写分离的锁,可以提升效率
2.读读能共存,读写、写写不能共存
13. 可重入锁(RetrantLock)实现原理
1.RetrantLock 是通过CAS和AQS实现的
2.CAS(Compare And Swap):三个参数,一个当前内存值V、
旧的预期值A、即将更新的值B,当且仅当预期值A和内存
值V相同时,将内存值修改为B并返回true,否则什么都
不做,并返回false。
原子性操作
3.RetrantLock内部有一个AbstractQueuedSynchronizer实
例,AbstractQueuedSynchronizer是一个抽象类,
RetrantLock中有两种对他的实现,一种是公平锁,一种是
非公平锁
4.在lock时,调用一个CAS的方法compareAndSet来将state
设置为1,state是一个volitale的变量,并将当前线程和
锁绑定
5.当compareAndSet失败时,尝试获取锁:如果和锁绑定的线
程时当前线程,state+1
6.如果获取锁失败,将其加入到队列中等待,从而保证了并发
执行的操作变成了串行
7.扩展:公平锁和非公平锁的区别:非公平锁无视队列,直接
查看当前可不可以拿到锁;公平锁会先查看队列,队列非空
的话会加入队列
14. Others
synchronized 的实现原理以及锁优化?:Monitor
volatile 的实现原理?:内存屏障
CAS?CAS 有什么缺陷,如何解决?CompareAndSwap,通过cpu指
令实现的
AQS :AbstractQueueSynchronizer,是ReentrantLock一个内部
类
如何检测死锁?怎么预防死锁?:死锁必须满足四个条件,破坏任
意一个条件都可以解除死锁
Fork/Join框架
线程池
0. 什么是线程池(thread pool)
1.频繁的创建和销毁对象很耗费资源,所以java引入了线程
池。
Java 5+中的Executor接口定义一个执行线程的工具。
它的子类型即线程池接口是ExecutorService。
2.Executors 是一个工具类,可以帮我们生成一些特性的线程
池
newSingleThreadExecutor:创建一个单线程化的Executor,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newFixedThreadPool:创建一个指定工作线程数量的线程池。
每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newScheduleThreadPool:创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
复制代码
1.我们常用的ThreadPoolExecutor实现了ExecutorService
接口,以下是原理和参数说明
原理:
step1.调用ThreadPoolExecutor的execute提交线程,首先检查CorePool,如果CorePool内的线程小于CorePoolSize,新创建线程执行任务。
step2.如果当前CorePool内的线程大于等于CorePoolSize,那么将线程加入到BlockingQueue。
step3.如果不能加入BlockingQueue,在小于MaxPoolSize的情况下创建线程执行任务。
step4.如果线程数大于等于MaxPoolSize,那么执行拒绝策略。
参数说明:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize 核心线程池大小
maximumPoolSize 线程池最大容量大小
keepAliveTime 线程池空闲时,线程存活的时间
TimeUnit 时间单位
ThreadFactory 线程工厂
BlockingQueue任务队列
RejectedExecutionHandler 线程拒绝策略
扩展:ThreadPoolExecutor 的submit和excute方法都能执行任务,有什么区别?
1. 入参不同:excute只能接受Runnable,submit可以接受Runnable和Callable
2. submit有返回值
3. 在异常处理时,submit可以通过Future.get捕获抛出的异常
复制代码
1. 线程池如何调优,最大数目如何确认
1.线程池的调优优根据具体情况具体分析,尽量使系统资源利
用率最大
2.例如如果cpu效率明显高于IO,那么就应该创建更多线程
提高cpu利用率,避免io等待(参考1,参考2)
3.Android中最大数目可以是:cpu数目*2+1,但也要根据具
体场景,例如picaso会根据网络状况调整最大数目(参考)
2. 如果你提交给ThreadPoolExcuter任务时,线程池队列已满,这时会发生什么
1.如果还没达到最大线程数,则新建线程
2.如果已经达到最大线
程数,交给RejectExecutionHandler处理。
3.如果没有设置自定
义RejectExecutionHandler,则抛出RejectExecutionExcuption
3. 线程池的用法与优势
优势: 实现对线程的复用,避免了反复创建及销毁线程的开销;使
用线程池统一管理线程可以减少并发线程的数目,而线程数过多往
往会在线程上下文切换上以及线程同步上浪费过多时间。
用法: 我们可以调用ThreadPoolExecutor的某个构造方法来自己
创建一个线程池。
但通常情况下我们可以使用Executors类提供给
我们的静态工厂方法来更方便的创建一个线程池对象。
创建了线程
池对象后,我们就可以调用submit或者excute方法提交任务到线
程池中去执行了;线程池使用完毕后我们要记得调用shutdown方
法来关闭它。
多线程中的工具类
0. Java并发编程:CountDownLatch、CyclicBarrier(栅栏)和Semaphore (信号量)
1.CountDownLatch:利用它可以实现类似计数器的功能。
比如
有一个任务A,它要等待其他4个任务执行完毕之后才能执
行,此时就可以利用CountDownLatch来实现这种功能了
public class Test {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(2);
new Thread(){
public void run() {
try {
System.out.println("子线程
"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程
"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
new Thread(){
public void run() {
try {
System.out.println("子线程
"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程
"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
try {
System.out.println("等待2个子线程执行完毕...");
latch.await();
System.out.println("2个子线程已经执行完毕");
System.out.println("继续执行主线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}复制代码
1.CyclicBarrier: 实现让一组线程等待至某个状态之后再全
部同时执行
public class Test {
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N);
for(int i=0;i<N;i++)
new Writer(barrier).start();
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程
"+Thread.currentThread().getName()+"正在写入数据...");
try {
Thread.sleep(5000); //以睡眠来模拟写入数据操作
System.out.println("线程
"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕"); cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
System.out.println("所有线程写入完毕,继续处理其他任务...");
}
}
}
扩展(CyclicBarrier和CountdownLatch的区别):1.CountdownLatch等待几个任务执行完毕,CyclicBarrier等待达到某个状态;2.CyclicBarrier可以调用reset,循环使用;3.CyclicBarrier可以有含Runnable的构造方法,当达到某一状态时执行某一任务。
复制代码
1.Semaphore:Semaphore可以控同时访问的某个资源的线程
个数
public class Test {
public static void main(String[] args) {
int N = 8; //工人数
Semaphore semaphore = new Semaphore(5); //机器数目
for(int i=0;i<N;i++)
new Worker(i,semaphore).start();
}
static class Worker extends Thread{
private int num;
private Semaphore semaphore;
public Worker(int num,Semaphore semaphore){
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("工人"+this.num+"占用一个机器在生产...");
Thread.sleep(2000);
System.out.println("工人"+this.num+"释放出机器");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}复制代码
1. java中的信号量(Semaphore)
1.Semaphore可以控制当前资源被访问的线程个数,超过最大
个数后线程处于阻塞等待状态
2.当线程个数指定为1时,可以当锁使用
2. 怎么实现所有线程在等待某个事件的发生才会去执行
所有线程需要阻塞等待,并且观察到事件状态改变满足条件时自动
执行,可以用以下方法实现
1.闭锁CountDownLatch:闭锁是典型的等待事件发生的同步
工具类,将闭锁的初始值设置1,所有线程调用await方法
等待,当事件发生时调用countDown将闭锁值减为0,则所
有await等待闭锁的线程得以继续执行。
2.阻塞队列BlockingQueue:所有等待事件的线程尝试从空的
阻塞队列获取元素,将阻塞,当事件发生时,向阻塞队列中
同时放入N个元素(N的值与等待的线程数相同),则所有等
待的线程从阻塞队列中取出元素后得以继续执行。
3.信号量Semaphore:设置信号量的初始值为等待的线程数N,
一开始将信号量申请完,让剩余的信号量为0,待事件发生
时,同时释放N个占用的信号量,则等待信号量的所有线程
将获取信号量得以继续执行。
3. 生产者-消费者实现之阻塞队列
1.扩展:通过sychronized关键字实现
2.阻塞队列的特征是当取或放元素是,队列不满足条件(比如
队列为空时进行取操作)可以阻塞等待,知道满足条件
public class BlockingQueueTest {
private int size = 20;
private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(size);
public static void main(String[] args) {
BlockingQueueTest test = new BlockingQueueTest();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override public void run() {
while(true){
try {
//从阻塞队列中取出一个元素
queue.take();
System.out.println("队列剩余" + queue.size() + "个元素");
} catch (InterruptedException e) {
} } }
}
class Producer extends Thread{
@Override public void run() {
while (true) {
try {
//向阻塞队列中插入一个元素
queue.put(1);
System.out.println("队列剩余空间:" + (size - queue.size())); } catch (InterruptedException e) {} }}
}
}复制代码
4. ArrayBlockingQueue, CountDownLatch类的作用
1.ArrayBlockingQueue:一个基于数组实现的阻塞队列,它在
构造时需要指定容量。
当试图向满队列中添加元素或者从空
队列中移除元素时,当前线程会被阻塞。
2.CountDownLatch:同步计数器,是一个线程工具类,可以让
一个或几个线程等待其他线程
5. Condition
Condition是一个接口,有await和signal方法,和Object的wait、
notify类似 Condition 通过lock获得:Condition condition =
lock.newCondition(); 相对于Object的wait、notify,Condition
的控制更加灵活,可以满足唤起某一线程的目的
进程
0. 进程的三个状态
1.就绪状态:获得CPU调度时由就绪状态转换为运行状态
2.运行状态:CPU时间片用完了由运行状态转换为就绪状
态运行状态
3.阻塞状态:因等待某个事件发生而进入阻塞状态,事件发
生后由阻塞状态转换为就绪状态
1. 进程的同步和互斥
1.互斥:两个进程由于不能同时使用同一临界资源,只能在一
个进程使用完了,另一进程才能使用,这种现象称为进程间
的互斥。
2.对于互斥的资源,A进程到达了该点后,若此时B进程正在
对此资源进行操作,则A停下来,等待这些操作的完成再继
续操作。
这就是进程间的同步
2. 死锁产生的必要条件
1.互斥:一个资源一次只能被一个进程所使用,即是排它性使
用
2.不剥夺条件:一个资源仅能被占有它的进程所释放,而不能
被别的进程强占
3.请求与保持条件:进程已经保持了至少一个资源,但又提出
了新的资源要求,而该资源又已被其它进程占有,此时请求
进程阻塞,但又对已经获得的其它资源保持不放
4.环路等待条件:当每类资源只有一个时,在发生死锁时,必
然存在一个进程—资源的环形链
类加载
0. 描述一下JVM加载class文件的原理机制
类加载器的作用是根据指定全限定名称将class文件加载到JVM内存中,并转为Class对象。
加载器的种类
1.启动类加载器(根加载器 Bootstrap ClassLoader):由
native代码实现,负责将存放在<JAVA_HOME>\lib目录或
-Xbootclasspath参数指定的路径中的类库加载到内存中
2.扩展加载器(Extension ClassLoader):java语言实现,
父加载器是Bootstrap,:负责加载<JAVA_HOME>\lib\ext
目录或java.ext.dirs系统变量指定的路径中的所有类库。
3.应用程序类加载器(Application ClassLoader):java实
现,负责加载用户类路径(classpath)上的指定类库,我
们可以直接使用这个类加载器。
一般情况,如果我们没有自
定义类加载器默认就是用这个加载器。
4.自定义类加载器:有时为了安全会将类加密,或者从远程(服
务器)加载类,这个时候就需要自定义类加载器。
自定义
通过继承ClassLoader类实现,loadClass方法已经实现了
双亲委派模式,当父类没有加载成功时,调用当前类的
findclass方法,所以我们一般重写该方法。
加载过程
1.类加载器采用双亲委派模型进行加载:每次通过先委托父类
加载器加载,当父类加载器无法加载时,再自己加载。
2.类的生命周期可以分为七个阶段:加载 -> 连接(验证 ->
准备*(为静态变量分配内存并设置默认的初始值)* -> 解
析*(将符号引用替换为直接引用)*)-> 初始化 -> 使用 ->
卸载
1. 类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式
1.使用双亲委派模式,保证只加载一次该类
2.我们可以使用自定义的类加载器加载同名类,这样就阻止了
系统双亲委派模式的加载
2. ClassLoader的隔离问题
1.JVM 及Dalvik 对类唯一的识别是ClassLoader id +
PackageName + ClassName
2.两个相同的类可能因为两个ClassLoader加载而不兼容
反射和范型
0. 反射的原理和作用
1.通过类的class对象类获得类的各种信息,创建对应的对象
或者调用方法
2.App的动态加载或者Android中调用其他对象private方
法,都需要反射
1. 类对象的获取方式
1.String.class:不执行静态块和动态构造块
2."hello".getClass();:执行静态块和动态构造块
3.Class.forName("ng.String");:执行静态块,不执
行动态构造块
2. 如何通过反射创建对象
1.String.class.newInstance();
2.String.class.getConstrutor(Stirng.class).newInstanc
e("hello word");
3. 如何通过反射获取和设置对象私有字段的值
1.通过类对象的getDeclaredField()方法获得(Field)对象
2.调用Field对象的setAccessible(true)方法将其设置为可
访问
3.通过get/set方法来获取/设置字段的值
4. 通过反射调用对象的方法
1.通过类对象的getMethod方法获得Method对象
2.调用对象的invoke()方法
5. 范型
1.范型可以用于类定义和方法定义
2.范型的实现是通过擦除实现的,也就是说编译之后范型信息
会被擦出
6. 通配符
1.通配符有两种用法:?extends A 和? super A
2.?extends A 表示?的上界是A,具体什么类型并不清楚,
适合于获取,获取到的一定是A类型
3.? super A 表示?的下界是A,具体什么类型并不清楚,
适合于插入,一定可以插入A类型
7. 注解(Annotation)
注解分为三种:源码级别(source),类文件级别(class)或者
运行时级别(runtime);butternife是类文件级别。