面试题整理-校招(基础版)

  1. 分别介绍一下String,StringBuilder,StringBuffer ?

String:
    -被final修饰,不可以被继承;
    -不可变性;
    -通过字面量的方式给一个字符串变量赋值,此时变量引用指向字符串常量池中地址,字符串常量池中不会存储相同内容的字符串;
StringBuilder:
    -可变字符序列;
    -不保证同步;
StringBuffer:
    -可变字符序列;
    -保证同步,线程安全;
  1. String通过 字面量方式实例化 和 通过 new+构造器方式实例化 的区别?

字面量方式实例化:
    -变量引用指向字符串常量池;
    -字面量与字面量的拼接结果依旧指向字符串常量池;例如:s = "1" + "2";其实在编译器编译的时候,已经将他们合并到一起了,也就是以"12"的形式存在于常量池,此时的常量池中不存在"1"和"2";
    -只要拼接中有一个是变量,结果就在堆中;例如:s1 = "1";s3 = s1 + "2";编译生成的字节码内容其实就是利用StringBuilder实现,调用2次append方法将"1"和"2"拼接,并调用toString方法返回String对象,只不过这个动作是编译器做的,源码中看不见,在字节码中才能体现出来;
    -如果拼接结果调用intern方法,返回的是字符串常量池中的引用(StringTable中的value值);例如:s = s3.intern();s就是指向字符串常量池中;
new+构造器 方式实例化:
    -就是正常的调用构造方法在堆中创建String类型的对象;例如:s = new String("1");s引用指向堆内存,但是此时常量池中也是存在"1"的(因为"1"字面量存在于class文件,在执行字节码指令ldc的时候,会在字符串常量池创建字符串对象实例);
  1. HashMap和hashtable的区别?详细介绍一下HashMap。

-最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap就必须为之提供同步。
-HashMap:
    -初始容量16(数组长度),负载因子0.75;
    -JDK1.7是数组+链表;JDK1.8是数组+链表+红黑树;
    -为什么要在链表的基础上增加红黑树呢?
    答:因为红黑树是排好序的,左节点比其父节点小,右节点比其父节点大,所以查询更快,时间复杂度为O(log(n));而链表的遍历循环时间复杂度为O(n);
    -什么时候才需要扩容?
    答:当 HashMap 中的元素个数超过数组大小(数组长度)*loadFactor(负载因子)时,就会进行数组扩容;
    -删除方法 remove()
    答:删除方法就是首先先找到元素的位置,如果是链表就遍历链表找到元素之后删除。如果是用红黑树就遍历树然后找到之后做删除。
    -当两个对象的 hashCode 相等时会怎么样?
    答:会产生哈希碰撞。若 key 值内容相同则替换旧的 value,不然连接到链表后面,链表长度超过阈值 8 并且数组长度达到64就转换为红黑树存储,小于等于6会再次转化为链表(在发生调用resize方法时才有可能发生),注意只是当前桶中的链表进行转换,而不是整个数组的都转换;
  1. JVM垃圾回收器有多少种,说出你知道的?说一下垃圾回收算法有多少种,分别是什么?

