这篇文章将介绍 Java 面试过程中的常见问题。这里只介绍非多线程的部分,关于Java多线程的面试的问题,可以看我之前写的Java多线程面试系列文章。
java中的四种引用有哪些
java中的四种引用有:强引用、软引用、弱引用和虚引用。不同的引用类型,主要体现的是对象不同的可达性(reachable)状态和对垃圾收集的影响。
- 强引用("Strong" Reference),就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。对于一个普通的对 象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,就是可以被垃圾收集的了,当然具体回收时机还是要看垃圾收集策略。
- 软引用(SoftReference),是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当JVM认为内存不足时,才会去试图回收软引用指向的对象。JVM会确保在抛 出OutOfMemoryError之前,清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓 存的同时,不会耗尽内存。
- 弱引用(WeakReference)并不能使对象豁免垃圾收集,仅仅是提供一种访问在弱引用状态下对象的途径。这就可以用来构建一种没有特定约束的关系,比如,维护一种非强制性 的映射关系,如果试图获取时对象还在,就使用它,否则重现实例化。它同样是很多缓存实现的选择。
- 幻象引用,有时候也翻译成虚引用,你不能通过它访问对象。幻象引用仅仅是提供了一种确保对象被fnalize以后,做某些事情的机制,比如,通常用来做所谓的PostMortem清理机制,我在专栏上一讲中介绍的Java平台自身Cleaner机制等,也有人利用幻象引用监控对象的创建和销毁。
成员变量的继承与覆盖
public class C {
int num = 1;
}
public class D extends C {
int num = 2;
}
C c = new D();
c.num 获取的值是父类的,为1
从上面代码中可以看到,子类定义与父类同名的成员变量时,并没有覆盖父类的成员变量,而是两个成员变量共存。即使子类声明与父类完全一样的成员变量,也不会覆盖。
子类实例化时会同时定义两个成员变量,子类可同时访问这两个成员变量(this.i&super.i),但父类不能访问子类的成员变量。方法中使用成员变量时,由方法所在的类决定使用父类还是子类的成员变量。方法在父类中定义和执行,访问父类的成员变量;方法在子类中定义(包括覆盖父类方法)和执行,访问子类的成员变量。
不同位置的代码执行顺序
public class C {
static {
System.out.println("C static");
}
{
System.out.println("C block");
}
public C() {
System.out.println("C()");
}
}
public class D extends C {
static {
System.out.println("D static");
}
public D() {
System.out.println("D()");
}
{
System.out.println("D block");
}
}
调用 C c = new D(); 时执行结果是
C static
D static
C block
C()
D block
D()
再执行 c = new D();执行结果为
C block
C()
D block
D()
执行 D d = new D();的执行结果为
C block
C()
D block
D()
Java对象和数组在内存中大小
对于基本数据类型,占用的大小为:
- boolean 1个字节
- char 2个字节
- byte 1个字节
- short 2个字节
- int 4个字节
- long 8个字节
- float 4个字节
- double 8个字节
对于类对象占用的内存大小,由其内部定义的属性和对象标志和指针来决定,如下图所示:

- mark work : 包含一系列标志位和字段,比如锁状态、锁标志等。64位系统占8个字节,32位系统占4个字节。
- class pointer : 指向Class对象的内存地址。64位系统占8个字节,32位系统占4个字节。如果64位系统中开启了压缩则占用4个字节
- 实例字段 :包含的基本属性类型和引用属性类型。其中引用属性类型在64位系统占8个字节,32位系统占4个字节。当开启压缩时则在64位系统中占4个字节。注意,static的属性不会包含在类对象布局中。
- padding : 对齐填充,确保类对象占用的字节为8的倍数
对于数组对象占用内存大小,如下图所示:

可以看出,数组对象比类对象多一个数组长度,它占用4个字节。
数组的 Shallow Size = 数组对象头 + length * 引用指针大小,
数组的 Retained Size = Shallow Size+length * 每个元素的 Retained Size。
- Shallow size就是对象本身占用内存的大小,不包含其引用的对象。
- Retained size是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。
Java内存区域

- 程序计数区:指示当前线程的执行字节码的行号
- Java虚拟机栈:用来存储Java方法一次调用所对应的栈帧
- 本地方法栈:与Java虚拟机栈相似,不同的是它调用的是 native 方法。
- 方法区:Java 1.7及以前实现方式是 永久代;1.8使用的实现方式是 metaspace。在Java1.8中,用来保存类的结构信息,运行时常量池,字段,方法代码等,并将字符串常量池、静态变量转移到堆中。运行时常量池(Run-Time Constant Pool),是方法区的一部分。如果仔细分析过反编译的类文件结构,你能看到版本号、字段、方法、超类、接口等各种信息,还有一项信息就是常量池。Java的常量池可以存放各种常量信息,不管是编译期生成的各种字面量,还是需要在运行时决定的符号引用,所以它比一般语言的符号表存储的信息更加宽泛
- 堆:用来存放Java对象实例
- 直接内存:jvm直接访问内核空间的内存,应用:NIO实现zero-copy
Java的异常

如图,Exception和Error都是继承了Throwable类,在Java中只有Throwable类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。
- Exception是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
- Error是指在正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序(比如JVM自身)处于非正常的、不可恢复状态。(Error可以捕获)一般情况下不需要捕获。
常见的Error有:OutOfMemoryError(内存溢出错误)、java虚拟机运行错误(VirtualMachineError)、类定义错误(NoClassDefFoundError);常见的Exception有:NullPointerException、ClassNotFoundException
java类加载机制

