导图社区 Java工程面试-Java基础
记录了尚硅谷java面试相关的基础知识、日常工作中一般用自己实现的线程池,不用Executors创建,参考阿里巴巴java开发手册并发这一张。垃圾收集器相关参数说明。
编辑于2023-02-11 14:06:25 上海Java工程面试
Java基础
jvm 体系结构

参数配置
1. 标配参数 java -version java -help 2. X参数 -Xint 即时执行 -Xcomp 第一次使用就编译成本地代码 -Xmixed 混合模式 3. XX参数 (1) Boolean 类型 -XX+或者-表示某个属性值,+表示开启,-表示关闭 -XX+PrintGCDetails  (2) KV类型 -XX属性key=属性值value  -Xms<=>-XX:InitialHeapSize -Xmx<=>-XX:MaxHeapSize -Xss(初始栈空间的大小) 如何查看一个正在运行的java程序,他的某个jvm参数是否开启?具体是多少? jps -l 得到对应的进程编号 jinfo -flag PrintGCDetails pid 打印进程pid的PrintGCDetails参数的情况 jinfo -flags pid 打印进程所有的jvm配置参数的信息 java -XX:+PrintFlagsInitial 查看jvm初始化参数 java -XX:+PrintFlagsFinal -version 查看修改和更新的参数内容 查出来的参数内容中 =表示默认参数值, :=的表示的是人为修改后或者jvm自己修改以后的参数值         
Collections
线程安全
Vector
特性
有序的,可以存储重复值和null值。 底层是数组实现的,线程安全(数组操作都加了synchronized锁)。结构与ArrayList非常相似,同样是一个线性的动态可扩容数组。 初始容量是10,没有设置扩容增量的情况下以自身的2倍容量扩容,可以设置容量增量,初始容量和扩容量可以通过构造函数public Vector(int initialCapacity, int capacityIncrement)进行初始化。 Vector底层是用数组实现的,其容量是可以动态扩展的,默认初始容量是10,默认增长因子是0,详细的扩容方式会在构造方法中讲述。 Vector对象和ArrayList一样可以随意插入不同类的对象,因为其动态扩展的特性,不需要考虑容量限制。事实上Vector与ArrayList的实现方式非常相似,但是ArrayList中的方法没有synchronized关键字修饰,而Vector类中的方法都有synchronized关键字修饰,其中的单个方法都属于原子操作,是线程安全的。注意:多个原子操作组成的复合操作并不是线程安全的,必须将该复合操作变成原子操作,才能保证线程安全。
缺点
然而正因为Vector类中每个方法中都添加了synchronized的关键字来保证同步,使得它的效率大大的降低了,比ArrayList的效率要慢,因此一般情况下都不使用Vector对象,而会选择使用ArrayList。
CopyOnWriteArrayList
特性
写时复制 读写分离 采用了reentrantLock加锁操作,添加元素的时候首先加可重入锁,然后复制数组,添加新的元素后,更新原来的数组,最后释放锁。 
CopyOnWriteArraySet
线程不安全
Arraylist
线程安全解决方案
Vector
Collections.synchronizedList(new ArrayList<>());
CopyOnWriteArrayList
Set
线程安全解决方案
CopyOnWriteArraySet
HashSet
特性
底层是HashMap, 只是用到key值部分保存元素, value部分是一个PRESENT = new Object();的常量
Map
线程安全解决方案
ConcurrentHashMap
采用分段锁机制解决并发
Exceptions
五个常见的异常
ConcurrentModificationException
ClassCastException(类转换异常)
数据类型转换错误,比如有个String temp="abc"; 如果设为(int)temp就会报错了,因为它们类型不一样,但是设为(object)temp就可以,因为object是它们的父类
IndexOutOfBoundsException(数组越界)
这个异常我们在操作数组的时候会经常遇到,异常的解释是“数组下标越界“,现在程序中大多都有对数组的操作,因此在调用数组的时候一定要认真检查,看自己调用的下标是不是超出了数组的范围,一般来说,显示(即直接用常数当下标)调用不太容易出这样的错,但隐式(即用变量表示下标)调用就经常出错了,还有一种情况,是程序中定义的数组的长度是通过某些特定方法决定的,不是事先声明的,这个时候,最好先查看一下数组的length,以免出现这个异常。
NullPointerException(空指针)
这个异常在编程时也经常遇到,异常的解释是 “程序遇上了空指针 “,简单地说就是调用了未经初始化的对象或者是不存在的对象,这个错误经常出现在调用数组这些操作中,对数组操作中出现空指针,很多情况下是一些刚开始学习编程的人常犯的错误,即把数组的初始化和数组元素的初始化混淆起来了。数组的初始化是对数组分配需要的空间,而初始化后的数组,其中的元素并没有实例化,依然是空的,所以还需要对每个元素都进行初始化(如果要调用的话)。
IllegalAccessException(安全权限异常)
这个异常的解释是“没有访问权限“,当应用程序要调用一个类,但当前的方法即没有对该类的访问权限便会出现这个异常。对程序中用了Package的情况下要注意这个异常。
IOException(输入输出异常)
一般读写文件会出现这个异常,比如你想从磁盘上读一个文件到你写的程序,如果硬盘上没有这文件,java虚拟机就会报这个异常。
Error
1.StackOverflowError; 2.OutOfMemoryError; 2.1 Java Heap Space 2.2 GC overhead limit exceed 2.3 Direct Buffer Memory 2.4 Unable to Create New Native Thread; 2.5 MetaSpace
锁
公平锁和非公平锁
是什么