-10种垃圾收集器:    
    -Serial收集器:
        -最基础、历史最悠久的收集器;
        -单线程工作,更多强调的是进行垃圾收集时,必须暂停其他所有工作线程,直到收集结束;
        -运行在客户端模式下默认新生代收集器,单核处理器环境下没有线程切换的开销,收集效率高;
    -ParNew收集器:
        -ParNew收集器实质上是Serial收集器的多线程并行版本,其他特新基本一致;
        -ParNew收集器是激活CMS后(使用-XX:+UseConcMarkSweepGC选项)的默认新生代收集器;
    -Parallel Scavenge收集器:
        -Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控吞吐量(Throughput)。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。
        -停顿时间越短就越适合需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提升用户体验;而高吞吐量则可以最高效率地利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务。
        -自适应调节策略也是Parallel Scavenge收集器区别于ParNew收集器的一个重要特性。
    -Serial Old收集器:
        -Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
        -这个收集器的主要意义也是供客户端模式下的HotSpot虚拟机使用。
        -如果在服务端模式下,它也可能有两种用途:一种是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用。另外一种就是作为CMS收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用。
    -Parellel Old收集器:
        -Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并行收集,基于标记-整理算法实现。
    -CMS收集器:
        -CMS收集器是一种以获取最短回收停顿时间为目标的收集器。
        -CMS收集器是基于标记-清除算法实现的,运作过程分为四个步骤:
            -初始标记:初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快;
            -并发标记:并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行;
            -重新标记:而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短;
            -并发清除:最后是并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
        -其中初始标记、重新标记这两个步骤仍然需要STW,由于在整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
        -优点:
            -并发收集;
            -低停顿;
        -缺点:
            -首先,CMS收集器对处理器资源非常敏感。当处理器核心数量低于4个时,对用户影响会变得很大。
            -然后,由于CMS收集器无法处理“浮动垃圾”,有可能出现“Concurrent Mode Failure”失败进而导致另一次完全STW的FullGC的产生。
            -CMS是一款基于“标记-清除”算法实现的收集器,收集结束时会有大量空间碎片产生。
    -Garbage First收集器(G1):
        -G1是一款主要面向服务端应用的垃圾收集器。
        -虽然G1也仍是遵守分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域,每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过了多次收集的旧对象都能获取很好的收集效果。
        -Region中还有一个特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,且应为2的N次幂。而对那些超过了整个Region容量的超大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待。
        -虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们是一系列区域(不需要连续)的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用-XX:MaxGCPauseMills指定,默认值是200毫秒),优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限的时间内获取尽可能高的收集效率。
        -运作过程大致可划分为以下四个步骤:
            -初始标记:仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region汇总分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
            -并发标记:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
            -最终标记:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
            -筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来指定回收计划,可以自有选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。
        -从上述阶段的描述可以看出,G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的,换言之,它并非纯粹地追求低延迟,官方给它设定的目标是在低延迟可控的情况下获得尽可能高的吞吐量,所以才能担当起“全功能收集器”的重任与期望。
        -从G1开始,最先进的垃圾收集器的设计导向都不约而同地变为追求能够应付应用的内存分配速率,而不追求一次把整个Java堆全部清理干净。这样,应用在分配,同时收集器在收集,只要收集的速度能跟得上对象分配的速度,那一切就能运作得很完美。这种新的收集器设计思路从工程实现上看是从G1开始兴起的,所以说G1是收集器技术发展的一个里程碑。
    -Shenandoah收集器:
        -工作过程可以大致分为以下九个阶段:初始标记、并发标记、最终标记、并发清理、并发清理、初始引用更新、并发引用更新、最终引用更新、并发清理;
        -实际上Shenandoah收集器使用过比较并交换(Compare And Swap,CAS)操作来保证并发时对象的访问正确性的。
    -ZGC收集器:
        -ZGC收集器是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。
        -ZGC的运作过程大致可划分为以下四个大的阶段:并发标记、并发预备重分配、并发重分配、并发重分配;
    -Epsilon收集器:
        -这是一款不能够进行垃圾收集的垃圾收集器。
-垃圾收集算法:
    -标记-清除算法;
        -老年代会采用、但是会产生内存碎片;
    -标记-复制算法;
        -新生代采用此种算法;
        -会浪费一部分内存;
    -标记-整理算法;
        -老年代会采用、不会产生内存碎片,但是效率会比标记-清除要低;
  1. 说一下JVM内存模型,并说出哪些是线程独占、哪些是线程间共享,并介绍各个部分的用途?

-运行时数据区域:堆、虚拟机栈、方法区、程序计数器、本地方法栈;
-线程独占:虚拟机栈、程序计数器、本地方法栈;
-线程间共享:堆、方法区;
-用途:
    -程序计数器:
        -一块较小的内存空间,可看做当前线程所执行字节码的行号指示器;
        -为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器;
        -如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地方法,这个计数器值则为空;
        -此内存区域是唯一一个没有规定OOM情况的区域;
    -虚拟机栈:
        -每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口的信息;
        -局部变量表存放了编译期可知的各种Java虚拟机基本数据类型、对象引用和returnAddress类型;
        -局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要再栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小;
        -有可能发生OOM情况;
    -本地方法栈:
        -于虚拟机栈基本一致;
    -堆:
        -此内存区域的唯一目的就是存放对象实例;
        -是垃圾收集器在重点管理的区域;
    -方法区:
        -用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据;
        -运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期生成的各种字面量于符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中;
  1. 说一下你对java异常的理解?

“异常”代表程序运行中遇到了意料之外的事情,为了表征异常,Java标准库中内建了一些通用的异常,这些类以 Throwable为父类。而 Throwable又派生出 Error类和 Exception类两大子类。
Error及其子类,代表了 JVM自身的异常。这一类异常发生时,无法通过程序来修正。最可靠的方式就是尽快停止 JVM的运行。
Exception 及其子类,代表程序运行中发生了意料之外的事情。这些意外的事情可以被 Java异常处理机制处理。而 Exception类及其子类又可以划分为两大类:
    -RuntimeException及其子类:这一类异常其实是程序设计的错误,通过修正程序设计是可以避免的,如数组越界异常、数值异常等。
    -非RuntimeException及其子类:这一类异常的发生通常由外部因素导致,是不可预知和避免的,如 IO异常、类型寻找异常等。
