Java面试基础问题

Java基础问题

0、Java源码的编译过程?(华为)

源代码-> 词法分析器 -> 语法分析器 -> 语义分析器 -> 字节码生成器

0.1、jvm的作用?

保证Java一次编译到处运行,屏蔽了机器底层机器码。保证Java不面向任何的处理器而只是面向于虚拟机。

0.2、Java如何跳出多重循环?(华为)


        String a1 = "";
        String b1 = "";
        here:
        for (int i = 1; i <= 4; i++) {
            a1 = "外层循环第"+i+"层";
            for (int j = 1; j <= 4; j++) {
                b1 = "内层循环第"+j+"层";
                if (2 == j & 2 == i) {
                    break here;
                }
            }
        }

1、Hash为什么要右移16位异或?(美团)(滴滴)

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 }

首先这个方法的返回值还是一个哈希值。为什么不直接返回key.hashCode()呢?还要与 (h >>> 16)异或。

先看一个例子:

h = key.hashcode() 1111 1101 1101 1111 0101 1101 0010 1111
^
h >>> 16           0000 0000 0000 0000 1111 1101 1101 1111
----------------------------------------------------------
h ^ (h >>> 16)     1111 1101 1101 1111 1010 0000 1111 0000
h = key.hashcode() 1111 1101 1101 1111 0101 1101 0010 1111  

对比h = key.hashcode()h ^ (h >>> 16) 发现,将h无符号右移16为相当于将高区16位移动到了低区的16位,再与原hashcode做异或运算,可以将高低位二进制特征混合起来。例子中可以看出高区的16位并没有变化。低区的16位发 生了较大的变化。这样做的目的是什么呢?

我们计算出的hash值在后面会参与到元素index的计算中。计算公式为 hash & (length - 1)。

仔细观察上文不难发现,高区的16位很有可能会被数组槽位数的二进制码锁屏蔽,如果我们不做刚才移位异或运算,那么在计算index时将丢失高区特征。如果没有上面这个异或操作,假设里两个hash值只有高位一点点的差异,然后在计算index过程中还丢失了高位的信息,那么就计算出同一个index。这也是将性能做到极致的一种体现!!!

使用异或运算的原因?(美团)

异或运算能更平均的保留各部分的特征,如果采用**&运算计算出来的值会向1靠拢,采用|**运算计算出来的值会向0靠拢

为什么槽位数必须使用2^n?(美团)

  1. 为了让hash后的结果更加均匀

    如果 length = 17 那么 hash & (17 - 1) 。16转化为二进制包含更多的0,这样一来计算会被更多的0屏蔽。

  2. 便于扩容后的重新计算index。

为什么扩容时总是把capacity扩大为原来的2倍?

由于我们要维护hashmap的大小为2^n,这样就使得len-1的二进制中全部都是1。进行位运算时可以降低hash碰撞的出现。

HashMap的负载因子为什么是0.75?(美团)

负载因子主要与扩容有关,如果将负载因子设置为1,空间利用的就更加充分了,但是这样一来会增大hash碰撞的出现,有些位置的链表会过长,不利于查找。如果设置的过小的话虽然降低了hash碰撞的发生,但是会频繁触发扩容机制。

所以为了折中,将负载因子设置为0.75是对空间与时间的取舍。

HashMap的线程安全问题?(滴滴)

1.多线程的put操作可能导致元素丢失

2.put和get并发时可能导致get为null

  • 线程1执行put时,因为元素个数超出threshold而导致rehash,线程2此时执行get,有可能导致这个问题。

3.jdk 1.7 中并发put导致的循环链表导致get出现死循环

  • 发生在多线程并发resize的情况下可能会导致环形链表的出现。

hashmap的哈希冲突可以通过用多个hash函数解决吗?(滴滴)

不能,只是能降低冲突的概率,完全解决冲突是不可能的。

解决hash冲突的方法?(美团)

1.拉链法(链地址):

**2.线性探测法:**冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

**3.二次探测法:**冲突发生时,在表的左右进行跳跃式探测,比较灵活。

