Java基础面试题大总结(4)

简介: Java基础面试题大总结(4)

62、GC Root有哪些?


在Java语言中,可作为GC Roots的对象包括下面几种:

虚拟机栈(栈帧中的本地变量表)中引用的对象。

方法区中类静态属性引用的对象。

方法区中常量引用的对象。

本地方法栈中JNI(即一般说的Native方法)引用的对象。


63、垃圾收集有哪些算法,各自的特点?



标记 - 清除算法


首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。


复制算法


为了解决效率问题,一种称为“复制”(Copying)的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。


标记 - 整理算法


复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。


根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。


分代收集算法


当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。


一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。


在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。


在老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清理或者标记—整理算法来进行回收。


64、为什么使用Executor框架?


1、 每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的。


2、 调用 new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程之间的频繁交替也会消耗很多系统资源。


3、 接使用new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现。


65、你能保证 GC 执行吗?


不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC的执行。


66、volatile关键字的原理是什么?干什么用的?


使用了volatile关键字的变量,每当变量的值有变动的时候,都会将更改立即同步到主内存中;而如果某个线程想要使用这个变量,就先要从主存中刷新到工作内存,这样就确保了变量的可见性。

一般使用一个volatile修饰的bool变量,来控制线程的运行状态。


volatile boolean stop = false; 
void stop(){ 
  this.stop = true; 
} 
void start(){ 
  new Thread(()->{ while (!stop){ sth } }).start(); 
}


67、java中有几种方法可以实现一个线程?


继承 Thread 类

实现 Runnable 接口

实现 Callable 接口,需要实现的是 call() 方法


68、Java 中的 HashSet,内部是如何工作的?


HashSet 的内部采用 HashMap来实现。由于 Map 需要 key 和 value,所以所有 key 的都有一个默认 value。类似于 HashMap,HashSet 不允许重复的 key,只允许有一个null key,意思就是 HashSet 中只允许存储一个 null 对象。


69、redux的工作流程?


首先,我们看下几个核心概念:


1、 Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个Store。


2、 State:Store对象包含所有数据,如果想得到某个时点的数据,就要对Store生成快照,这种时点的数据集合,就叫做State。


3、 Action:State的变化,会导致View的变化。但是,用户接触不到State,只能接触到View。所以,State的变化必须是View导致的。Action就是View发出的通知,表示State应该要发生变化了。


4、 Action Creator:View要发送多少种消息,就会有多少种Action。如果都手写,会很麻烦,所以我们定义一个函数来生成Action,这个函数就叫Action Creator。


5、 Reducer:Store收到Action以后,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Reducer。Reducer是一个函数,它接受Action和当前State作为参数,返回一个新的State。


6、 dispatch:是View发出Action的唯一方法。


然后我们过下整个工作流程:


1、 首先,用户(通过View)发出Action,发出方式就用到了dispatch方法。


2、 然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State


3、 State一旦有变化,Store就会调用监听函数,来更新View。


到这儿为止,一次用户交互流程结束。可以看到,在整个流程中数据都是单向流动的,这种方式保证了流程的清晰。


image.png


70、String类的常用方法有那些?


1、 charAt:返回指定索引处的字符


2、 indexOf():返回指定字符的索引


3、 replace():字符串替换


4、 trim():去除字符串两端空白


5、 split():分割字符串,返回一个分割后的字符串数组


6、 getBytes():返回字符串的byte类型数组


7、 length():返回字符串长度


8、 toLowerCase():将字符串转成小写字母


9、 toUpperCase():将字符串转成大写字符


10、 substring():截取字符串


11、 format():格式化字符串


12、 equals():字符串比较


13、intern(): 返回的值在常量池中


71、当父类引用指向子类对象的时候,子类重写了父类方法和属性,那么当访问属性的时候,访问是谁的属性?调用方法时,调用的是谁的方法?


子类重写了父类方法和属性,访问的是父类的属性,调用的是子类的方法


72、JVM 监控与分析工具你用过哪些?介绍一下。


1、 jps,显示系统所有虚拟机进程信息的命令行工具


