字节跳动面试查缺补漏

一面

会的题不再记录,主要记录一些不会的知识点

OOM的场景?堆和栈的一些区别?

  • Java堆中产生了过多的对象,造成内存溢出,或者内存泄漏(内存泄漏就是一些对象无法被垃圾收集器回收)。
  • 虚拟机栈和本地方法栈溢出。如果线程请求的栈深度大于虚拟机允许的最大深度,抛出stackoverflowError;如果支持动态扩展,当扩展栈无法申请到足够内存,将抛出OOM异常。
  • 一般来说HotSpot虚拟机不允许动态扩展内存,所以基本上由线程请求新栈帧时,深度不够只会导致StackOVerflowError。
  • 方法区和运行时常量池溢出
  • 本地直接内存溢出,调用unsafe类
  • 堆区:
    • 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
    • 2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
  • 栈区:
    1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
    2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
    3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
  • 方法区:
    1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
    2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

HashMap的扩容机制?存储的方式?hash后码相同时怎么处理?

  • 扩容机制
    • 当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置
  • 存储方式,hash到同一位置如何处理
    1)通过hash(Object key)算法得到hash值;
    2)判断table是否为null或者长度为0,如果是执行resize()进行扩容;
    3)通过hash值以及table数组长度得到插入的数组索引i,判断数组table[i]是否为空或为null;
    4)如果table[i] == null,直接新建节点添加,转向 8),如果table[i]不为空,转向 5);
    5)判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,这里的相同指的是hashCode以及equals,否则转向 6);
    6)判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转7);
    7)遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
    8)插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

线程池是什么?怎么用?有什么好处?线程池的核心参数?

    • 线程池是维护一系列线程,这些线程运行完后不立即销毁,而是可以重复使用
  • 用法,核心构造方法:

    public ThreadPoolExecutor(
        int corePoolSize,//核心线程池的大小,如果核心线程池有空闲位置,新的任务就会被核心线程池新建一个线程执行,执行完毕后不会销毁线程,线程会进入缓存队列等待再次被运行              
        int maximumPoolSize,//线程池能创建最大的线程数量。如果核心线程池和缓存队列都已经满了,新的任务进来就会创建新的线程来执行。但是数量不能超过maximunPoolSize,否侧会采取拒绝接受任务策略
        long keepAliveTime,//非核心线程能够空闲的最长时间,超过时间,线程终止,但只在线程数量超过核心线程数时才起作用  
        TimeUnit unit,//时间单位,和keepAliveTime配合使用
        BlockingQueue<Runnable> workQueue,//缓存队列,存放等待执行的任务        
        ThreadFactory threadFactory,//线程工厂,用来创建线程              
        RejectedExecutionHandler handler//拒绝处理策略,线程数量大于最大线程数就会采用拒绝处理策略
                             );
    
    
    • 执行新任务的函数

    • public void execute(Runnable command) {
          if (command == null)
              throw new NullPointerException();
          if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
              if (runState == RUNNING && workQueue.offer(command)) {
                  if (runState != RUNNING || poolSize == 0)
                      ensureQueuedTaskHandled(command);
              }
              else if (!addIfUnderMaximumPoolSize(command))
                  reject(command); 
          }
      }
      
  • 总结:

    • 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
    • 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
    • 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
    • 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止
  • 利用线程池管理并复用线程、控制最大并发数等。

  • 实现任务线程队列缓存策略和拒绝机制。

  • 实现某些与时间相关的功能,如定时执行、周期执行等。

  • 隔离线程环境。比如,交易服务和搜索服务在同一台服务器上,分别开启两个线程池,交易线程的资源消耗明显要大;因此,通过配置独立的线程池,将较慢的交易服务与搜索服务隔开,避免个服务线程互相影响

不过并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:

Executors.newCachedThreadPool();        //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor();   //创建容量为1的缓冲池
Executors.newFixedThreadPool(int);    //创建固定容量大小的缓冲池
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

以上参考:https://www.cnblogs.com/zzuli/p/9386463.html

数据库的索引结构?

  • 哈希表:适用于等值查询,不适合范围查询
  • 有序数组:等值、范围查询很适合(O(lgn)),但不适合插入和删除,适合存储静态数据
  • B+树 :适合很多场景

Redis的数据结构

Redis数据结构

  • String类型,二进制安全的,经典使用场景,把字符串,图片或视频信息放到redis中,再用mysql做持久化存储,单线程防止并发冲突,可以做session
  • Hash 结构,是一个Mapmap,key-value 中的 value 还可以是一个key value
    • 例如 key 为user1 ,对应的valu可以是{{name:“Jerry”},{age:11},{grade:“good”}}
    • 可以保存实体对象的信息
  • List链表结构,双端的,value可重复
    • 栈用法:lpush/lpop
    • 队列用法:lpush/rpop
    • 有限集合:lpush/ltrim
    • 消息队列:lpush/brpop
  • Set集合,不允许重复,无序的,支持集合间求交并差
    • 求互相关注的人,共同朋友,点赞等
  • zSet有序集合
    • 排行榜

事务隔离机制里用了AOP,是如何用的?

使用@Transactional注解

  • 在方法上添加该注解,可以使该方法开始执行前就创建一个事务
  • 这个事务利用AOP来获取方法中的变量等信息
  • 执行完方法后,事务根据方法的成功与否进行提交或回滚

top中的load指的是什么?

  • top中的load

    • load average 系统平均负载被定义为在特定时间间隔内运行队列中的平均进程数

    • 一般命令输出的最后内容表示在过去的1、5、15分钟内运行队列中的平均进程数量
      
    • 一般来说只要每个CPU的当前活动进程数不大于3那么系统的性能就是良好的,如果每个CPU的任务数大于5,那么就表示这台机器的性能有严重问题

