Java基础面试题

1.Java面向对象有哪些特征?

  封装:封装隐藏了类的内部实现机制。对属性进行了封装:外界只能通过特定的方法进行访问。对方法进行了封装:外界只能通过定制好的方式调用,不用了解方法内部逻辑,方便使用。保护了数据。便于修改,增强了代码的可维护性和复用性。

  继承:继承是从已有的类中派生出新的类,即子类继承自父类。当子类通过extends关键字继承了父类后,便继承了父类的属性和方法。通过继承避免了对各个类中重复的属性和方法进行反复描述。增强了代码的复用性。代码更加简洁。

  多态:多态是类和类的关系。多态必须具备三个条件:1.要有继承关系    2.要有方法的重写    3.父类的引用指向子类的对象。正是因为两个类有继承关系,并存在方法的重写,才能在调用时有父类的引用指向子类的对象。通过多态可以是一个对象在不同载体中呈现不同的状态。即同一个父类引用在调用同一个方法时会得到不同的结果。多态减少了类的代码量,提高了代码的可维护性、可扩展性和复用性。

 

2、JVM面试题

 

 

 

3、ArrayList 和 LinkedList 的区别

  数据结构不同:ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构。

  效率不同:

   当随机访问List(get和set操作)时,ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。

 

   当对数据进行增加和删除的操作(add和remove操作)时,LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。

  自由性不同:ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。

  主要控件开销不同:ArrayList主要控件开销在于需要在lList列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。

 

4、JDK、JRE、JVM之间的区别

  JDK:Java标准开发包,它提供了编译、运行Java程序所需的各种工具和资源,包括Java运行环境、Java工具和Java基础类库。
  JRE:Java运行环境,用于运行Java的字节码文件。JRE包括了JVM以及JVM工作所需要的类库,普通用户而只需要安装JRE来运行Java程序,而程序员开发者必须安装JDK来编译,调试程序

  JVM:Java虚拟机,是JRE的一部分,他是整个Java实现跨平台的最核心部分,负责运行字节码文件

 

5、== 和 equals 方法的区别

  ==:如果数据是基本数据类型,比较的是值,如果引用类型,比较的是引用地址

  equals:具体看各个类重写equals方法之后的比较逻辑,比如String类,虽然是引用类型,但是String类中重写了equals方法,方法内部比较的是字符串中的各个字符是否全部相等

 

6、hashCode() 与 equals() 之间的关系

  在java中,每一个对象可以调用自己的hashcode()方法得到自己的哈希值

  • 如果两个对象的hashcode不相同,那么这两个对象肯定是不同的两个对象
  • 如果两个对象的hashcode相同,不代表这两个对象一定是同一个对象,也可能是两个对象
  • 如果两个对象相等,那么他们的hashCode就一定相同

  在java的一些集合类的实现类中,在比较两个对象是否相等的时候,会根据上面的原则,会先调用对象的hashCode()方法得到hashCode进行比较,如果hashCode不相同就可以直接认为这两个对象不相同,如果hashCode值相同,那么就会进一步调用equals()方法进行比较,而equals()方法,就是用来最终确定两个对象是不是相等,通常equals实现会比较重,逻辑比较多,而hashCode()主要就是得到一个哈希值,实际上就是一个数字,相对而言比较轻,所以在比较两个对象的时候,通常会先用hashCode

7、简述 final 作用

  修饰类:表示类不可以被继承

  修饰方法:表示方法不可被子类覆盖,但是可以重载

  修饰变量:表示变量一旦被赋值就不可以更改它的值

 

8、String、StringBuffer、StringBuilder的区别

  1、String是不可变的,如果尝试去修改,会产生一个字符串对象,StringBuffer和StringBuilder是可变的

  2、StringBuffer是线程安全的,StringBuilder是线程安全的,所以在单线程环境下使用StringBuilder效率会更高

 

