面试八股文之——JAVA基础
众所周知,程序员的技术能力考核大部分来源于面试和笔试,少数人可以靠着开源项目或者是证书、个人作品(书籍)等提升求职竞争力而直接获取offer。绝大多数程序员依旧是靠面试来获取offer。因此对待面试题,很多时候,应聘者需要做很多的准备,本文将对JAVA基础知识高频面试题目进行分享。
一、数据类型
1.Integer与int的区别?有int,为啥还要设计封装类Integer?
回答角度:
数据类型,存储位置,初始值,包装类的作用
。
1.int是Java语言的基础
类型,存储于栈
中,初始值为0
。
2.Integer是int的包装类,是对象
类型,初始值为null
,存储于堆内存
中,通过对基础类型int的封装,使得对整形数据的操作更加灵活
与方便。
2.为什么两个整数a和b都等于1000,比对为false,a和b值均为100,比对结果为true?
回答角度:
缓存设计、享元模式
八大基础类型中,byte、short、int、long、char这五种类型都采用了缓存设计
,其中byte、short、int、long这四种数值类型为了减少频繁创建对象带来的内存消耗,缓存了-128~127范围的值(
为啥选这个范围,考虑到这个范围的数据使用是最频繁的),因此在此范围的值,读取来自于常量池构建的对象,并不会重新创建对象。这里借助于享元模式
,提升空间和时间的性能。
3.new String(“abc”) 到底创建了几个对象?
分情况讨论:
1.如果字符串"abc"常量不存在,则会创建两个对象,一个是存堆中,一个是存于常量池中。
2.如果字符串"abc"常量存在,则会创建一个对象,只会在堆中构建。
4.String、StringBuffer、StringBuilder的区别?
回答角度:
值可变性、线程安全、性能、存储位置
1.String内部使用final修饰,是不可变类,每次修改,都会重新构建一个新对象,其他两个类是可变类,因此在字符串变更时候,不会重新构建对象。
2.String作为不可变类,先天线程安全,StringBuffer内部方法使用synchronized关键字修饰,保证线程安全,
StringBuilder线程不安全
3.StringBuilder性能大于StringBuffer,因为StringBuffer使用了同步锁,String性能最低,因为对象不可变性,在对String类型数据进行修改时,只能通过重新创建对象来实现。
4.String存储于字符串常量池中,其他两个存储于堆中
二、Object对象
1.Java对象的创建过程?
回答角度:
JVM检查、类加载、类初始化、内存分配、普通成员初始化、对象头设置、init方法和构造方法
1.JVM会检查对象是否被加载并且初始化
2.没有的话,则JVM会立刻加载目标类,然后调用目标类的构造器完成初始化,目标类的加载是通过类加载器来实现的。主要作用就是把一个类加载到内存中
3.初始化包括静态变量、类成员变量、静态代码块进行初始化
4.初始化后,为新对象分配内存空间,分配内存一般分为:指针碰撞,空闲列表;JVM会根据Java堆是否规整来决定分配方式
5.JVM对普通成员变量进行初始化赋值,比如int初始化为0,Integer对象类型初始化为null
6.JVM对目标对象的对象头进行设置,GC分代年龄、锁标记、对像所属类元信息等。
7.执行目标对象内部生成的init方法【代码编译之后在字节码文件中生成】,初始化成员变量、执行构造块、执行构造方法。
2.谈谈对深克隆与浅克隆的理解?
设计模式中原型模式,就是专门处理对象复制问题。这个对象的复制,分为浅克隆和深克隆,
所谓浅克隆,即在复制一个对象时候,仅仅复制对象本身以及其引用类型成员的引用,而不复制引用指向的对象本身,因此新对象和原对象共享相同的属性。新旧对象引用类型属性的变更会相互影响。
深克隆,即在进行对象复制时,会对所有引用执行的对象进行递归复制。新旧对象相互对立,互不影响。
3.强引用、软引用、弱引用、虚引用的区别?
强引用:只要引用关系存在,JVM就永远不会对其进行回收,除非强制将对象赋值为null或者没有引用关系。
软引用:JVM会在内存不足,溢出前会对其进行垃圾回收,通常用来实现内存敏感的缓存。空间足,保留缓存,空间不足,则清理缓存。
弱引用:下次GC一定回收,不管内存是否足够。
虚引用:这个必须和引用队列一起使用,用来观测对象的GC过程。提供了一种确保对象被finalize之后执行某些事件的机制。
4.一个空Object到底占用多大内存?
1.对象分为三部分:
(1) 对象头,对象头继续分为Markword、类元指针、数组长度(4个字节)。
(2) 实例数据,实际存储对象的字段信息
(3) 对齐填充,Java对象需要按照8字节的整数倍来对齐,来避免虚伪共享问题。
其中Markword用以存储GC分代年龄、hashCode等,类元指针用来存储当前实例对象所属于哪个类。
这两个存储的空间大小受操作系统和JVM指针压缩配置的影响,
以64位系统为例,Markword占用8个字节,32位操作系统为4个字节,
未开启指针压缩情况下,类元指针占用8个字节。此时空对象占用16个字节。
如果开启指针压缩,类元指针占用4个字节,整个对象占用12字节,此时对齐填充会补齐4个字节,因此在64位操作系统下一个空Object仍然是16个字节。
5.为什么重写equals方法就一定要重写hashCode()方法?
Java集合中hash结构在判断某个key是否存在时,为了提高效率,先比对对象的hashCode,如hashCode结果一致,再使用equals方法进行比对,减少equals方法的调用。因此为了让对象和集合类能正常工作,必须保证
equals方法判定结果一致,则hashCode结果必须一致
。因此在重写equals方法时候,如不重写hashCode方法,可能会发生equals判定一致,hashCode判定不一致的错误。因此开发中约定俗成一条规则,equals方法重写,必须重写hashCode方法
。
三、其他特性
1.受检异常和非受检异常?
1.受检异常表示在代码编译时候需要强制检查的异常,通常需要显示地使用try/catch进行捕获,或者是使用throws进行抛出,否则程序无法通过编译。除了Error类和RuntimeException类及派生类,其他都是受检异常,
比如IOException,SQLException等2.非受检异常则不需要强制检查的异常,比如Error类和RuntimeException类及派生类。
2.fail-fast机制和fail-safe机制分别有什么作用?
这个是多线程并发操作集合时的失败处理机制。
fail-fast是快速失败
,在对集合进行遍历过程中,一旦发现容器中数据被修改,立即抛出ConcurrentModificationException,触发遍历失败。java.util下的集合类都是这个机制,比如HashMap/ArrayList
fail-fast是安全失败
,在对集合进行遍历过程中,一旦发现容器中数据被修改,不抛出异常,原因是,遍历过程中不是直接操作原集合,而是先复制集合,在操作复制后的集合。java.util.concurrent包下的集合都是采用这个机制,比如ConcurrentHashMap/CopyOnWriteArrayList等。
3.序列化和反序列化的理解?
序列化的目的是解决网络通信中对象传输问题
。为了便于对象的存储和传输,通常需要将对象转化为字节流,这个过程称为序列化,同样,将字节流转化为对象的过程称为反序列化。其次为了提升通信双方对对象的可识别性,会将对象格式化指定结构比如JSON/XML/Protobuf,然后在进行序列化传输。实际应用过程中采用哪种序列化技术需要综合考虑以下几点:
1.序列化后数据大小
2.序列化的性能
3.是否支持跨平台、跨语言
4.技术的成熟度
4.什么是SPI,SPI的作用?
SPI,全程Service Provider Interface,
是一种基于接口的动态扩展机制
,Java SPI 相当于Java提供了一套接口,然后第三方可以基于这个接口完成功能的扩展。
典型的例子就是数据库驱动Java.jdbc.Driver。JDK定义了驱动类,并没有提供具体实现,开发过程中,根据驱动类型,动态加载对应的扩展实现,完成对数据库的连接。
很多开源框架借鉴了Java SPI的思想,提供了自己的SPI框架,比如Dubbo的ExtensionLoader,Spring 提供了SpringFactoriesLoader,SPI机制将装配的控制权移至程序之外,做到标准和实现的解耦,提供了动态可插拔的能力。
Java SPI 存在不足,不能根据需求加载需要的扩展实现
,会实例化所有实现类,存在一定的资源浪费
5.finally语句块一定会执行吗?
不一定,两种情况不会执行
1.程序没有进入try代码块因为异常导致程序终止,问题原因代码捕获异常范围不够。
2.try或者catch语句块中执行了System.exit(0)语句,JVM直接退出。
6.内存溢出和内存泄漏的理解?
1.内存泄漏,是本该凋亡的对象,JVM没有对其进行垃圾回收,长期大量的对象造成的内存泄露,会导致内存溢出。
2.内存溢出,内存空间不足以分配现有的对象,比如要分配一个10MB的数组,此时JVM内存空间只剩下5MB,就会触发内存溢出,这是个重大错误。