2、Java多线程编程时有哪几种线程间通信方式?(跟谁学)

  1. 共享内存法

    volatile,synchronized

  2. wait/notify机制

    来自Object类的方法。当满足某种情况时A线程调用wait()方法放弃CPU时间片,并进入阻塞状态。当满足某种条件时,B线程调用notify()方法通知A线程。唤醒A线程,并让它进入可运行状态。

  3. Lock/Condition机制

    Condition是Java提供了来实现等待/通知的类,Condition类还提供比wait/notify更丰富的功能,Condition对象是由lock对象所创建的。但是同一个锁可以创建多个Condition的对象,即创建多个对象监视器。这样的好处就是可以指定唤醒线程。notify唤醒的线程是随机唤醒一个。

3、volatile如何实现内存可见性?(美团)(字节跳动)

volatile为什么会出现:(字节跳动)

首先先分析一下没有volatile的情况下线程在自己的私有内存中对共享变量做出了改变之后无法及时告知其他线程,这就是volatile的作用,解决内存可见性问题。这种问题用synchronized关键字可以解决。但是一个问题是synchronized是重量级锁,同一时间内只允许一个线程去操作共享变量。操作完成之后在将改变后的变量值刷新回共享内存空间中。这样一来的话并发性就没有了。而且synchronized关键词的使用基于操作系统实现,会使得线程从用户态陷入内核态。这一步是很耗时间的。于使volatile应运而生。它是一个轻量级的synchronized。只是用来解决内存可见性问题的。

1、volatile可见性实现原理:(字节跳动)

变量被volatile关键字修饰后,底层汇编指令中会出现一个lock前缀指令。会导致以下两种事情的发生:

  1. 修改volatile变量时会强制将修改后的值刷新到主内存中。
  2. 修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。

2、volatile有序性实现原理:(字节跳动)

**指令重排序:**编译器在不改变单线程程序语义的前提下,重新安排语句的执行顺序,指令重排序在单线程下不会有问题,但是在多线程下,可能会出现问题。

volatile有序性的保证就是通过禁止指令重排序来实现的。指令重排序包括编译器和处理器重排序,JMM会分别限制这两种指令重排序。禁止指令重排序又是通过加内存屏障实现的。

// 内存屏障(memory barriers):一组处理器指令,用于实现对内存操作的顺序限制。

添加了volatile关键字可以避免半初始化的指令重排。

3.2、volatile为什么不保证原子性?

java中只有对变量的赋值和读取是原子性的,其他的操作都不是原子性的。所以即使volatile即使能保证被修饰的变量具有可见性,但是不能保证原子性。

3.3、了解CountDownLatch吗?

闭锁可以用来确保某些活动直到其他活动全部结束之后才进行;

主要包含两个方法,一个是countDown(),一个是await();以及一个计数器变量cntcountDown() 方法用来给计数器cnt减一;
await() 方法是用来阻塞当前线程,直到计数器为0的时候在唤醒线程继续执行;

4、了解Semaphore吗?

信号量,用于多个共享资源的互斥使用,也可以用来控制线程的并发量,类似于线程池的作用。

可以用于限制线程的并发数。

5、Thread Local 作用、原理、内存泄漏问题?(字节跳动)(滴滴)

又叫线程本地变量、或线程本地存储。

作用:

ThreadLocal为解决多线程下的线程安全问题提供了一个新思路,它通过为每一个线程提供一个独立的变量副本解决了线程并发访问共享变量出现的安全问题。在很多情况下ThreadLocal比直接使用synchronized同步机制解决线程安全问题更加方便、简洁。且拥有更加高的并发性。

原理:

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
  1. 在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal的引用,value为变量副本(即T类型的变量)。
  2. 初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal对象引用为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals
  3. 然后在当前线程里面,如果要使用副本变量,就可以通过get()方法在threadLocals里面查找。

ThreadLocal内存泄漏

由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时(无论是否OOM)弱引用Key会被回收。这个时候就会出现Entry中Key已经被回收,出现一个null Key的情况,外部读取ThreadLocalMap中的元素是无法通过null Key来找到Value的。因此如果当前线程的生命周期很长,一直存在,那么其内部的ThreadLocalMap对象也一直生存下来,这些null key就存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,这条强引用链会导致Entry不会回收, Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏

**解决办法:**每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

ThreadLocal应用场景

最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。

6、jvm模型?(字节跳动)(美团)(滴滴)

分为 线程私有部分线程共有部分

私有部分:虚拟机栈、本地方法栈(JNI)、程序计数器。

