《互联网大厂Java面试:核心知识大考验》

30 阅读8分钟

互联网大厂Java面试:核心知识大考验

面试官:好了,我们开始面试。第一轮,先问你几个基础的Java核心知识问题。首先,Java 中数据类型分为哪几类?

王铁牛:分为基本数据类型和引用数据类型。基本数据类型像int、double这些,引用数据类型就是类、接口、数组这些。

面试官:回答得不错。那String 是基本数据类型吗?

王铁牛:不是,它是引用数据类型。

面试官:很好。最后一个问题,简述一下Java 中的多态。

王铁牛:多态就是同一个行为具有多个不同表现形式或形态的能力。比如一个父类的引用指向它的子类对象,调用同一个方法时会根据对象的实际类型执行不同的行为。

面试官:第一轮表现不错,接下来第二轮,我会问一些关于JUC和多线程的问题。首先,什么是CAS?

王铁牛:CAS就是比较并交换,通过比较内存值和预期值是否相等,如果相等就更新为新值。

面试官:那它有什么缺点?

王铁牛:嗯……它可能会导致ABA问题吧。

面试官:还算说对了。最后一个,讲讲如何解决多线程中的死锁问题。

王铁牛:可以通过避免锁的嵌套、设置锁的超时时间、使用死锁检测算法来解决。

面试官:第二轮整体表现还行,进入第三轮,这轮问题会更复杂一些,关于JVM、线程池、HashMap这些。首先,JVM的内存结构分为哪几个部分?

王铁牛:有堆、栈、方法区、程序计数器、本地方法栈。

面试官:那堆内存又分为哪几块?

王铁牛:年轻代、老年代、永久代……哦不,现在是元空间了。

面试官:最后一个问题,简单说下线程池的工作原理。

王铁牛:就是有个线程队列,提交的任务先放队列里,线程池里的线程从队列取任务执行。

面试官:好的,面试就到这里,回去等通知吧。

