1、进程和线程
2、内存溢出和内存泄漏
3、面向对象和面向过程
1、面向过程
2、面向对象
4、Java的四个基本特性
5、抽象类和接口的区别
6、自动装箱与拆箱
Java采用了自动装箱和拆箱机制,节省了常用数值的内存开销和创建对象的开销,提高了效率
7、序列化与反序列化
对象的序列化:是把对象转换成字节序列的过程
对象的反序列化:是把字节序列恢复为对象的过程
对象序列化的主要用途:
8、编译和运行
1、编译时和运行时
2、编译时类型和运行时类型
3、编译时和运行时的动态绑定
9、GC简述
当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象,通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。但是,为了保证 GC能够在不同平台实现的问题,Java规范对GC的很多行为都没有进行严格的规定
10、serialVersionUID简述
对于实现java.io.Serializable接口的实体类来说,往往都会手动声明serialVersionUID,因为只要你实现了序列化,java自己就会默认给实体类加上一个serialVersionUID。java默认添加的serialVersionUID是会根据实体类的成员(成员变量,成员方法)变化而变化。当我们把实体类序列化到本地后,如果实体类的成员发生了变化,默认添加的serialVersionUID就会发生变化。此时硬盘上序列化对象的serialVersionUID与实体类中的serialVersionUID对不上,就会反序列化失败爆出异常。所以,通常对于实现了SerialVersionUID接口的实体类来说,都会手动声明serialVersionUID
11、Java中4种引用类型
SoftReference和WeakReference的区别:
12、线程和守护线程
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
Daemon的作用是为其他线程的运行提供便利服务,比如垃圾回收线程就是一个很称职的守护者。User和Daemon两者几乎没有区别,唯一的不同之处就在于,如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有可以守护的对象了,也就没有继续运行程序的必要了
1、String、StringBuffer和StringBuilder
1、可变性
2、线程安全性
3、性能
2、构造器Constructor是否可被override
3、String类是否可以继承
String类由final类,故不可以继承,凡是由final修饰的类都不能继承
可查看我的博客,按顺序阅读,需要您具备数据结构基础,且基于JDK1.7
1、TreeMap和HashMap
TreeMap | HashMap |
---|---|
有序的 | 无序的 |
不允许null | 允许null |
实现Comparable接口 | 不实现Comparable接口 |
底层是红黑二叉树,线程不同步 | 底层是哈希表,线程不同步 |
2、TreeSet和HashSet
TreeSet | HashSet |
---|---|
有序的 | 无序的 |
不允许null | 允许null |
底层是红黑二叉树,线程不同步 | 底层是哈希表,线程不同步 |
TreeSet是通过TreeMap实现的,用的只是Map的key | HashSet是通过HashMap实现的,用的只是Map的key |
3、Collection和Collections
4、hashCode和equals
equals相等,hashCode必相等;hashCode相等,equals不一定相等
1、Error和Exception
Error类和Exception类的父类都是throwable类
2、Unchecked Exception和Checked Exception
1、Unchecked Exception
2、Checked Exception
1、xml解析方式
详细可见我的博客:Android基础——XML数据的三种解析方式
1、线程实现的方式
继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的线程
2、如何停止一个线程
3、线程的状态转换
1、新建(new):创建了一个线程对象
2、可运行(runnable):线程对象创建后,线程调用start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu的使用权
3、运行(running):可运行状态(runnable)的线程获得了cpu使用权,执行程序代码
4、阻塞(block):线程因为某种原因放弃了cpu使用权,即让出了cpu使用权,暂时停止运行,直到线程进入可运行(runnable)状态,才有机会再次获得cpu使用权转到运行(running)状态。阻塞的情况分三种:
5、死亡(dead):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期,且死亡的线程不可再次复生
4、什么是线程安全
在多线程访问同一代码的时候,不会出现不确定的结果
5、如何保证线程安全
6、Synchronized如何使用
Synchronized是Java的关键字,是一种同步锁,它可以修饰的对象有以下几种
7、Synchronized和Lock的区别
相同点:Lock能完成Synchronized所实现的所有功能
不同点:
8、多线程的等待唤醒主要方法
9、sleep和wait的区别
sleep() | wait() |
---|---|
是Thread类的方法 | 是Object类中的方法 |
调用sleep(),在指定的时间里,暂停程序的执行,让出CPU给其他线程,当超过时间的限制后,又重新恢复到运行状态,在这个过程中,线程不会释放对象锁 | 调用wait()时,线程会释放对象锁,进入此对象的等待锁池中,只有此对象调用notify()时,线程进入运行状态 |
10、多线程中的死锁
死锁:指两个或两个以上的线程在执行的过程中,因抢夺资源而造成互相等待,导致线程无法进行下去
产生死锁的4个必要条件
11、什么叫守护线程,用什么方法实现守护线程
守护线程:指为其他线程的运行提供服务的线程,可通过setDaemon(boolean on)方法设置线程的Daemon模式,true为守护模式,false为用户模式
12、Java中的BIO,NIO,AIO分别是什么,应用场景是什么
13、Java中的IO和NIO的区别
14、volatile关键字
用volatile修饰的变量,线程在每次修改变量的时候,都会读取变量修改后的值,可以简单的理解为volatile修饰的变量保存的是变量的地址。volatile变量具有synchronized的可见性,但是不具备原子性
volatile不是线程安全的,要使volatile变量提供理想的线程安全,必须同时满足下面两个条件
比如增量操作(x++)看上去类似一个单独操作,实际上它是一个由[读取-修改-写入]操作序列组成的组合操作,必须以原子方式执行,而volatile不能提供必须的原子特性。实现正确的操作,应该使x的值在操作期间保持线程安全,而volatile变量无法实现这点
然而,Java提供了java.util.concurrent.atomic.*
包下的变量或引用,让变量或对象的操作具有原子性,在高并发的情况下,依然能保持获取到最新修改的值,常见的有AtomicBoolean、AtomicReference等
1、什么是反射
在运行状态中,对于任意一个类,都可以获得这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性
2、反射使用步骤
1、进程的优先级
优先级从低到高排列,优先级最高的进程,最先获取资源,最后释放
1、空进程
这是Android系统优先杀死的,因为此时该进程已经没有任何用途
2、后台进程
包含不可见的Activity,即跳转到其他Activity后,由于资源不足,系统会将原来的Activity杀死(即跳转的来源)
3、服务进程
即Service,当系统资源不足时,系统可能会杀掉正在执行任务的Service,因此在Service执行比较耗时的操作,并不能保证一定能执行完毕
4、可见进程
当前屏幕上可以看到的Activity,例如显示一个对话框的Activity,那么对话框变成了前台进程,而调用他的Activity是可见进程,但并不是前台的
5、前台进程
当前处于最前端的Activity,也就是Android最后考虑杀死的对象,一般来说,前台进程Android系统是不会杀死的,只有当前4个都杀掉资源依旧不够才可能会发生
2、IPC机制
1、什么是类加载器
ClassLoader是用来动态加载class文件到内存中
2、类加载器类型
3、双亲委托模型(父委托加载机制)
当加载一个类时,首先会判断当前类是否已经被加载,如果被加载直接返回当前类加载器,如果没有被加载,则把机会让给父类,先让父类加载,若是父类中不能加载,则会去找到Bootstrap加载器,如果Bootstrap加载器加载失败,则会退回上层,自己通过findClass自己去加载对应的路径(这是孝顺型的,先想到父类,但是他们不是通过继承来实现的)
具体实现代码在ClassLoader类中的loadClass方法中,API19和API24加载过程有少许区别(以下是API24的源码),但大部分是一致的,下面执行的逻辑与我们上面所说的一致
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//首先会判断当前类是否已经被加载
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//把机会让给父类,先让父类加载
c = parent.loadClass(name, false);
} else {
//如果父类无法加载,则会去找Bootstrap加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
//如果前面的都加载失败,则调用findClass用自己的加载器加载
c = findClass(name);
}
}
return c;
}
优点:
4、类加载过程
5、自定义类加载器
public class MyClassLoader extends DexClassLoader {
public MyClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData != null) {
return defineClass(name, classData, 0, classData.length);
} else {
throw new ClassNotFoundException();
}
}
/**
* 读取文件的字节数组
*
* @param name
* @return
*/
private byte[] getClassData(String name) {
try {
InputStream inputStream = new FileInputStream(name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int buffSize = 4096;
byte[] buffer = new byte[buffSize];
int bytesRead = -1;
while ((bytesRead = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
6、类加载主要方法的区别
1、静态代码块、构造代码块、构造方法的执行顺序
class Fu{
static {
System.out.println("父静态代码块");
}
{
System.out.println("父代码块");
}
public Fu(){
System.out.println("父构造函数");
}
}
class Zi extends Fu{
static {
System.out.println("子静态代码块");
}
{
System.out.println("子代码块");
}
public Zi(){
System.out.println("子构造函数");
}
}
public class Text{
public static void main(String[] args) {
Zi zi = new Zi();
}
}
输出结果
父静态代码块
子静态代码块
父代码块
父构造函数
子代码块
子构造函数