共有部分:方法区、堆(年轻代、老年代)。

7、类的实例化顺序?

  1. 父类static修饰的方法变量、子类static修饰的方法变量。
  2. 父类的普通逻辑,父类的构造函数
  3. 子类的普通逻辑,子类的构造函数

8、几种垃圾收集算法?(太多了)

**标记清除:**会产生大量的内存碎片

**复制算法:**用于新生代的垃圾回收。不会产生内存碎片,但是会浪费约10%的内存空间。

**标记压缩(清除):**用于老年代的垃圾回收。耗时较长,但是不会产生内存碎片也不会浪费空间。

分代收集:

9、常用的垃圾收集器?(太多了)

serial:年轻代单线程串行垃圾收集器

CMS:老年代多线程并行垃圾收集器

  1. 初始标记(STW)GC root的直接引用
  2. 并发标记,标记所有GC root可达的引用对象(无STW)
  3. 重新标记(STW)这一步为了标记在并发标记期间由于工作线程工作带来的垃圾
  4. 并发清除。

缺点是 由于以最短停顿为目标,所以会导致吞吐量低。标记清除算法带来的内存碎片问题。

G1:填补了CMS的不足,是当前服务端最优的垃圾收集器。通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。

**初始标记(STW)、并发标记、最终标记(STW)、筛选清除。**G1收集过程中整体来看基于标记整理算法,局部来看基于复制算法。所以在收集垃圾过程中不会产生垃圾碎片。

10、AQS内部如何控制并发?(字节跳动)

AQS(AbstractQueuedSynchronizer)J.U.C包下lock实现的核心。主要是其提供的一个FIFO的队列来维护获取锁失败而进入阻塞的线程,以及一个volatile关键字修饰的state变量表示当前同步状态。当一个线程获取到同步状态(修改state=1),那么其他线程便无法获取,转而被构造成节点并加入同步队列中。**加入队列的过程基于CAS算法。即比较当前线程认为的尾节点与当前节点,比较成功后才能正式加入队列尾部。**队列头节点表示的为当前正在运行的线程,该线程执行结束后会激活它下面的一个线程进入执行状态。

FIFO同步队列控制并发。

11、解释对象创建的过程?(美团)

  1. 申请堆内存(半初始化,变量未赋值)
  2. 变量赋值
  3. 建立引用关系

注意区分对象创建问题类加载过程问题!

12、DCL单例为什么需要加vloatile (半初始化的指令重排)?(滴滴)(字节跳动)

/**
单例设计模式---饿汉
缺点:上来就初始化了一个对象,浪费资源
**/ 
public class test {
    // 静态的对象实例
    private static test instance = new test();
    private test() {};
    // 供外界调用的获取实例的方法
    public static getInstance () {
        return instance;
    }
}
/**
单例设计模式---懒汉 解决了浪费资源的问题但是又带来了线程不安全问题。

**/ 
public class test {
    private static test instance; 
    private test() {};
    public static getInstance () {
        if (instance == null) {
            instance = new test();
        }else {
            return instance;
        }
    }
}
// 单例设计模式---懒汉 加锁保证线程安全
public class test {
    private static test instance; 
    private test() {};
    // synchronized 保证线程安全,缺点锁粒度太大,影响性能
    public static synchronized getInstance () {
        if (instance == null) {
            instance = new test();
        }else {
            return instance;
        }
    }
}
// 单例设计模式---懒汉  加锁 并双重锁定检查(Double Check Lock){DCL单例}
// 注意:多线程环境下的指令重排可能会产生问题
 public class test {
    private static volatile test instance; 
    private test() {};
    // synchronized 保证线程安全,缺点锁粒度太大,影响性能
    public static getInstance () {
        if (instance == null) { //第一次检查
            synchronized (test.class) {
                if (instance == null) { // 第二次检查
                    instance = new test(); 
                }
            }
        }else {
            return instance;
        }
    }
}

volatile两个作用:保持线程可见性;禁止指令重排序

DCL单例需要加volatile,来禁止指令重排。

由于由于java编译器允许处理器乱序执行(以获得最优的性能),new对象的操作不是原子性的。这句代码最终会被编译成多条汇编指令。所以需要volatile关键字来禁止指令重排。