答案:

  • Java 中数据类型分为哪几类:分为基本数据类型和引用数据类型。基本数据类型包括整型(如 byte、short、int、long)、浮点型(如 float、double)、字符型(char)、布尔型(boolean)。引用数据类型指的是类(class)、接口(interface)、数组(array)等。基本数据类型存储的是具体的值,而引用数据类型存储的是对象的引用。
  • String 是基本数据类型吗:不是,String 是引用数据类型。它是一个类,在 Java 中一切皆对象,所以 String 是对象的一种,属于引用数据类型。它用于存储和操作文本数据。
  • 简述一下Java 中的多态:多态是指同一个行为具有多个不同表现形式或形态的能力。在Java中,多态主要体现在方法的重写和重载上。当一个父类的引用指向它的子类对象时,调用同一个方法会根据对象的实际类型执行不同的行为。这使得代码具有更好的扩展性和灵活性。例如,有一个父类Animal,子类Dog和Cat都继承自Animal并重写了父类的叫声方法bark(),当使用Animal类型的引用指向Dog或Cat对象时,调用bark()方法会根据实际对象是Dog还是Cat来执行相应的叫声。
  • 什么是CAS:CAS即比较并交换(Compare and Swap),它是一种用于实现乐观锁的机制。在内存中,它通过比较内存值和预期值是否相等,如果相等就将内存值更新为新值,并返回更新成功;如果不相等,则返回更新失败。这种机制常用于多线程环境下对共享变量的原子操作,避免了传统锁机制带来的性能开销。例如在Java的Atomic类中广泛使用了CAS操作来实现原子性的变量更新。
  • CAS有什么缺点:CAS主要有一个比较明显的缺点就是可能会导致ABA问题。当一个变量的值从A变为B,然后又变回A时,CAS操作可能会认为这个值没有发生变化,但实际上它已经被修改过了。例如在一个链表节点的操作中,如果存在ABA问题,可能会导致程序出现逻辑错误。解决ABA问题可以使用版本号或者时间戳等机制,在比较值的同时也比较版本号或时间戳,只有当值和版本号都符合预期时才进行更新。
  • 如何解决多线程中的死锁问题
    • 避免锁的嵌套:尽量避免在一个线程中同时获取多个锁,防止形成锁的嵌套,从而避免死锁的发生。例如,一个线程不能在已经持有锁A的情况下再去请求锁B,而另一个线程持有锁B并请求锁A,这样就会导致死锁。
    • 设置锁的超时时间:当一个线程请求锁时,可以设置一个超时时间。如果在超时时间内没有获取到锁,线程可以放弃获取锁的操作,避免一直等待导致死锁。例如使用Lock接口的tryLock(long timeout, TimeUnit unit)方法来尝试获取锁,并设置超时时间。
    • 使用死锁检测算法:操作系统或运行时环境可以定期检测系统中是否存在死锁情况。例如通过资源分配图算法来检测是否存在环路,如果存在环路则表示可能存在死锁。一旦检测到死锁,可以通过终止一个或多个线程来打破死锁。在Java中,也有一些工具和库可以帮助检测和分析死锁情况,如jstack工具。
  • JVM的内存结构分为哪几个部分:JVM的内存结构主要分为以下几个部分:
    • 程序计数器:是一块较小的内存空间,它记录着当前线程所执行的字节码的行号指示器。每个线程都有一个独立的程序计数器,它是线程私有的。
    • Java 虚拟机栈:也是线程私有的,它描述的是Java方法执行的内存模型。每个方法在执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
    • 本地方法栈:与虚拟机栈类似,它用于支持native方法的执行。
    • :是Java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,几乎所有的对象实例都在这里分配内存。
    • 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在Java 8及以后,方法区被元空间(MetaSpace)所取代,元空间使用的是本地内存。
  • 堆内存又分为哪几块:堆内存主要分为年轻代、老年代和永久代(Java 8及以后为元空间)。
    • 年轻代:是新对象产生的地方,它又分为 Eden 区和两个 Survivor 区(一般是S0和S1)。新创建的对象首先会在 Eden 区分配内存,当 Eden 区满了之后,会触发一次 Minor GC(新生代垃圾回收),存活下来的对象会被移动到其中一个 Survivor 区。
    • 老年代:存放经过多次 Minor GC 后仍然存活的对象。当 Survivor 区空间不够用时,对象会直接晋升到老年代。老年代的垃圾回收频率较低,但每次回收的时间较长,通常使用 Major GC 或 Full GC 来进行回收。
    • 永久代(Java 8及以后为元空间):在Java 8之前,永久代用于存储类的元数据信息,如类的结构、方法、常量池等。Java 8及以后,引入了元空间,它使用本地内存来存储类的元数据,将字符串常量池和静态变量等从永久代移到了堆中。
  • 简单说下线程池的工作原理:线程池的工作原理如下:
    • 核心线程池:线程池创建时会初始化一定数量的核心线程。当有任务提交时,核心线程会从任务队列中获取任务并执行。如果核心线程都在忙碌,任务会被放入任务队列中等待执行。
    • 任务队列:用于存放提交的任务,当核心线程忙碌时,新提交的任务会被放入任务队列中。常见的任务队列有ArrayBlockingQueue、LinkedBlockingQueue等。
    • 最大线程数:线程池允许存在的最大线程数量。当任务队列已满且核心线程都在忙碌时,会创建新的线程来执行任务,但线程数量不能超过最大线程数。
    • 非核心线程:超过核心线程数的线程就是非核心线程。非核心线程在空闲一段时间后(通过keepAliveTime和unit指定)会被销毁。
    • 拒绝策略:当任务队列已满且线程数达到最大线程数时,会触发拒绝策略。常见的拒绝策略有AbortPolicy(直接抛出异常)、CallerRunsPolicy(调用者线程执行任务)、DiscardPolicy(丢弃新提交的任务)、DiscardOldestPolicy(丢弃任务队列中最旧的任务)。例如,当使用ThreadPoolExecutor创建线程池时,可以通过构造函数传入不同的任务队列和拒绝策略来定制线程池的行为。