端口号用哪一个?

  • 1,lsof -i:端口号

    2,netstat -tunlp|grep 端口号

    这两个命令都可以查看端口被什么进程占用

乐观锁在数据库中的实现形式?

数据库的乐观锁

  • 在易发生冲突的数据行中添加一个代表版本的字段(比如版本号,时间戳等。。。)
  • 在更新事务开始时,先记录开始时的版本号
  • 更新事务执行完毕后,提交时与记录的版本号进行对比,相同则可以提交,不相同就提交失败
  • 这是典型的CAS操作,使用版本号和时间戳递增也有效的避免了ABA问题

具体参考https://www.cnblogs.com/kyoner/p/11318979.html

二面

数据库的第三范式

  • 第一范式:属性的原子性
    • 每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值
    • 如果某一列有多个值,要重新构建实体
  • 第二范式:属性完全依赖于主键
    • 首先要满足第一范式
    • 数据库里的每个实例或行可以被唯一地区分,即有一列属性可以将实体完全区分,即必须有主键
    • 非主属性不能依赖于主键的部分属性,必须依赖于主键的所有属性
  • 第三范式:确保每一列与属性直接相关,而非间接相关
    • 要满足第二范式
    • 主要为了避免数据冗余
  • BCNF范式:在3NF基础上消除对主码子集的依赖
    • 避免关键字段决定关键字段的情况

缓存击穿和缓存穿透

缓存击穿

  • 一个数据存在于数据库中,缓存中没有,但刚好这个时候查询这个数据的请求非常多,就造成查询请求直接绕过缓存,去请求数据库
  • 一些热点数据的过期,会造成这种现象,数据库一瞬间被查询很多次
  • 解决:
    • 设置热点数据用不过期
    • 对数据库访问时加锁

缓存穿透

  • 数据库中没有,缓存也没有的数据,一般是一些不可能存在的数据,比如ID为-1
  • 同时请求缓存和数据库,造成查询压力过大
  • 解决:
    • 接口层直接增加校验,过滤掉不可能出现的数据(如ID为-1的数据)
    • 可以直接把返回结果为Null当作缓存的查询结果,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击,每次查这个ID就直接从缓存给它返回null
    • 布隆过滤器?

Linux的buffer内存和cache内存

  • 内存cache(页面缓存,page cache)
    • 一般是用在读取磁盘,当用户需要访问服务器数据时,服务器会优先把硬盘需要访问的数据直接写入到内存,再从内存加载到比内存更快的cache缓存区,当用户访问时,速度会更快。
  • 内存buffer(缓冲区缓存 buffer cache)
    • 一般是用在写入磁盘,当存储速度快的设备与存储速度慢的设备进行通信时,存储慢的数据先把数据存放到buffer,达到一定时间或buffer存储量时,buffer的数据会写入到内存,清空buffer数据提供后面数据继续写入,在此期间存储快的设备CPU可以干其他的事情。

虚拟内存、常驻内存、共享内存

  • VIRT 虚拟内存:

    • 进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据,以及malloc、new分配的堆空间和分配的栈空间等;
    • 内核通过操作系统中页映射表(page table),为每个进程维护一个页映射表,页映射表的基本原理是将程序运行过程中需要访问的一段虚拟内存空间通过页映射表映射到一段物理内存空间上,通过虚拟内存来访问物理内存的真正地址
    • 虚拟内存只代表该程序运行过程中可访问的内存空间大,不代表使用了很多物理内存
    • VIRT = SWAP + RES
  • RES 常驻内存:

    • 进程当前使用的内存大小,包括使用中的malloc、new分配的堆空间和分配的栈空间,但不包括swap out量;
    • 被映射到进程虚拟内存空间的物理内存,进程真正占用的物理内存大小
    • RES = CODE + DATA
  • SHR 共享内存:

    • 程序依赖于很多外部的动态库(.so),比如libc.so、libld.so,这些动态库在内存中只会保存/映射一份
    • 程序需要使用这些动态库或共享内存进行通讯,就会出现不同进程的虚拟内存空间会映射到相同的物理内存空间。这部分物理内存空间其实是被多个进程所共享的,所以我们将他们称为共享内存,用SHR来表示
    • 一个进程真正占用的内存 = RES-SHR

参考 https://www.cnblogs.com/ruize-coding/p/11639550.html

ConnectTimeOut ReadTimeout异常的区别?Unknown host是什么原因导致的?

  • ConnectTimeout
    指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间。
    在java中,网络状况正常的情况下,例如使用HttpClient或者HttpURLConnetion连接时设置参数connectTimeout=5000即5秒,如果连接用时超过5秒就是抛出java.net.SocketException: connetct time out的异常。
  • ReadTimeout
    指的是建立连接后从服务器读取到可用资源所用的时间。
    在这里我们可以这样理解ReadTimeout:正常情况下,当我们发出请求时可以收到请求的结果,也就是页面上展示的内容,但是当网络状况很差的时候,就会出现页面上无法展示出内容的情况。另外当我们使用爬虫或者其他全自动的程序时,无法判断当前的网络状况是否良好,此时就有了ReadTimeout的用武之地了,通过设置ReadTimeout参数,例:ReadTimeout=5000,超过5秒没有读取到内容时,就认为此次读取不到内容并抛出Java.net.SocketException: read time out的异常。
  • Unknownhost
    • 由于DNS服务器配置错误导致

参考:https://blog.csdn.net/Mlong54/article/details/78831251

发布了16 篇原创文章 · 获赞 2 · 访问量 1826

猜你喜欢

转载自blog.csdn.net/weixin_43925277/article/details/104506470
今日推荐