2、 jstat,监视分析虚拟机运行状态的命令行工具


3、 jinfo,查看和调整虚拟机参数的命令行工具


4、 jmap,生成虚拟机堆内存转储快照的命令行工具


5、 jhat,显示和分析虚拟机的转储快照文件的命令行工具


6、 jstack,生成虚拟机的线程快照的命令行工具


7、 jcmd,虚拟机诊断工具,JDK 7 提供


8、 jhsdb,基于服务性代理实现的进程外可视化调试工具,JDK 9 提供


9、 JConsole,基于JMX的可视化监视和管理工具


10、 jvisualvm,图形化虚拟机使用情况的分析工具


11、 Java Mission Control,监控和管理 Java 应用程序的工具


1、 MAT,Memory Analyzer Tool,虚拟机内存分析工具


2、 vjtools,唯品会的包含核心类库与问题分析工具


3、 arthas,阿里开源的 Java 诊断工具


4、 greys,JVM进程执行过程中的异常诊断工具


5、 GCHisto,GC 分析工具


6、 GCViewer,GC 日志文件分析工具


7、 GCeasy,在线版 GC 日志文件分析工具


8、 JProfiler,检查、监控、追踪 Java 性能的工具


9、 BTrace,基于动态字节码修改技术(Hotswap)实现的Java程序追踪与分析工具


下面可以重点体验下:


JDK 自带的命令行工具方便快捷,不是特别复杂的问题可以快速定位;


阿里的 arthas 命令行也不错;


可视化工具 MAT、JProfiler 比较强大。


73、Java序列化中如果有些字段不想进行序列化,怎么办?


对于不想进行序列化的变量,使用transient关键字修饰。


transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。


74、进程与线程的区别,进程间如何通讯,线程间如何通讯?


在并发编程领域,有进程和线程两个概念,在Java语言中说起并发编程,常常是指多线程,但是了解进程的概念也非常重要: – 进程是操作系统的资源调度实体,有自己的内存地址空间和运行环境; – 线程一般被称为轻量级的进程,线程和进程一样,也有自己的运行环境,但是创建一个线程要需要的资源比创建一个进程要少。线程存在于进程之中——每个进程至少有一个线程。一个进程下的多个线程之间可以共享进程的资源,包括内存空间和打开的文件。 – 进程跟程序(programs)、应用(applications)具备相同的含义,进程间通讯依靠IPC资源,例如管道(pipes)、套接字(sockets)等; – 线程间通讯依靠JVM提供的API,例如wait方法、notify方法和notifyAll方法,线程间还可以通过共享的主内存来进行值的传递。


75、说明一下public static void main(String args[])这段声明里每个关键字的作用。


public: main方法是Java程序运行时调用的第一个方法,因此它必须对Java环境可见。所以可见性设置为pulic.


static: Java平台调用这个方法时不会创建这个类的一个实例,因此这个方法必须声明为static。


void: main方法没有返回值。


String是命令行传进参数的类型,args是指命令行传进的字符串数组。


76、如果去掉了main方法的static修饰符会怎样?


程序能正常编译。运行时会抛NoSuchMethodError异常。


77、System.out.println()的含义?


System是系统提供的final类;out是PrintStream对象;println是out里一个重载方法。


78、ArrayList如何删除指定元素?


这是一个很麻烦的面试题,很考验对源码的认识和理解,我们来慢慢的剖析一下。

如果使用常规的思想:

public class TestListMain {
    public static void main(String[] args) {
        List<String> result = new ArrayList<>();
        result.add("a");
        result.add("b");
        result.add("c");
        result.add("d");
        for (String s : result) {
            if ("b".equals(s)) {
                result.remove("b");
            }
        }
    }
}


image.png


既然从源代码分析不出来,我们就看下源代码编译后的class文件中的内容是怎样的吧,毕竟class文件才是JVM真正执行的代码,不看不知道,一看吓一跳,JDK原来是这么玩的。原来如此,我们原始代码中的for-each语句,编译后的实际是以迭代器来代替执行的。