**创建一个对象的过程中一旦出现了指令重排,可能就会获得半初始化的对象,**即还没来得及赋值就先建立了引用关系。要避免这种情况的发生就要使用volatile关键字修饰实列变量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DgSbilSK-1628220678309)(https://github.com/lokles/Web-Development-Interview-With-Java/blob/main/images/1595146749088.png)]

13、对象在内存中的存储布局?(美团)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gs7zsy87-1628220678312)(https://github.com/lokles/Web-Development-Interview-With-Java/blob/main/images/1595147242606.png)]

mark wordclass pointer 组成对象头class pointer指向对象的类。padding作用是将对象在内存中占用的字节数补齐到被8整除。(64位OS下)

13.2 、追问,对象头具体包括什么?(有赞)(跟谁学)

主要包括锁的信息,有一个锁升级的过程。还记录了对象的年龄,四位二进制,超过16岁进入老年代。以及类指针

在synchronized大(优化)升级之前,是重量级锁,锁操作都要经过OS。向OS内核去申请。(jdk1.5之后)到现在的synchronized是有一个复杂的锁升级过程。

无锁 -> 偏向锁 -> 自旋锁(轻量级锁) -> (重量级锁)悲观锁。

以上的升级状态都记录在对象头中。

**偏向锁:**hotspot虚拟机认为大多数时间是不存在锁竞争的,所以每次都会把锁分配给上一次获得锁的线程,直到出现了锁竞争。

**自旋锁:**线程之间以CAS的方式进行锁资源的争抢。当一个线程自旋超过了10次或者当前自旋等待的线程超过了CPU核数的1/2(升级后优化为自适应自旋),会进行锁升级。

synchronized: 向OS申请资源,从用户态切换到内核态。线程挂起进入等待队列,等待OS的调度。然后再映射回用户空间。

14、对象如何定位?(美团)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8YcrVmZm-1628220678315)(https://github.com/lokles/Web-Development-Interview-With-Java/blob/main/images/1595149214138.png)]

hotspot使用的是直接指针方式。

15、对象如何分配?(shopee)(美团)

  1. 栈上分配:需要满足标量替换以及无逃逸(在一个方法中使用)。比在堆中分配时间快一倍。且无需GC回收。方法执行结束自动出栈。

  2. **堆中分配:**无法进行栈上分配:判断对象个头,大对象直接入老年代。否则在伊甸区分配。伊甸区分配前先判断是否符合线程本地分配(由于线程争先恐后的在内存中分配,会加锁,效率不高,所以JVM做了一级优化,直接将对象分配到线程的私有空间中,这一操作不需要锁)

    具体过程如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rxzYkvQM-1628220678318)(https://github.com/lokles/Web-Development-Interview-With-Java/blob/main/images/1595150434871.png)]

16、一个Object占多少字节?(shopee)

Object o = new Object();
// o普通对象指针(Oops)4 字节(开启压缩占 4 字节,没开启占 8 字节),object对象占 16 字节 

17、Java的线程模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SptMOplb-1628220678321)(https://github.com/lokles/Web-Development-Interview-With-Java/blob/main/images/1595152948319.png)]

线程与进程区别?

  • 资源分配:进程是资源分配的最小单位
  • 调度:线程是调度的最小单位
  • 通信:进程通信需要IPC
  • 创建:进程创建开销很大,线程创建的开销小。

进程与协程区别?

18、谈一下AQS,为什么底层使用CAS和volatile?(字节跳动)

  1. AQS源码中state状态值使用volatile修饰保证内存的可见性。因为涉及到多线程对state的修改,必须保证其对所有线程的可见性。
  2. CAS操作主要用于对state值的修改。

19、Synchronized与ReentrantLock的区别?(太多了)

实现原理上:

synchronized是依靠jvm以及配合操作系统来实现,是一个关键字reentrantLockjdk1.5之后提供的API层面的互斥锁。

使用便利性上:

synchronized只需要添加上相关关键字即可,加锁与释放过程由操作系统完成。reentrantLock则需要手动加锁与释放锁。

锁粒度与灵活度:

reentrantLock要强于synchronized

reentrantLock提供了三个高级功能:

  1. 等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。
  2. 多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
  3. 一个ReentrantLock对象可以同时绑定对个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

性能区别:

synchronized优化之后性能与reentrantLock已经不相上下了,官方甚至更建议使用synchronized关键字。

20、Synchronized实现原理?(美团)(字节)(滴滴)

要说清楚锁升级的过程。

每个对象(在对象头中)有一个监视器锁(monitor),当monitor被占用时就处于锁定状态。线程执行monitorenter(汇编指令)尝试获取monitor的所有权。

  1. 如果monitor计数器当前值为0,那么该线程进入monitor并将计数器加1,
  2. 如果当前monitor计数器值不为0,那么该线程阻塞并进入(OS维护的)队列等待,等到OS的调度。

底层字节码被编译成monitorentermonitorexit两个指令。线程执行monitorexit指令,monitor计数器减1,如果减到0了,表示当前线程不在拥有该监视器锁。等待队列中的线程有机会获得锁资源。

20.2、追问,你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁?(字节)

这个锁就是一个引用对象,也就是要加锁或者解锁的对象。

  1. 如果指明了锁对象如Synchronized(this) 则对this对象进行加锁

  2. 如果直接在方法上添加Synchronized,则锁定的是该方法所在对象

  3. 如果是对静态方法使用Synchronized,则是对静态方法所对应的类对象加锁

对一个对象加锁不影响对该对象其他方法的使用

20.3、追问,什么是可重入性,为什么说 Synchronized 是可重入锁?(字节)

重入性就是在一个同步方法中调用另一个同步方法,主要是为了防止自己把自己锁死的情况发生。

20.4、synchronized可重入锁的实现?

jvm对于重入锁的操作也很简单,在执行 monitorenter 指令时,如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁(而不是已拥有了锁则不能继续获取),就把锁的计数器 +1,其实本质上就通过这种方式实现了可重入性。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁

20.5、追问,为什么说 Synchronized 是非公平锁?

非公平是指在获取锁的行为上,并不是按照线程申请顺序进行分配的,当锁被释放后,所有线程都有机会获取到锁,这样提高了性能,但是可能会出现某些线程饥饿的情况。

21、什么是锁消除和锁粗化?

22、为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有什么特性?

当 Synchronized升级为重量级锁时,他是一个悲观锁。获取不到锁资源线程的线程由OS统一管理,涉及到用户态到内核态的切换。

乐观锁就是,当一个线程想要对变量进行操作时,先读取变量值,然后真正更改时会再次对当前值与自己之前读取的值是否相同,相同才会进行更改,不相同的话就会再次读取,然后在进行对比更改。主要是基于CAS实现。

CAS(compare and swap) :它涉及到3个操作数:1.内存值,预期值, 新值,只有当内存值和预期值相等的时候(证明没有其他线程在使用),才会将内存值设置为预期值。

CAS具有原子性,他的原子性由CPU保证,由JNI调用c++硬件代码实现,jdk中提供了unsafe来进行这些操作。

22.1、乐观锁一定就是好的吗?

不一定

  1. 乐观锁的情况下,如果线程并发度确实很高,那么大多数的线程都会处于自旋等待以获取锁对象的状态。这样会导致CPU占用过高。

  2. CAS另一个缺点就是ABA问题。一个值从A改为B又改为A,则CAS认为没有发生变化,解决的方式是使用版本号来记录操作次数。

23、ReentrantLock实现原理?

24、AQS原理?

AQS框架是用来构建锁的同步器框架,包括了常用的ReentrantLock,ReadWriteLock,CountDownLatch等都是基于AQS框架来实现的。

AQS使用一个FIFO队列表示排队等待锁的线程,队列头结点称作“哨兵节点”或者“哑结点”,它不与任何线程关联。其他的节点与等待线程关联,每个阶段维护一个等待状态waitStatus。

AQS中有一个表示状态的字段state,例如ReentrantLock用它来表示线程重入锁的次数,Semphore用它表示剩余的许可数量,FutureTask用它表示任务的状态。对state变量值的更新都采用CAS操作保证更新操作的原子性。

25、ReentrantLock 是如何实现可重入性的?

ReentrantLock内部持有了一个sync对象,这个对象实现了AQS,并且加锁的时候使用CAS算法,在所对象申请的时候,在锁等待node链表中查看当前申请的锁的对象是否是同一个对象,如果是的话,进行重入。

26、除了 ReetrantLock,你还接触过 JUC 中的哪些并发工具?

  • 提供了CountDownLatch CyclicBarrier Semaphore等更加高级的同步框架
  • 提供了currentHashMap,有序的ConcurrentSkipListMap等线程安全容器
  • 提供了针对各种场景的并发队列的实现
  • 强大的Executor框架,可以创建不同类型的线程池

27、如何让 Java 的线程彼此同步?你了解过哪些同步器?

28、Java中线程池是如何实现的?(滴滴)

java线程池中的对象被抽象成work对象,基于AQS,存放在线程池中的HashSet中,等待执行的任务则存放在成员变量workQueue中

29、线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗?(滴滴)

不是

30、既然提到可以通过配置不同参数创建出不同的线程池,那么 Java 中默认实现好的线程池又有哪些呢?

  • SingleThreadExecutor 线程池:只有一个线程在工作,也就是串行,如果线程出现问题,会有一个新的线程代替它
  • FixedThreadPool 线程池: 固定大小线程池,线程池大小达到最大就会保持不变
  • CachedThreadPool 线程池:无界线程池,SynchronousQueue 是一个是缓冲区为 1 的阻塞队列
  • ScheduledThreadPool 线程池: 核心线程固定,大小不限的线程池

31、如何在线程池中提交线程?

  1. execute():无返回值

  2. submit():返回Future对象。可以通过get()方法获取返回值。如果线程没有执行完成会阻塞。

32、动态代理是如何实现的?

直接使用InvocationHandler接口进行实现,同时利用Proxy类设置动态请求对象;使用CGLIB来避免对于代理设计模式需要使用接口实现的限制。

33、HashMap 为什么线程不安全?

HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入**(头插法)**。多线程环境下执行插入操作时,可能会发生多个线程同时获取了链表的头节点。可能会造成线程写入的操作被覆盖。

34、HashMap 对比ConcurrentHashMap?(网易)

HashMap:线程不安全,

ConcurrentHashMap:线程安全。JDK1.7之前由分段锁(继承了可重入锁)实现。JDK1.8之后由CAS+Synchronized实现。

35、LinkedHashMap了解吗?

一种可以实现LRU的数据结构。是有序的HashMap

36、wait()对比sleep()?(大华)(字节)

  1. wait()是object类的方法,sleep()是thread的方法
  2. 调用wait之后进入阻塞状态,同时会失去CPU时间片。而调用sleep()不会失去。

37、垃圾回收算法中是如何来判断垃圾的?

  1. 引用计数法:为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。
  2. 可达性分析:以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。

38、GC root包含什么?(字节)(有赞)(跟谁学)

  • 虚拟机栈空间中非静态变量表中引用的对象
  • 本地方法栈中 JNI 中引用的对象
  • 方法区中静态变量引用的对象
  • 方法区中的常量引用的对象

39、类的加载过程?

  1. 加载:加载class字节码文件
  2. 验证:验证字节码文件中是否会拟机安全的
  3. 准备:准备阶段为类变量分配内存并设置初始值,使用的是方法区(jdk 1.8 元空间实现)的内存。
  4. 解析:将常量池的符号引用替换为直接引用的过程。
  5. 初始化:初始化阶段才真正开始执行类中定义的 Java 程序代码。

40、类加载器的分类?

启动类加载器:

扩展类加载器:

应用程序类加载器:

41、双亲委任机制?

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

优点

  1. 避免类的重复加载
  2. 保护程序安全,防止核心API被破坏。

42、JVM常用的参数?(美团)

初始堆内存 -Xms

最大堆内存 -Xmx

元空间 -XX MetaSpaceSize

新生代初始内存 -XX NewSize

新生代最大内存 -XX MaxNewSize

设置栈内存:-Xss

43、JVM 加载 Class 文件的原理机制?

以双亲委任制的方式去加载class文件。

44、垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?(滴滴)

垃圾回收器不会马上释放内存空间,而是在某个对象被标记为垃圾之后的下一次GC时才会对内存空间进行释放。

我们可以手动执行System.gc(),通知虚拟机进行GC,但是Java语言规范并不保证GC一定会执行。

45、深拷贝和浅拷贝?

浅拷贝:原始对象的引用与副本对象的引用指向堆中的同一个对象。

深拷贝:将原始对象完整复制一份放到堆内存中,原始对象的引用与副本对象的引用指向堆中的不同对象。

Object 类提供的 clone( ) 只能实现浅拷贝。

46、System.gc() 和 Runtime.gc() 会做什么事情?

调用System.gc()方法会通知jvm进行垃圾回收,但是并不保证一定会垃圾回收。

每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime 方法获取当前运行时。 Runtime.getRuntime().gc()Runtime.gc()System.gc()并没有实质性的区别,唯一的区别就是前者比较好写一点儿。

47、finalize() 方法什么时候被调用?

当一个对象不再被引用时,GC过程中会调用该对象的finalize() 方法(继承自Object类)对该对象进行回收。

48、JVM 的永久代中会发生垃圾回收么?

会,主要是对常量池以及类的卸载。对类的卸载需要满足三个条件。

  • 该类所有的实例都已经被回收,此时堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。

49、什么是分布式垃圾回收(DGC)?它是如何工作的?

50、为什么集合类没有实现 Cloneable 和 Serializable 接口?(跟谁学)

Cloneable标识一个类可以被克隆,Serializable标识一个类可以被序列化。

集合类没有实现这两个接口,但是集合类的具体实现类实现了这两个接口。集合类接口不是具体的容器,所以不需要实现这两个接口,没有任何意义。

51、Iterator 和 ListIterator 的区别是什么?

Iterator可以用来遍历Set、List。 ListIterator只能用来遍历List 。

ListIterator实现了Iterator,在Iterator的基础上有了更强大的功能,比如增加、替换元素。还支持向前遍历。

52、原子类的实现原理?

atomic 主要利用 CAS (Compare And Swap) 和 volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。

53、JavaIO 流中使用了哪些设计模式?

装饰模式和适配器模式

54、主线程会等待其他线程执行完吗?

不会,其他线程不受主线程结束的影响。

55、gc时间是否可控?

单次GC的时间其实是不可控的,但是取了平均值,GC就可以动态去调整heap的大小,或者其他的一些GC参数,从而保证每次GC的时间不会超过这个平均值。

56、垃圾收集算法中的复制算法会有s1区空间不够的情况吗?(跟谁学)

IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性的复制到另外一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性的复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。

57、虚拟机栈包含什么?(跟谁学)

虚拟机栈空间以栈帧为基本单位

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟街运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表操作数栈动态链接方法返回地址等信息。每一个方法从调用开始到执行完成的过程,都是一个栈帧在虚拟机栈里面从出栈到入栈的过程。

58、四种引用介绍一下?

不同的引用类型,主要体现的是对象不同的可达性状态和对垃圾收集的影响。

强、软、弱、虚

59、java 内存模型?(很长问)

Java 内存模型试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。

60、堆、栈内存溢出问题的排查与解决方法?(滴滴)

堆内存溢出原因:heap空间吃紧。排查:检查是否内存溢出,检查代码是否有死循环、递归等操作。最后考虑使用 -Xmx增加堆的大小。

栈溢出原因:栈帧太大或者虚拟机栈空间太小,当内存无法分配时会导致StackOverflowError 异常。

查找关键报错信息,确定是StackOverflowError还是OutOfMemoryError
如果是StackOverflowError,检查代码是否递归调用方法等。
如果是OutOfMemoryError,检查是否有死循环创建线程等,通过-Xss降低的每个线程栈大小的容量。

61、不使用额外的变量完成交换两个变量数值?(有赞)

利用异或性质。

public class 交换两个变量 {
    public static void main(String[] args) {
        int a = 2;
        int b = 3;
        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
        System.out.println(a);
        System.out.println(b);
    }
}

62、线上生产如何尽量避免Full GC的出现?(有赞)

  1. 禁止System.gc()方法的调用,因为该方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存。
  2. 老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:
    java.lang.OutOfMemoryError: Java heap space
    为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

63、java中有CAS的实现吗?

JUC包下的原子类都是基于CAS操作的。JAVA中的CAS操作都是通过sun包下Unsafe类实现,而Unsafe类中的方法都是native方法,由JVM本地实现。Unsafe中对CAS的实现是C++写的,从上图可以看出最后调用的是Atomic:comxchg这个方法,这个方法的实现放在hotspot下的os_cpu包中,说明这个方法的实现和操作系统、CPU都有关系。

64、TreeMap与HashMap与HashTable的区别?

  1. HashMap线程不安全,TreeMap与HashTable是线程安全的。

64、线程池构造?(滴滴)

64.1、为什么有了核心线程数参数还需要最大线程数参数?(滴滴)(美团)

基于性能考虑,核心线程数的设置与日常流量有关。最大线程数与**最大峰值流量(如秒杀场景下)**有关,超过最大线程数后反而会导致机器的性能变低。并且合理的设置阻塞队列的长度。

64.2、等待队列的有界、无界了解吗?(滴滴)

阻塞队列的几种实现:

LinkedBlockingQueue
SynchronousQueue
ArrayBlockingQueue;

根据线程池的运行原理,如果选用了无界的等待队列,那么会导致最大线程数参数失效。因为队列永远不会装满,我们的服务器面对高并发时也就无法发挥最优性能。

与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。

64.3、你知道线程池为什么这样设计吗?(滴滴)

线程池这样设计实际上是构建了一个生产者消费者模型,它将线程和任务两者解耦,**从而良好的缓冲任务,复用线程。**线程池的运行分为两大部分,任务管理、线程管理。

任务管理充当生产者,当任务提交后,线程池会判断该任务后续的流转:(1)直接申请线程执行该任务;(2)缓冲到队列中等待线程执行;(3)拒绝该任务。

线程管理充当消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。

64.4、线程数的设置与IO时间以及cpu执行时间的一个关系?(美团)

阿姆达尔定律:

设置的线程数 = CPU 核数 * (1 + IO time / CPU computing time)

举例说明,假设4核 CPU,每个任务中的 IO 任务占总任务的80%,CPU时间占用20%。则线程数应设置为:4 * (1 + 4) = 20个线程,这里的20个线程对应的是4核心的 CPU。

队列大小 = 线程数 * (目标相应时间/任务实际处理时间)等待队列一定要使用有界队列,否则会拖垮整个系统。

65、Java是编译型语言还是解释型语言?(华为)

半解释半编译。首先由Javac将Java文件编译为class文件,然后由jvm解释执行。

66、说说你对Error和Exception的理解?(华为)

首先两者继承自Throwable类,Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。 Error 是指程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。

异常分为 运行时异常其他异常

运行时异常(不受检异常):空指针异常、数组索引越界异常、

其他异常(受检异常):例如 IOException 使用 try catch finally 进行处理

67、谈一下反射?(网易)

java语言本身是静态类型的语言,但是由于有了反射机制使得java具有了一定的动态特性。

反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。

反射是动态代理的基础,动态代理提供了运行时的代理模式。是Spring AOP的实现方式。

68、TreeMap与LinkedHashMap的有序性区别?(华为)

LinkedHashMap 通常提供的是遍历顺序符合插入顺序,它的实现是通过为条目(键值对)维护一个双向链表。注意,通过特定构造函数,我们可以创建反映访问顺序的实例,所谓的 put、get 都算作访问。

对于 TreeMap,它的整体顺序是由键的顺序关系决定的,通过 Comparator 或Comparable(自然顺序)来决定。

69、谈谈面向对象语言和面向过程语言的区别?(华为)

**面向过程:**面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。

**面向对象:**面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。

代码运行时 JVM(Java 虚拟机)出现的问题。

异常分为 运行时异常其他异常

运行时异常(不受检异常):空指针异常、数组索引越界异常、

其他异常(受检异常):例如 IOException 使用 try catch finally 进行处理

67、谈一下反射?(网易)

java语言本身是静态类型的语言,但是由于有了反射机制使得java具有了一定的动态特性。

反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。

反射是动态代理的基础,动态代理提供了运行时的代理模式。是Spring AOP的实现方式。

68、TreeMap与LinkedHashMap的有序性区别?(华为)

LinkedHashMap 通常提供的是遍历顺序符合插入顺序,它的实现是通过为条目(键值对)维护一个双向链表。注意,通过特定构造函数,我们可以创建反映访问顺序的实例,所谓的 put、get 都算作访问。

对于 TreeMap,它的整体顺序是由键的顺序关系决定的,通过 Comparator 或Comparable(自然顺序)来决定。

69、谈谈面向对象语言和面向过程语言的区别?(华为)

**面向过程:**面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。

**面向对象:**面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。

70、抽象类与接口的区别?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值