Error及其子类代表JVM出现异常,且无法通过软件修复;RuntimeException 及其子类是程序设计的错误,可以在编写程序时避免。以上这两大类异常称为免检异常,即不需要对这两类异常进行强制检查。而除上述两类异常外的其他异常,它们的发生与外部环境有关,称为必检异常。在编写程序时必须用 try、catch 语句将其包围起来。

对于 Throwable对象,其主要的成员变量有 detailMessage和 cause。
    -detailMessage为一个字符串,用来存储异常的详细信息。
    -cause 为另一个 Throwable 对象,用来存储引发异常的原因。这是因为一个异常发生时,通常引发异常的上级程序也发生异常,从而导致一连串的异常产生,叫作异常链。一个异常的 cause属性可以指向引发它的下级异常,从而将整个异常链保存下来。
其实异常就是一种程序的通知机制,当程序发生了无法自动修复的情况时,需要提示异常,这时候就需要人为的干预和处理,另外对于一些非RuntimeException的异常,就是底层API已经定义好了显示声明抛出这种异常,所以在外围调用这些方法的使用者就必须要显示的处理这种异常,或者将异常再抛出到外域。
  1. 说一下序列化和反序列化?

序列化是把对象转换为字节序列的过程;反序列化是把字节序列恢复为对象的过程。对象的序列化主要有两个目的:一是将对象转化成字节后保存在存储介质中,即为了持久化对象;二是将对象转化成字节后在网络上传输,即为了传输对象。而与之对应,将字节还原为对象的过程就是反序列化。
在 Java中,要表明一个类的对象是可序列化的,则必须继承 Serializable接口或其子接口 Externalizable接口。
Serializable 接口的使用非常简单,只要一个类实现了该接口,便表明该类的对象是可序列化的,而不需要增加任何方法。
序列化与反序列化过程中,要面临版本问题。例如,将一个 User类的对象 user1持久化到了硬盘中,然后增删了 User类的属性,那么此时还能将持久化在硬盘中的user1对象的序列还原成一个新的 User类的对象吗?
该问题的回答需要涉及 Serializable接口的 serialVersionUID字段。serialVersionUID字段叫作序列化版本控制字段,我们经常会在实现了 Serializable接口的类中见到它。
在反序列化过程中,如果对象字节序列中的 serialVersionUID与当前类的该值不同,则反序列化失败,否则成功。
如果没有显式地为一个类定义 serialVersionUID属性,系统就会自动生成一个。自动生成的序列化版本控制字段与类的类名、类及其属性修饰符、接口及接口顺序、属性、构造函数等相关,其中任何一项的改变都会导致 serialVersionUID发生变化。
因此,对于上面所述的 user1对象的序列能否还原成一个新的 User类的对象,需要分情况进行讨论。
    -如果旧 User类和新 User类中均有 serialVersionUID字段,且其值一样,则持久化在硬盘中的 user1对象的序列可以还原成一个新的 User类的对象。还原的过程中,新User类对象中新增的属性值为 null。
    -如果旧 User 类和新 User 类中一方不含 serialVersionUID 字段,或两方都含有serialVersionUID字段但其值不同,则无法反序列化,反序列化过程中会报出序列号版本不一致异常(InvalidClassException)。
在使用时,一般都会为实现Serializable接口的类显式声明一个serialVersionUID。这样便可以:
    -在希望类的版本间实现序列化和反序列化的兼容时,保持 serialVersionUID值不变。
    -在希望类的版本间序列化和反序列化不兼容时,确保 serialVersionUID值发生变化。
  1. 对Spring有了解么?听说过循环依赖么?知道循环依赖是如何解决的么?

-是一个Java应用程序的框架;
-是针对Bean生命周期进行管理的轻量级框架;
-spring中的循环依赖是通过三级缓存解决的;
分别介绍一下三级缓存的各级?
    -一级缓存:用来存放已经完成实例化+初始化的单例Bean;private final Map singletonObjects = new ConcurrentHashMap<>(256);//用来存放注册的SingletonBean,具体的实现类是ConcurrentHashMap。
    -二级缓存:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖;private final Map earlySingletonObjects = new ConcurrentHashMap<>(16);//是 singletonFactory 制造出来的 singleton 的缓存 ,与 singletonObjects 的不同之处在于,当一个单例 bean 被放到这里面后,那么当前bean还在创建过程中,就可以通过 getBean 方法获取到了,其目的是用来检测循环引用。
    -三级缓存:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖;private final Map> singletonFactories = new HashMap<>(16);//存储制造 singleton 的工厂,当 Spring 制造一个 bean 时因为依赖的 bean 还未完成 Spring 的单例制造会将前者包装成一个 ObjectFactory 放入。ObjectFactory 的getObject方法返回值就是 bean。
  1. 2

你可能感兴趣的:(java,java,面试)