9、重载和重写的区别

  重载:发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同,发生在编译中

  重写:发生在父子类中,方法名,参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类,如果父类方法访问修饰符为private则子类不能重写该方法

 

10、接口和抽象类的区别

  1、抽象类可以存在普通成员函数,而接口中只能存在public abstract方法

  2、抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的

  3、抽象类只能继承一个,接口可以实现多个

 

11、List 和 Set 的区别

  List:有序,按对象进入的顺序保护对象,可重复,允许对各Null元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标元素。增删块,查询慢 不能去重

  Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元素,在逐一遍历各个元素。可以去重,底层逻辑时HashMap,他是以Set的值作为Map的键 

 

12、HashMap 和 HashTable 的区别  底层实现是什么

  区别:

    1、HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全

    2、HashMap允许key和value为null,而HashTable不允许

  底层实现:数组 + 链表实现

 

13、谈谈ConcurrentHashMap的扩容机制

  • 1.7版本的ConcurrentHashMap是基于Segment分段实现的
  • 每个Segment相对于⼀个⼩型的HashMap
  • 每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
  • 先⽣成新的数组,然后转移元素到新数组中
  • 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值

 

  • jdk1.8之后:不再基于segment对象实现
  • 当某个线程put操作时,如果发现CocurrentHashMap正在扩容那么该线程一起扩容。
  • 当某个线程put操作,发现没有进行扩容,则将key-value添加到ConcurrentHashMap种,超过阈值再进行扩容。
  • 支持多线程扩容
  • 扩容之前也生成新的数组
  • 在转移元素之前,将数组分成多组,依靠多线程完成数组转移操作。

 

14、jdk1.7到jdk1.8 HashMap发生了什么变化(底层)

  •   1.7中底层是数组 + 链表  1.8中底层是数组 + 链表 + 红黑树,加红黑树的目的是提高HashMap插入和查询整体效率
  •   1.7中链表插入使用的是头插法,1.8中链表插入使用的是尾插法,因为1.8中插入key和value时需要判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使用尾插法
  •   1.7中哈希算法比较复杂,存在各种右移与异或运算,1.8中进行了简化,因为复杂的哈希算法的目的就是提高散列性,来提供HashMap的整体效率,而1.8中新增了红黑树,所以可以适当的简化哈希算法,节省cpu资源

  

15、HashMap的Put方法

 

 

 

  先说HashMap的Put方法的大体流程:

  1.     根据Key通过哈希算法与与运算得出数组下标
  2.     如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放入该位置
  3.     如果数组下标位置元素不为空,则要分情况讨论
    •     如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进行扩容,如果不用扩容就生成Entry对象,并使用头插法添加到当前位置的链表中
    •     如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红黑树Node,还是链表Node
        •   如果是红黑树Node,则将key和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value。
        •   如果此位置上的Node对象是链表节点,则将key和value封装为一个链表Node并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插入到链表中,插入到链表后,会看当前链表的节点个数,如果大于等于8,那么则会将该链表转成红黑树将key和value封装为Node插入到链表或红黑树中后,再判断是否需要进行扩容,如果需要就扩容,如果不需要就结束PUT方法

     

16、泛型中 extends 和 super 的区别 

  1、<? extends T> 表示包括T在内的任何T的子类

  2、<? super T> 表示包括T在内的任何T的父类

 

17、深拷贝 和 浅拷贝

  深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种基本数据类型,一种是实例对象的应用

    浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是同一个对象

    深拷贝是指,即会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部属性指向的不是同一个类

 

18、HashMap 的扩容机制原理

  1.7版本
    1.先生成新数组
    2.遍历老数组中的每个位置上的链表上的每个元素
    3.取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标

    4.将元素添加到新数组中去
    5.所有元素转移完了之后,将新数组赋值给HashMap对象的table属性


  1.8版本
    1.先生成新数组
    2.遍历老数组中的每个位置上的链表或红黑树
    3.如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
    4.如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置
      a.统计每个下标位置的元素个数
      b.如果该位置下的元素个数超过了6,则生成一个新的红黑树,并将根节点的添加到新数组的对应位置c.如果该位置下的元素个数没有超过6,那么则生成一个链表,并将链表的头节点添加到新数组的对应位置5.所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

 