两者的区别

题外话

可重入锁(又称递归锁)
是什么
一个线程获取到某个锁以后,可以再次自动获得这个锁,进入这个锁锁住的代码块 
ReentrantLock/Synchronized就是典型的可重入锁
两者的区别
  
可重入锁最大的作用是避免死锁
ReentrantLockDemo
自旋锁
是什么

独占锁(写锁)/共享锁(读锁)/互斥锁
是什么

ReadWriteLockDemo
参数传递
值传递
基础类型作为参数传递时,一般都是值传递,传一个副本
引用传递
对象传递之类的是引用传递
Callable
callable 接口可以获取到线程的执行结果,封装在FutureTask对象里面使用, 可以用来执行时间较长的计算任务,获取返回值,提高计算效率。 class MyThread2 implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("*************** Come in Callable ***********"); TimeUnit.SECONDS.sleep(2); return 1024; } } /** * 多线程三种获得多线程的方式 */ public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<Integer> futureTask = new FutureTask<>(new MyThread2()); // 多个线程执行一个futureTask对象,只会执行一次,如果要执行多次,就创建多个futuretask对象 Thread t1 = new Thread(futureTask, "AA"); t1.start(); Thread t2 = new Thread(futureTask, "BB"); t2.start(); // int result1 = futureTask.get(); // 这里会阻塞主线程,直到callable计算完成 System.out.println(Thread.currentThread().getName() + "********"); int result0 = 100; while (!futureTask.isDone()){ } int result1 = futureTask.get(); /* futureTask.get() 获得callable线程的计算结果,如果没有计算完成,会导致阻塞,直到计算完成 */ System.out.println("***** results = " + (result0 + result1)); } }
线程池
为什么用线程池,线程池的优势

线程池如何使用
1. 架构说明: Java中的线程池是通过Executor框架实现的,该框架用到了Executor, Executors, ExecutorService, ThreadPoolExecutor(线程池的底层)这几个类   2. 编码实现 2.1 了解 Executors.newScheduledThreadPool() java8 新特性-》Executors.newWorkStealingPool(int) 2.2 重点 Executors.newFixedThreadPool(); Executors.newSingleThreadExecutor(); Executors.newCachedThreadPool();
Executors.newFixedThreadPool();
一个池子固定数目的线程,适用于执行长期的任务 
Executors.newSingleThreadExecutor();
一个池子一个线程,一个任务一个任务执行 
Executors.newCachedThreadPool();
一个池子N个线程,适合执行短期异步任务 
ThreadPoolExecutor
CPU密集型

IO密集型
 
线程池的几个重要参数
7个重要参数:  一、 corePoolSize 线程池核心线程大小 corePoolSize 是线程池中的一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。 二、maximumPoolSize 线程池最大线程数量 线程池能够容纳同时执行的最大线程数,此值大于等于1。一个任务被提交到线程池以后,首先会找有没有空闲并且存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会放到工作队列中,直到工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。工作队列满,且线程数等于最大线程数,此时再提交任务则会调用拒绝策略。 三、keepAliveTime 多余的空闲线程存活时间 当线程空闲时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止。默认情况下:只有当线程池中的线程数大于corePoolSize时keepAliveTime才会起作用,直到线程中的线程数不大于corepoolSIze, 四、unit 空闲线程存活时间单位 keepAliveTime的计量单位 五、workQueue 工作队列 任务被提交给线程池时,会先进入工作队列,任务调度时再从工作队列中取出。 常用工作队列有以下几种 1. ArrayBlockingQueue(数组的有界阻塞队列) ArrayBlockingQueue 在创建时必须设置大小,按FIFO排序(先进先出)。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。 2. LinkedBlockingQueue(链表的无界阻塞队列) 按 FIFO 排序任务,可以设置容量(有界队列),不设置容量则默认使用 Integer.Max_VALUE 作为容量 (无界队列)。该队列的吞吐量高于 ArrayBlockingQueue。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。有两个快捷创建线程池的工厂方法 Executors.newSingleThreadExecutor、Executors.newFixedThreadPool,使用了这个队列,并且都没有设置容量(无界队列)。 3.SynchronousQueue(一个不缓存任务的阻塞队列) 生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。 其 吞 吐 量 通 常 高 于LinkedBlockingQueue。 快捷工厂方法 Executors.newCachedThreadPool 所创建的线程池使用此队列。与前面的队列相比,这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。 4. PriorityBlockingQueue(具有优先级的无界阻塞队列) 优先级通过参数Comparator实现。 5. DelayQueue(这是一个无界阻塞延迟队列) 底层基于 PriorityBlockingQueue 实现的,队列中每个元素都有过期时间,当从队列获取元素(元素出队)时,只有已经过期的元素才会出队,而队列头部的元素是过期最快的元素。快捷工厂方法 Executors.newScheduledThreadPool 所创建的线程池使用此队列。 Java 中的阻塞队列(BlockingQueue)与普通队列相比,有一个重要的特点:在阻塞队列为空时,会阻塞当前线程的元素获取操作。具体来说,在一个线程从一个空的阻塞队列中取元素时,线程会被阻塞,直到阻塞队列中有了元素;当队列中有元素后,被阻塞的线程会自动被唤醒(唤醒过程不需要用户程序干预)。 六、threadFactory 线程工厂 创建一个线程工厂用来创建线程,可以用来设定线程名、是否为daemon线程等等,生成线程池中工作线程的工厂 七、handler 拒绝策略 AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常。(默认这种) DiscardPolicy:丢弃任务,但是不抛出异常 DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) 。也就是当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务从队尾添加进去,等待执行。 CallerRunsPolicy:谁调用,谁处理。由调用线程(即提交任务给线程池的线程)处理该任务,如果线程池已经被shutdown则直接丢弃 
线程池的底层工作原理
  