public class TestListMain {
    public TestListMain() {
    }
    public static void main(String[] args) {
        List<String> result = new ArrayList();
        result.add("a");
        result.add("b");
        result.add("c");
        result.add("d");
        //创建迭代器
        Iterator var2 = result.iterator();
        while(var2.hasNext()) {
            String s = (String)var2.next();
            if ("b".equals(s)) {
                result.remove("b");
            }
        }
    }
}


通过ArrayList创建的Itr这个内部类迭代器,于是for-each循环就转化成了迭代器加while循环的方式,原来看上去的for-each循环被挂羊头卖狗肉了。


 public Iterator<E> iterator() {

       return new Itr();

   }


Itr这个内部类迭代器,通过判断hasNext()来判断迭代器是否有内容,而next()方法则获取迭代器中的内容。


 private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;
        Itr() {}
        public boolean hasNext() {
            return cursor != size;
        }
        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
     ...
 }


image.png

真正抛异常的地方是这个检测方法, 当modCount与expectedModCount不相等的时候直接抛出异常了。那我们要看下modCount以及expectedModCount分别是什么。这里的modCount代表ArrayList的修改次数,而expectedModCount代表的是迭代器的修改次数,在创建Itr迭代器的时候,将modCount赋值给了expectedModCount,因此在本例中一开始modCount和expectedModCount都是4(添加了四次String元素)。但是在获取到b元素之后,ArrayList进行了remove操作,因此modCount就累加为5了。因此在进行检查的时候就出现了不一致,最终导致了异常的产生。到此我们找到了抛异常的原因,循环使用迭代器进行循环,但是操作元素却是使用的ArrayList操作,因此迭代器在循环的时候发现元素被修改了所以抛出异常。


image.png


我们再来思考下,为什么要有这个检测呢?这个异常到底起到什么作用呢?我们先来开下ConcurrentModificationException的注释是怎么描述的。简单理解就是不允许一个线程在修改集合,另一个线程在集合基础之上进行迭代。一旦检测到了这种情况就会通过fast-fail机制,抛出异常,防止后面的不可知状况。


/**
 ***
 * For example, it is not generally permissible for one thread to modify a Collection
 * while another thread is iterating over it.  In general, the results of the
 * iteration are undefined under these circumstances.  Some Iterator
 * implementations (including those of all the general purpose collection implementations
 * provided by the JRE) may choose to throw this exception if this behavior is
 * detected.  Iterators that do this are known as <i>fail-fast</i> iterators,
 * as they fail quickly and cleanly, rather that risking arbitrary,
 * non-deterministic behavior at an undetermined time in the future.
 ***
**/
public class ConcurrentModificationException extends RuntimeException {
    ...
}


回顾整个过程微信图片_20220520133635.png


如何正确的删除

既然抛异常的原因是循环使用了迭代器,而删除使用ArrayList导致检测不通过。那么我们就循环使用迭代器,删除也是用迭代器,这样就可以保证一致了。