19、CopyOnWriteArrayList的底层原理是怎样的

  1.首先CopyOnWiriteArraylist内部也是用过数组来实现的,在向CopyOnWriteArraylist添加元素时,会复制一个新的数组,写操作在新数组上进

行,读操作在原数组上进行
  2.并且,写操作会加锁,防止出现并发写入丢失数据的问题
  3.写操作结束之后会把原数组指向新数组
  4.CopyOnWriteArraylist允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnWriteArayList会比较占
内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景

 

20、什么是字节码  采用字节码的好处

  •   编译器javac 将Java源文件(java)文件编译成为字节码文件(*.clss),可以做到一次编译到处运行,windows上编译好的class文件,可以直接在linux上运行,通过这种方式做到跨平台,不过lava的跨平台有一个前提条件,就是不同的操作系统上安装的JDK或JRE是不一样的,虽然字节码是通用的,但是需要把字节码解释成各个操作系统的机器码是需要不同的解释器的,所以针对各个操作系统需要有各自的JDK或JRE。
  •   采用字节码的好处,一方面实现了跨平台,另外一方面也提高了代码执行的性能,编译器在编译源代码时可以做一些编译期的优化,比如锁消除、标量替换、方法内联等。

 

21、Java中的异常体系是怎样的

  •    Java中的所有异常都来自顶级父类Throwable。
  •   Throwable下有两个子类Exception和Error。
  •   Eror表示非常严重的错误,比如java.lang .StackOverFlowError和Javalang.OutOfiMemoyfError,通常这些错误出现时,仅仅想靠程序自己是解决不了的,可能是虚拟机、磁盘、操作系统层面出现的问题了,所以通常也不建议在代码中去捕获这些Error,因为捕获的意义不大,因为程序可能已经根本运行不了了。
  •   Exception表示异常,表示程疗出现Exception时,是可以靠程序自己来解决的,比如NulPointerException、llegalAccessException等,我们可以捕获这些异常来做特殊处理。
  •   Exception的子类通常又可以分为RuntimeException和非RuntimeException两类
  •   RunTimeException表示运行期异常,表示这个异常是在代码运行过程中抛出的,这些异常是非检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误吴引起的,程序应该从逻辑角度尽可能避免这类异常的发生,比如NulPointerException、IndexOutOfBoundsException等。
  •   非RuntimeException表示非运行期异常,也就是我们常说的检查异常,是必须进行处理的异常,如果不处理,程序就不能检查异常通过。如IOException、SQLException等以及用户自定义的Exception异常。

  

22、Java中有哪些类加载器

   JDK自带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader。

  •    BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件。
  •   ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件夹下的jar包和class类。
  •   AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。

 

23、说说类加载器双亲委派模型

  1. BootstrapClassLoader

  2. ExtClassLoader

  3. AppClassLoader


AppClassLoader的父加载器是ExtClassLoader,ExtClassLoader的父加载器是BootstrapClassLoader。
JVM在加载一个类时,会调甲AppClissLoadef的loadClass方法来加载这个类,不过在这个方法中,会先使用ExtClassLoader的loadClass方法来加载类,同样ExtClassloader的loadClass方法中会先使用BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果BootstrapClassLoader没有加载到,那么ExtClassLoader就会自己尝试加载该类,如果没有加载到,那么则会由ApplassLoader来加载这个类。


所以,双亲委派指得是,JVM在加载类时,会委派给Ext和Bootstrap进行加载,如果没加载到才由自己进行加载。

24、JVM中哪些是线程共享区

  堆区和方法区是所有线程共享的,栈、本地方法栈、程序计数器是每个线程独有的

 

 

 

 