拒绝策略
是什么: 等待队列满了,同时线程池的线程数达到了max数量,无法继续为新任务提供服务 这时候我们就需要拒绝策略机制合理的处理这个问题 
日常工作中一般用自己实现的线程池,不用Executors创建,参考阿里巴巴java开发手册并发这一张
引用类型
强软弱虚: java提供了四种引用类型,在垃圾回收的时候都有各自的特点。 ReferenceQueue是用来配合工作的,没有ReferenceQueue一样可以运行。 创建引用的时候可以指定关联的队列,当GC释放对象内存的时候,会将引用加入引用队列, 如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动 这相当于是一种通知机制。 当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的对象被回收。通过这种当时,JVM允许我们在对象被销毁以后做一些我们想做的事情。 整体架构:  1.强引用  2.软引用  3.弱引用  2-3 软引用和弱引用的使用场景:  了解; WeakHashMap 4.phantom(幽灵;虚)reference 虚引用  引用队列: 
Java Gc
1.GC 作用域:堆和方法区 2.常见垃圾回收算法: (1)引用计数  (2)标记复制   (3)标记清除  (4)标记整理 
GC Roots
GCRoots类型: 1. 虚拟机栈(栈帧中的本地变量表)中引用的对象 2.方法区中的类静态属性引用的对象 3.方法区中的常量引用的对象 4.本地方法栈中JNI(即一般说的native方法)中引用的对象 
垃圾回收器
一、目前为止还没有完美的垃圾回收器,只针对具体的应用选择最合适的垃圾回收器,进行分代收集。 二、4种主要的垃圾回收器:  1. 串行垃圾回收器(Serial) 为单线程环境设计的,且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境。 2.并行垃圾回收器(parallel) 多个垃圾收集线程并行工作,此时用户线程是暂停的,适用于科学计算、大数据处理、首台处理等弱交互场景。 3.并发垃圾回收器(CMS) 用户线程和垃圾收集线程同时执行(不一定并行,可能交替执行),不需要停顿用户线程,互联网公司多用它,适用对响应时间有要求的场景。 上述三个的总结:  4.G1垃圾回收器 将堆内存分割成不同的区域然后并发的对其进行垃圾回收 三、新生代和老年代适用的垃圾回收算法: G1的不明确区分新生代和老年代。  各个垃圾回收算法的一般配对关系图:  四、设置不同垃圾回收器的VM配置参数    
垃圾收集器相关参数说明
一、默认的垃圾回收器:   二、默认有哪些垃圾回收器:  三、参数说明 DefNew->Default New Generation Tenured->Old ParNew->Parallel New Generation PSYoungGen->Parallel Scavenge ParOldGen->Parallel Old Generation GClog信息中打印出的参数信息  四、Server/Client 模式 
新生代
串行 Serial
 
并行 ParNew
Serial收集器多线程版本   
并行回收 Parallel
    
老年代
串行 SerialOld

并行 Parallel Old
 
CMS(ConcurrentMarkSweep 并发标记清除)
    标记清楚过程:  初始标记:只是标记一下GCRoots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程 并发标记:进行GCRoots的跟踪过程,和用户线程一起工作,不需要暂停工作线程。主要的标记过程,标记全部对象 重新标记:为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。由于并发标记时,用户线程依然在运行,因此在正式清理前,再做修正。 并发清除:  优缺点:   
G1(这个比较特殊)
    
特点
    
回收步骤
  
如何选择垃圾回收器