public class TestListMain {
    public static void main(String[] args) {
        List<String> result = new ArrayList<>();
        result.add("a");
        result.add("b");
        result.add("c");
        result.add("d");
       Iterator<String> iterator = list.iterator();
    while (iterator .hasNext()) {
      String str = iterator.next();
      if ("b".equals(str)) {
        iterator.remove();
      }
    }
}


79、为啥线程不安全还使用ArrayList呢?


因为在我们正常得使用场景中,都是用来查询的,不会涉及太频繁的增删,如果涉及到频繁的增删,可以使用LinkedList,如果需要线程安全的就是可以使用Vector,CopyOrWriteArrayList


80、1.7和1.8版本初始化的区别


1.7的时候是初始化就创建一个容量为10的数组,1.8后是初始化先创建一个空数组,第一次add时才扩容为10



相关文章
|
3月前
|
算法 Java
50道java集合面试题
50道 java 集合面试题
|
6月前
|
缓存 Java 关系型数据库
2025 年最新华为 Java 面试题及答案,全方位打造面试宝典
Java面试高频考点与实践指南(150字摘要) 本文系统梳理了Java面试核心考点,包括Java基础(数据类型、面向对象特性、常用类使用)、并发编程(线程机制、锁原理、并发容器)、JVM(内存模型、GC算法、类加载机制)、Spring框架(IoC/AOP、Bean生命周期、事务管理)、数据库(MySQL引擎、事务隔离、索引优化)及分布式(CAP理论、ID生成、Redis缓存)。同时提供华为级实战代码,涵盖Spring Cloud Alibaba微服务、Sentinel限流、Seata分布式事务,以及完整的D
339 1
|
6月前
|
存储 安全 Java
常见 JAVA 集合面试题整理 自用版持续更新
这是一份详尽的Java集合面试题总结,涵盖ArrayList与LinkedList、HashMap与HashTable、HashSet与TreeSet的区别,以及ConcurrentHashMap的实现原理。内容从底层数据结构、性能特点到应用场景逐一剖析,并提供代码示例便于理解。此外,还介绍了如何遍历HashMap和HashTable。无论是初学者还是进阶开发者,都能从中受益。代码资源可从[链接](https://pan.quark.cn/s/14fcf913bae6)获取。
294 3
|
5月前
|
缓存 Java API
Java 面试实操指南与最新技术结合的实战攻略
本指南涵盖Java 17+新特性、Spring Boot 3微服务、响应式编程、容器化部署与数据缓存实操,结合代码案例解析高频面试技术点,助你掌握最新Java技术栈,提升实战能力,轻松应对Java中高级岗位面试。
458 0
|
5月前
|
Java 数据库连接 数据库
Java 相关知识点总结含基础语法进阶技巧及面试重点知识
本文全面总结了Java核心知识点,涵盖基础语法、面向对象、集合框架、并发编程、网络编程及主流框架如Spring生态、MyBatis等,结合JVM原理与性能优化技巧,并通过一个学生信息管理系统的实战案例,帮助你快速掌握Java开发技能,适合Java学习与面试准备。
235 2
Java 相关知识点总结含基础语法进阶技巧及面试重点知识
|
3月前
|
算法 Java
50道java基础面试题
50道java基础面试题
|
6月前
|
存储 安全 Java
2025 最新史上最全 Java 面试题独家整理带详细答案及解析
本文从Java基础、面向对象、多线程与并发等方面详细解析常见面试题及答案,并结合实际应用帮助理解。内容涵盖基本数据类型、自动装箱拆箱、String类区别,面向对象三大特性(封装、继承、多态),线程创建与安全问题解决方法,以及集合框架如ArrayList与LinkedList的对比和HashMap工作原理。适合准备面试或深入学习Java的开发者参考。附代码获取链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
3132 48
|
6月前
|
算法 架构师 Java
Java 开发岗及 java 架构师百度校招历年经典面试题汇总
以下是百度校招Java岗位面试题精选摘要(150字): Java开发岗重点关注集合类、并发和系统设计。HashMap线程安全可通过Collections.synchronizedMap()或ConcurrentHashMap实现,后者采用分段锁提升并发性能。负载均衡算法包括轮询、加权轮询和最少连接数,一致性哈希可均匀分布请求。Redis持久化有RDB(快照恢复快)和AOF(日志更安全)两种方式。架构师岗涉及JMM内存模型、happens-before原则和无锁数据结构(基于CAS)。
172 5
|
6月前
|
Java API 微服务
2025 年 Java 校招面试全攻略:从面试心得看 Java 岗位求职技巧
《2025年Java校招最新技术要点与实操指南》 本文梳理了2025年Java校招的核心技术栈,并提供了可直接运行的代码实例。重点技术包括: Java 17+新特性(Record类、Sealed类等) Spring Boot 3+WebFlux响应式编程 微服务架构与Spring Cloud组件 Docker容器化部署 Redis缓存集成 OpenAI API调用 通过实际代码演示了如何应用这些技术,如Java 17的Record类简化POJO、WebFlux构建响应式API、Docker容器化部署。
273 5