final finally finalize的区别
fnal可以用来修饰类、方法、变量,分别有不同的意义,fnal修饰的class代表不可以继承扩展,fnal的变量是不可以修改的,而fnal的方法也是不可以重写的(override)。
fnally则是Java保证重点代码一定要被执行的一种机制。当程序有死循环或者调用system
fnalize是基础类java.lang.Object的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。fnalize机制现在已经不推荐使用,并且在JDK 9开始被标记 为deprecated。
String、StringBuffer、StringBuilder 的区别
- String是Java重要基础类,用于构造和管理字符串,是final类且属性也为final,具有不可变性,字符串操作会产生新对象且影响性能。
- StringBuffer用于解决拼接产生过多中间对象问题,可通过append或add方法添加字符串,它是线程安全的可修改字符序列,但有性能开销,非线程安全需求时推荐使用StringBuilder。
- StringBuilder是Java 1.5新增的,与StringBuffer无本质区别,但去掉了线程安全部分,减小了开销,是多数情况下字符串拼接首选。
8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:
- 直接使用双引号声明出来的String对象会直接存储在常量池中。
- 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
int和Integer的区别
int是Java的原始数据类型之一,是整形数字。Java虽强调一切皆对象,但原始数据类型除外。Integer是int的包装类,有int字段存储数据,提供基本操作及转换功能。Java 5引入自动装箱和拆箱,可自动转换。Java 5还改进了Integer,新增静态工厂方法valueOf,有缓存机制,默认缓存 -128到127之间,性能得以提升,传统方式是直接用构造器创建对象。
对比Vector、ArrayList、LinkedList有何区别
这三者都是实现集合框架中的List,也就是所谓的有序集合,因此具体功能也比较近似,比如都提供按照位置进行定位、添加或者删除的操作,都提供迭代器以遍历其内容等。但因 为具体的设计区别,在行为、性能、线程安全等方面,表现又有很大不同。
- Vector是Java早期提供的线程安全的动态数组,如果不需要线程安全,并不建议选择,毕竟同步是有额外开销的。Vector内部是使用对象数组来保存数据,可以根据需要自动的增加 容量,当数组已满时,会创建新的数组,并拷贝原有数组数据。
- ArrayList是应用更加广泛的动态数组实现,它本身不是线程安全的,所以性能要好很多。与Vector近似,ArrayList也是可以根据需要调整容量,不过两者的调整逻辑有所区 别,Vector在扩容时会提高1倍,而ArrayList则是增加50%。
- LinkedList顾名思义是Java提供的双向链表,所以它不需要像上面两种那样调整容量,它也不是线程安全的。
对比Hashtable、HashMap、TreeMap有什么不同
Hashtable、HashMap、TreeMap都是最常见的一些Map实现,是以键值对的形式存储和操作数据的容器类型。
- Hashtable是早期Java类库提供的一个哈希表实现,本身是同步的,不支持null键和值,由于同步导致的性能开销,所以已经很少被推荐使用。
- HashMap是应用更加广泛的哈希表实现,行为上大致上与HashTable一致,主要区别在于HashMap不是同步的,支持null键和值等。通常情况下,HashMap进行put或者get操 作,可以达到常数时间的性能,所以它是绝大部分利用键值对存取场景的首选,比如,实现一个用户ID和用户信息对应的运行时存储结构。
- TreeMap则是基于红黑树的一种提供顺序访问的Map,和HashMap不同,它的get、put、remove之类操作都是O(log(n))的时间复杂度,具体顺序可以由指定 的Comparator来决定,或者根据键的自然顺序来判断。
谈谈接口和抽象类有什么区别?
接口
- 是行为抽象,为抽象方法集合,可实现 API 定义与实现分离,无法实例化。
- 成员均为隐式 public static final,方法要么是抽象方法要么是静态方法,无非常量成员。Java 标准库有很多接口,如 java.util.List。
抽象类
- 用 abstract 修饰,不能实例化,目的是代码重用。
- 和普通类形式相似,可含一个或多个抽象方法或无抽象方法。常用于抽取共用方法或成员变量供继承实现复用。如 Java 标准库中 collection 框架的 java.util.AbstractList。
- Java 类实现接口用 implements,继承抽象类用 extends
谈谈你知道的设计模式
大致按照模式的应用目标分类,设计模式可以分为创建型模式、结构型模式和行为型模式。 创建型模式,是对对象创建过程的各种问题和解决方案的总结,包括各种工厂模式(Factory、Abstract Factory)、单例模式(Singleton)、构建器模式(Builder)、原型模 式(ProtoType)。 结构型模式,是针对软件设计结构的总结,关注于类、对象继承、组合方式的实践经验。常见的结构型模式,包括桥接模式(Bridge)、适配器模式(Adapter)、装饰者模式 (Decorator)、代理模式(Proxy)、组合模式(Composite)、外观模式(Facade)、享元模式(Flyweight)等。 行为型模式,是从类或对象之间交互、职责划分等角度总结的模式。比较常见的行为型模式有策略模式(Strategy)、解释器模式(Interpreter)、命令模式(Command)、 观察者模式(Observer)、迭代器模式(Iterator)、模板方法模式(Template Method)、访问者模式(Visitor)。