25、一个对象从加载到JVM,再到被GC清除,都经历了什么过程?

  • 1.首先把字节码文件内容加载到方法区
  • 2.然后再根据类信息在堆区创建对象
  • 3.对象首先会分配在堆区中年轻代的Eden区,经过一次Minor GC后,对象如果存活,就会进入Swivor区。在后续的每次Minor GC中,如果对象一直存活,就会在Suvivor区来回拷贝,每移动一次,年龄加1
  • 4.当年龄超过15后,对象依然存活,对象就会进入老年代
  • 5.如果经过Full GC,被标记为垃圾对象,那么就会被GC线程清理掉

 

26、你们项目如何排查JVM问题

对于还在正常运行的系统:

  • 1.可以使用jmap来查看JVM中各个区域的使用情况
  • 2.可以通过jstack来查看线程的运行情况,比如哪些线程阻塞、是否出现了死锁
  • 3.可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc比较频繁,那么就得进行调优了
  • 4.通过各个命令的结果,或者jvisualvm等工具来进行分析
  • 5.首先,初步猜测频繁发送fulec的原因,如果频繁发生fulgc但是又一直没有出现内存溢出,那么表示fullgc实际上是回收了很多对象了
  • 所以这些对象最好能在younggc过程中就直接回收掉,避免这些对象进入到老年代,对于这种情况,就要考虑这些存活时间不长的对象是不是比较大,导致年轻代放不下,直接进入到了老年代,尝试加大年轻代的大小,如果改完之后,fullgc减少,则证明修改有效
  • 6.同时,还可以找到占用CPU最多的线程,定位到具体的方法,优化这个方法的执行,看是否能避免某些对象的创建,从而节省内存

 

27、怎么确定一个对象到底是不是垃圾?

  1.引用计数算法:这种方式是给堆内存当中的每个对象记录一个引用个数。引用个数为0的就认为是垃圾。这是早期JDK中使用的方式。引用计数无法解决循环引用的问题。
  2.可达性算法:这种方式是在内存中,从根对象向下一直找引用,找到的对象就不是垃圾,没找到的对象就是垃圾。

 

28、JVM有哪些垃圾回收算法?

1.标记清除算法:
  a.标记阶段:把垃圾内存标记出来b.清除阶段:直接将垃圾内存回收。
  c.这种算法是比较简单的,但是有个很严重的问题,就是会产生大量的内存碎片。
2.复制算法:为了解决标记清除算法的内存碎片问题,就产生了复制算法。复制算法将内存分为大小相等的两半,每次只使用其中一半垃圾回收时,将当前这一块的存活对象全部拷贝到另一半,然后当前这一半内存就可以直接清除。这种算法没有内存碎片,但是他的问题就在于浪费空间。而且,他的效率跟存活对象的个数有关。
3.标记压缩算法:为了解决复制算法的缺陷,就提出了标记压缩算法。这种算法在标记阶段跟标记清除算法是一样的,但是在完成标记之后,不是直接清理垃圾内存,而是将存活对象往一端移动,然后将边界以外的所有内存直接清除。

 

29、什么是STW?

  STWW: Stop-The-World,是在垃圾回收算法执行过程当中,需要将JVM内存冻结的一种状态。在STW状态下,JAVA的所有线程都是停止执行的,GC线程除外,native方法可以执行,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。

 

30、JVM调优

  1、GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的一个重点

  2、增大younggc大小

 

31、JVM参数有哪些?

  JVM参数大致可以分为三类:
    1.标注指令:-开头,这些是所有的HotSpot都支持的参数。可以用java -help打印出来。
    2.非标准指令:-X开头,这些指令通常是跟特定的HotSpot版本对应的。可以用java -X打印出来。

    3.不稳定参数: -XX开头,这一类参数是跟特定HotSpot版本对应的,并且变化非常大。

 

posted @ 2022-09-18 11:14  Homnay  阅读(30)  评论(0)    收藏  举报