今日面经
链接:https://www.nowcoder.com/discuss/642608?source_id=profile_create_nctrack&channel=-1
jvm内存空间
方法区
方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
类的加载中提到了类加载的五个阶段。在加载阶段,会将字节流所代表的静态存储结构转化为方法区的运行时数据结构,在准备阶段,会将变量所使用的内存都将在方法区中进行分配。程序计数器
java是多线程的,在线程切换回来后,它需要知道原先的执行位置在哪里。用来记录这个执行位置的,就是程序计数器。
虚拟机栈
虚拟机栈也是线程私有的,生命周期与线程相同。每个线程都有自己的虚拟机栈,如果这个线程执行了一个方法,就会创建一个栈帧,方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
堆
堆内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
本地方法栈
底层native方法运行时,用的栈。
gc 垃圾回收器
串行:垃圾回收器 (Serial Garbage Collector)(单线程-串行垃圾回收器)
(1)串行垃圾回收器在进行垃圾回收时,它会持有所有应用程序的线程,冻结所有应用程序线程,使用单个垃圾回收线程来进行垃圾回收工作。
串行垃圾回收器是为单线程环境而设计的,如果你的程序不需要多线程,启动串行垃圾回收。
(2)串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)。
串行:ParNew收集器(多线程-串行垃圾回收器)
ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩
使用方法:-XX:+UseParNewGC ParNew收集器并行:Parallel收集器
Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩并行:Parallel Old 收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供并发标记扫描CMS收集器
4个步骤:
初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
重新标记(CMS remark)
并发清除(CMS concurrent sweep)
其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。G1(其主要目的是用来替换cms算法)
1、Region 区域化垃圾收集器:最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可。
2、回收步骤:
3、四步过程:
- 初始标记:标记一下GC Roots能直接关联到的对象,需要停顿线程,但耗时很短
- 并发标记:是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行
- 最终标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录
- 筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划
G1 整体采用标记-整理算法,局部是通过是通过复制算法,不会产生内存碎片。
类的加载,双亲委派,对象的生命周期
加载
加载阶段所要进行的工作:
- 通过类的全限定名(包名 + 类名),获取到该类的.class文件的二进制字节流(二进制字节流加载到内存)
- 将二进制字节流所代表的静态存储结构,转化为方法区运行时的数据结构(映射成jvm能识别的结构)
- 在内存中生成一个代表该类的java.lang.Class对象,作为方法区中这个类的各种数据的访问入口(在内存中生成class对象)
- 通过反射可以得到此类的详细信息
链接是指将加载阶段已经读入内存的类的二进制数据合并到 JVM 中,使之能够执行的过程,分为验证、准备、解析三个阶段
2.1 验证
确保class文件中的字节流包含的信息符合当前虚拟机的要求,保证这个被加载的class类的正确性,不会危害到虚拟机的安全
2.2 准备
为类中的静态字段分配内存,并设置默认的初始值,比如 int a = 5 初始值是0。被final修饰的static字段不会设置,因为final在编译的时候就分配了2.3 解析
解析阶段的目的,是将常量池内的符号引用转换为直接引用的过程(将常量池内的符号引用解析成为实际引用)。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化)事实上,解析操作往往伴随着 JVM 在执行完初始化之后再执行
符号引用就是一组符号用来描述所引用的目标
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
初始化
初始化就是执行类的构造器方法 init() 的过程(为类的静态变量赋予指定值,如上述的a被赋予5)
若该类具有父类,jvm会保证父类的 init() 先执行,然后再执行子类的 init()
如果类中存在初始化语句,就依次执行这些初始化语句,初始化语句指的是static修饰的语句
双亲委派机制
- 什么是双亲委派机制
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
- 类加载器的类别
BootstrapClassLoader(启动类加载器)
c++编写,加载java核心库 java.*,构造ExtClassLoader和AppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
ExtClassLoader (标准扩展类加载器)
java编写,加载扩展库,如classpath中的jre ,javax.*或者
java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器。
AppClassLoader(系统类加载器)
1 | java`编写,加载程序所在的目录,如`user.dir`所在的位置的`class |
CustomClassLoader(用户自定义类加载器)
java编写,用户自定义的类加载器,可加载指定路径的class文件
集合,hashmap的实现,concurrenthashmap的实现
HashMap:
- 数据结构上的变化,数组+链表+红黑树
- 头插法可能引起链表中存在环,改为尾插法
ConcurrentHashMap:
JDK1.7 :
1.7中的 ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成,即 ConcurrentHashMap 把哈希桶数组切分成小数组(Segment ),每个小数组有 n 个 HashEntry 组成。
如下图所示,首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据时,其他段的数据也能被其他线程访问,实现了真正的并发访问。
Segment 继承了 ReentrantLock。
JDK1.8:
1.8中的ConcurrentHashMap 选择了与 HashMap 相同的Node数组+链表+红黑树结构;在锁的实现上,抛弃了原有的Segment 分段锁,采用CAS + synchronized实现更加细粒度的锁。
将锁的级别控制在了更细粒度的哈希桶数组元素级别,也就是说只需要锁住这个链表头节点(红黑树的根节点),就不会影响其他的哈希桶数组元素的读写,大大提高了并发度。
synchornized的实现(新旧版本),对象头。
链接:https://blog.csdn.net/javazejian/article/details/72828483
lock的实现,线程不安全问题。
- ReentrantLock先通过CAS尝试获取锁;
- 如果此时锁已经被占用,该线程加入AQS队列并wait()
- 当前驱线程的锁被释放,挂在CLH队列为首的线程就会被notify(),然后继续CAS尝试获取锁,此时:
- 非公平锁,如果有其他线程尝试lock(),有可能被其他刚好申请锁的线程抢占。
- 公平锁,只有在CLH队列头的线程才可以获取锁,新来的线程只能插入到队尾。
volatile的原理
保持内存可见性
内存可见性(Memory Visibility):所有线程都能看到共享内存的最新状态。
防止指令重排
指令重排技术大大提高了程序执行效率,但同时也引入了一些问题。
初始化一个对象:
1 | memory = allocate(); //1:分配对象的内存空间 |
线程池threadpoolexecutor的使用。
- 当线程数小于核心线程数时,创建线程。
- 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,抛出异常,拒绝任务
IO,NIO,AIO,零拷贝的优势与应用
IO 又称 BIO,blocking IO,就是单线程 IO,会阻塞线程。
NIO,non-blocking IO,就是不阻塞线程。通过 channel 和 selector 来完成多通道 IO。但它要定时遍历 selectKey 来查看哪个 channel 激活了。有点不优雅,才有了 AIO。
AIO:Asynchronous IO,用方法回调方式来处理。真正的异步 IO。
传统的io模型:
「第一步」:将文件通过 「DMA」 技术从磁盘中拷贝到内核缓冲区
「第二步」:将文件从内核缓冲区拷贝到用户进程缓冲区域中
「第三步」:将文件从用户进程缓冲区中拷贝到 socket 缓冲区中
「第四步」:将socket缓冲区中的文件通过 「DMA」 技术拷贝到网卡
DMA「原理」:
DMA 传输将数据「从一个地址空间复制到另外一个地址空间」。当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。
缺点:
因为是使用了内存映射的关系,所以零拷贝技术「无法对数据内容做更改」。
聚集索引与非聚集索引
聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个
聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续
聚集索引:物理存储按照索引排序;聚集索引是一种索引组织形式,索引的键值逻辑顺序决定了表数据行的物理存储顺序
非聚集索引:物理存储不按照索引排序;非聚集索引则就是普通索引了,仅仅只是对数据列创建相应的索引,不影响整个表的物理存储顺序.
索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。
ACID
1.原子性:事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
2.一致性:执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
3.隔离性:并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
4.持久性:一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
事务隔离级别
事务隔离级别有4种,但是像Spring会提供给用户5种,来看一下:
1、DEFAULT
默认隔离级别,每种数据库支持的事务隔离级别不一样,如果Spring配置事务时将isolation设置为这个值的话,那么将使用底层数据库的默认事务隔离级别。顺便说一句,如果使用的MySQL,可以使用”select @@tx_isolation“来查看默认的事务隔离级别
2、READ_UNCOMMITTED
读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用
3、READ_COMMITED
读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读
4、REPEATABLE_READ
重复读取,即在数据读出来之后加锁,类似”select * from XXX for update”,明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决
5、SERLALIZABLE
串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了
InnoDB和myisam的锁区别
InnoDB默认是行级锁,支持表锁,也就在InnoDB中更新某条数据会对行上锁,如果是排他锁,那么其他的事务访问这一行的数据需要等锁释放之后才能进行,而对其他行数据是没有影响的。
MyISAM默认是表级锁,不支持行级锁,也就是在MyISAM中进行某条数据更新时,会对整个表上锁,所有的其他事务对表中对数据进行访问或者更新的时候都必须等那个事务释放表锁。
需要注意的是,当InnoDB不走索引的时候,就会默认上表级锁了。
InnoDB的锁分类
next key锁的原理
大表优化
链接:https://www.cnblogs.com/SimpleWu/p/10525946.html
字段
- 尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED
- VARCHAR的长度只分配真正需要的空间
- 单表不要有太多字段,建议在20以内
索引
- 索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描
- 应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描
- 字符字段只建前缀索引
- 字符字段最好不要做主键
- 不用外键,由程序保证约束
- 尽量不用UNIQUE,由程序保证约束
查询SQL
- 可通过开启慢查询日志来找出较慢的SQL
- 不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
- sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库
- 不用SELECT *
- OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内
引擎
目前广泛使用的是MyISAM和InnoDB两种引擎:
MyISAM
MyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:
- 不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁
- 不支持事务
- 不支持外键
- 不支持崩溃后的安全恢复
- 在表有读取查询的同时,支持往表中插入新纪录
- 支持BLOB和TEXT的前500个字符索引,支持全文索引
- 支持延迟更新索引,极大提升写入性能
- 对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用
InnoDB
InnoDB在MySQL 5.5后成为默认索引,它的特点是:
- 支持行锁,采用MVCC来支持高并发
- 支持事务
- 支持外键
- 支持崩溃后的安全恢复
- 不支持全文索引
总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERT和UPDATE密集型的表
mvcc
MVCC(Multi Version Concurrency Control的简称),代表多版本并发控制。与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)。
MVCC最大的优势:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能。
MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
下面看一下在REPEATABLE READ隔离级别下,MVCC具体是如何操作的。
SELECT
InnoDB会根据以下两个条件检查每行记录:
- InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
- 行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。
只有符合上述两个条件的记录,才能返回作为查询结果
INSERT
InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
DELETE
InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
UPDATE
InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。
保存这两个额外系统版本号,使大多数读操作都可以不用加锁。这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。
兴盛
如何设计数据库?合理安排他们的关联性?
1.需求分析阶段(常用自顶向下)
需求收集和分析,得到数据字典和数据流图。
进行数据库设计首先必须准确了解和分析用户需求(包括数据与处理)。需求分析是整个设计过程的基础,也是最困难,最耗时的一步。需求分析是否做得充分和准确,决定了在其上构建数据库大厦的速度与质量。需求分析做的不好,会导致整个数据库设计返工重做。
分析方法常用SA(Structured Analysis) 结构化分析方法,SA方法从最上层的系统组织结构入手,采用自顶向下,逐层分解的方式分析系统。2.概念结构设计阶段(常用自底向上)
对用户需求综合、归纳与抽象,形成概念模型,用E-R图表示。
1 | 概念结构设计是整个数据库设计的关键,它通过对用户需求进行综合,归纳与抽象,形成了一个独立于具体DBMS的概念模型。 |
3.逻辑结构设计阶段
1 | 任务:将基本E-R图转换为与选用DBMS产品所支持的数据模型相符合的逻辑结构。 |
4.物理设计
1 | 就是根据所选择的关系型数据库的特点对逻辑模型进行存储结构设计。它涉及的内容包含以下4方面: |
E-R图设计
在设计E-R图时,一般使用先局部后全局的方法。
- 选择局部应用:根据某个系统的具体情况,在多层的数据流图中选择一个适当层次的数据流图作为设计分E-R图的出发点 。
- 逐一设计分E-R图:将数据字典中的数据抽取出来,参照数据流图,设计出E-R图,再作必要的调整。
- 调整原则:为简化图的处置,现实世界中的事物能作为属性对待的,尽量作为属性对待。作为“属性”,不能再具有描述的性质,也不能与其他实体具有联系。
冲突:
1、属性冲突
属性域冲突,即属性值的类型、取值范围或取值集合不同。
例如零件号,有的厂商把它定义为整数类型,有的部门把它定义为字符类型。
属性取值单位冲突。
例如,零件的重量有的以公斤为单位,有的以斤为单位,有的以克为单位。
2、命名冲突
同名异义,即不同意义的对象在不同的局部应用中具有相同的名字。
3、结构冲突
同一对象在不同应用中具有不同的抽象。
设计模式的6大原则
1.单一职责
1 | 一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。 |
2.开闭原则(Open Close Principle)
1 | 一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。 |
3.里氏代换原则(Liskov Substitution Principle)
1 | 任何基类可以出现的地方,子类一定可以出现。 |
4.依赖倒转原则(Dependence Inversion Principle)
1 | 抽象(接口/抽象类)不应该依赖于细节,细节应该依赖于抽象(接口/抽象类)。 |
5.接口隔离原则(Interface Segregation Principle)
1 | 类间的依赖关系应该建立在最小的接口上 |
6.迪米特法则,又称最少知道原则(Demeter Principle)
1 | 一个对象应该对其他对象有最少的了解 |
zookeeper
watch机制
其实原理应该是很简单的,四个步骤:
- 客户端注册Watcher到服务端;
- 服务端发生数据变更;
- 服务端通知客户端数据变更
- 客户端回调Watcher处理变更应对逻辑;
半数选举
覆盖索引
- 解释一: 就是select的数据列只用从索引中就能够取得,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖。
- 解释二: 索引是高效找到行的一个方法,当能通过检索索引就可以读取想要的数据,那就不需要再到数据表中读取行了。如果一个索引包含了(或覆盖了)满足查询语句中字段与条件的数据就叫 做覆盖索引。
explain 执行计划,参数详解
一、 id
SELECT识别符。这是SELECT的查询序列号
我的理解是SQL执行的顺序的标识,SQL从大到小的执行
id相同时,执行顺序由上至下
如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行
二、select_type
\示查询中每个select子句的类型**
(1) SIMPLE(简单SELECT,不使用UNION或子查询等)
(2) PRIMARY(子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
(3) UNION(UNION中的第二个或后面的SELECT语句)
三、table
显示这一步所访问数据库中表名称(显示这一行的数据是关于哪张表的),有时不是真实的表名字,可能是简称,例如上面的e,d,也可能是第几步执行的结果的简称
四、type
对表访问方式,表示MySQL在表中找到所需行的方式,又称“访问类型”。
常用的类型有: ALL、index、range、 ref、eq_ref、const、system、**NULL(从左到右,性能从差到好)**
ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
index: Full Index Scan,index与ALL区别为index类型只遍历索引树
五、possible_keys
指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用
六、Key
key列显示MySQL实际决定使用的键(索引),必然包含在possible_keys中
如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。
七、key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)
不损失精确性的情况下,长度越短越好
八、ref
列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
九、rows
估算出结果集行数,表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
十、Extra
该列包含MySQL解决查询的详细信息,有以下几种情况:
Using where: 不用读取表中所有信息,仅通过索引就可以获取所需数据,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询,常见 group by ; order by
Using filesort:当Query中包含 order by 操作,而且无法利用索引完成的排序操作称为“文件排序”
java8新特性
Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)。
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
创建stream的方式:
(1)通过Collection系列集合提供的stream()方法或者parallelStream()方法来创建Stream。
(2)通过Arrays中的静态方法stream()获取数组流。
(3)通过Stream类的静态方法of()获取数组流。
指针和引用的区别
指针:是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。
引用:是某个已知变量 或对象的别名,它不是变量,自身没有值和地址,不占用内存空间。
一个引用只能对应着一个变量(引用是专一的),一个指针变量却可以存放多个变量或对象的地址(指针是多变的)。
前面也说了,引用是一个一个变量或对象的别名。那么比如张三的别名是阿三,如果李四的别名也叫阿三,当有人叫阿三的时候,到底哪个阿三才会回应呢?这就容易引起混淆了。
指针是一块空间,里面的内容是可以替换的,指针里存的是a的地址,通过指针你可以访问到a,当有需要了,你把指针的值改为了b,你同意还是可以范文到b。不存在空值的引用,但存在空值的指针
引用是一个已知对象或变量的别名,当这个变量都不存在,哪来的别名。引用相对于指针更加安全
引用在定义的时候就被初始化了,并且引用的对象不可改变。
http1和http2的区别
主要区别包括
- HTTP/2采用二进制格式而非文本格式
- HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个连接即可实现并行
- 使用报头压缩,HTTP/2降低了开销
- HTTP/2让服务器可以将响应主动“推送”到客户端缓存中
HTTP/2的新特性之一是基于流的流量控制。
不同于HTTP/1.1,只要客户端可以处理,服务端就会尽可能快的发送数据,HTTP/2提供了客户端调整传输速度的能力(服务端也可以)。WINDOW_UPDATE帧用来完成这件事情,每个帧告诉对方,发送方想要接收多少字节,它将发送一个WINDOW_UPDATE帧以指示其更新后的处理字节能力。
作用:防止发送方发送数据太多,耗尽接收方资源,从而使接收方来不及处理,造成数据溢出,造成丢包.
进程和线程的区别
一、关于进程和线程,首先从定义上理解就有所不同
1、进程是什么?
是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独立运行的一段程序。
2、线程又是什么?
线程进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。
在运行时,只是暂用一些计数器、寄存器和栈 。
二、他们之间的关系
1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
2、资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3、线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
4、处理机分给线程,即真正在处理机上运行的是线程。
5、线程是指进程内的一个执行单元,也是进程内的可调度实体。
三、从三个角度来剖析二者之间的区别
1、调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
2、并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
3、拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
并发和并行
并发当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。
并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
区别:
并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;
而并发是指两个或多个事件在同一时间间隔内发生。
在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。
倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。
如何理解泛型,它在底层是如何存储的?
自JDK 1.5 之后,Java 通过泛型解决了容器类型安全这一问题,而几乎所有人接触泛型也是通过Java的容器。那么泛型究竟是什么?
泛型的本质是参数化类型
也就是说,泛型就是将所操作的数据类型作为参数的一种语法。
泛型的作用
- 使用泛型能写出更加灵活通用的代码
- 泛型将代码安全性检查提前到编译期
- 泛型能够省去类型强制转换
为了实现以上功能,Java 设计者将泛型完全作为了语法糖加入了新的语法中,什么意思呢?也就是说泛型对于JVM来说是透明的,有泛型的和没有泛型的代码,通过编译器编译后所生成的二进制代码是完全相同的。
这个语法糖的实现被称为擦除
擦除的过程
泛型是为了将具体的类型作为参数传递给方法,类,接口。
擦除是在代码运行过程中将具体的类型都抹除。
可以看到泛型就是在使用泛型代码的时候,将类型信息传递给具体的泛型代码。而经过编译后,生成的.class文件和原始的代码一模一样,就好像传递过来的类型信息又被擦除了一样。
java是编译执行还是解释执行?
1 | 解释执行:将编译好的字节码一行一行地翻译为机器码执行。 |
Java采用的是解释和编译混合的模式。它首先通过javac将源码编译成字节码文件class.然后在运行的时候通过解释器或者JIT将字节码转换成最终的机器码。
单独使用解释器的缺点:
抛弃了JIT可能带来的性能优势。如果代码没有被JIT编译的话,再次运行时需要重复解析。
单独使用JIT的缺点:
需要将全部的代码编译成本地机器码。要花更多的时间,JVM启动会变慢非常多;
增加可执行代码的长度(字节码比JIT编译后的机器码小很多),这将导致页面调度,从而降低程序的速度。
有些JIT编译器的优化方式,比如分支预测,如果不进行profiling,往往并不能进行有效优化。
因此,HotSpot采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。
JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协作使用。
注:JIT为方法级,它会缓存编译过的字节码在CodeCache中,而不需要被重复解释。

Jit工作原理
当JIT编译启用时(默认是启用的),JVM读入.class文件解释后,将其发给JIT编译器。JIT编译器将字节码编译成本机机器代码。
通常javac将程序源码编译,转换成java字节码,JVM通过解释字节码将其翻译成相应的机器指令,逐条读入,逐条解释翻译。非常显然,经过解释运行,其运行速度必定会比可运行的二进制字节码程序慢。为了提高运行速度,引入了JIT技术。
在执行时JIT会把翻译过的机器码保存起来,已备下次使用,因此从理论上来说,採用该JIT技术能够,能够接近曾经纯编译技术。
TCP为什么是四次挥手,而不是三次?
因为TCP是全双工通信的
(1)第一次挥手
因此当主动方发送断开连接的请求(即FIN报文)给被动方时,仅仅代表主动方不会再发送数据报文了,但主动方仍可以接收数据报文。
(2)第二次挥手
被动方此时有可能还有相应的数据报文需要发送,因此需要先发送ACK报文,告知主动方“我知道你想断开连接的请求了”。这样主动方便不会因为没有收到应答而继续发送断开连接的请求(即FIN报文)。
(3)第三次挥手
被动方在处理完数据报文后,便发送给主动方FIN报文;这样可以保证数据通信正常可靠地完成。发送完FIN报文后,被动方进入LAST_ACK阶段(超时等待)。
(4)第四挥手
如果主动方及时发送ACK报文进行连接中断的确认,这时被动方就直接释放连接,进入可用状态。
说法二:
因为TCP有个半关闭状态,假设A.B要释放连接,那么A发送一个释放连接报文给B,B收到后发送确认,这个时候A不发数据,但是B如果发数据A还是要接受,这叫半关闭。然后B还要发给A连接释放报文,然后A发确认,所以是4次。
为什么是三次握手?
一.资源浪费观点
引自《计算机网络》释疑与习题解答 谢希仁
如果只有两次握手,那么当客户端的SYN连接请求在网络中阻塞,导致客户端没有接收到ACK报文,就会重新发送SYN,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的ACK确认信号,所以每收到一个SYN就只能主动建立一个连接,这会造成什么情况呢?如果客户端的SYN阻塞了,重复发送多次SYN报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
即两次握手会造成消息滞留情况下,服务器重复接受无用的连接请求SYN报文,而造成重复分配资源。
三、初始序列号
三次握手的本质是为了同步双方的初始序列号:
为了实现可靠数据传输,TCP 协议的通信双方,都必须维护一个序列号, 以标识发送出去的数据包中,哪些是已经被对方收到的。
三次握手的过程即是通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值的必经步骤。如果只是两次握手,至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认。TCP建立连接的握手,实质上就是建立一个双向的可靠通信连接,一边一个来回,每一边都自带超时重传来确保可靠性(而不是靠握手的次数)。
握手的作用,旨在确定两个双向的初始序列号,TCP用序列号来编址传输的字节,由于是两个方向的连接,所以需要两个序列号。
如何理解代理,代理的作用?
链接:https://blog.csdn.net/liguangix/article/details/80858807
静态代理
1、静态代理
静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
1 | /** |
Student类实现Person接口。Student可以具体实施上交班费的动作。
1 | public class Student implements Person { |
StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。
1 | /** |
如何使用代理模式:
1 | public class StaticProxyTest { |
2、动态代理
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:
1 | public void giveMoney() { |
- 创建一个InvocationHandler对象
1 | //创建一个与代理对象相关联的InvocationHandler |
- 使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
1 | Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class}); |
- 获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor
1 | Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class); |
- 通过构造器constructor来创建一个动态实例stuProxy
1 | Person stuProxy = (Person) cons.newInstance(stuHandler); |
就此,一个动态代理对象就创建完毕,当然,上面四个步骤可以通过Proxy类的newProxyInstances方法来简化:
1 | //创建一个与代理对象相关联的InvocationHandler |
小红书
为什么索引要最左匹配原则?
我们都知道索引的底层是一颗B+树,那么联合索引当然还是一颗B+树,只不过联合索引的健值数量不是一个,而是多个。构建一颗B+树只能根据一个值来构建,因此数据库依据联合索引最左的字段来构建B+树。
例子:假如创建一个(a,b)的联合索引,那么它的索引树是这样的

可以看到a的值是有顺序的,1,1,2,2,3,3,而b的值是没有顺序的1,2,1,4,1,2。所以b = 2这种查询条件没有办法利用索引,因为联合索引首先是按a排序的,b是无序的。
同时我们还可以发现在a值相等的情况下,b值又是按顺序排列的,但是这种顺序是相对的。所以最左匹配原则遇上范围查询就会停止,剩下的字段都无法使用索引。例如a = 1 and b = 2 a,b字段都可以使用索引,因为在a值确定的情况下b是相对有序的,而a>1and b=2,a字段可以匹配上索引,但b值不可以,因为a的值是一个范围,在这个范围中b是无序的。
tcp滑动窗口
首先明确:
1)TCP滑动窗口分为接受窗口,发送窗口
滑动窗口协议是传输层进行流控的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度,从而达到防止发送方发送速度过快而导致自己被淹没的目的。
对ACK的再认识,ack通常被理解为收到数据后给出的一个确认ACK,ACK包含两个非常重要的信息:
一是期望接收到的下一字节的序号n,该n代表接收方已经接收到了前n-1字节数据,此时如果接收方收到第n+1字节数据而不是第n字节数据,接收方是不会发送序号为n+2的ACK的。
举个例子,假如接收端收到1-1024字节,它会发送一个确认号为1025的ACK,但是接下来收到的是2049-3072,它是不会发送确认号为3072的ACK,而依旧发送1025的ACK。
二是当前的窗口大小m,如此发送方在接收到ACK包含的这两个数据后就可以计算出还可以发送多少字节的数据给对方,假定当前发送方已发送到第x字节,则可以发送的字节数就是y=m-(x-n).这就是滑动窗口控制流量的基本原理。
spring事务是怎么设计的?
SingleThreadConnectionHolder

本来线程不安全的,通过ThreadLocal这么封装一下,立刻就变成了线程的局部变量,不仅仅安全了,还保证了一个线程下面的操作拿到的Connection是同一个对象!这种思想,确实非常巧妙,这也是无锁编程思想的一种方式!
一个线程中的一个事务的多个操作,使用的是同一个Connection!
b+数–相关面试题
问题1:MySQL中存储索引用到的数据结构是B+树,B+树的查询时间跟树的高度有关,是log(n),如果用hash存储,那么查询时间是O(1)。既然hash比B+树更快,为什么mysql用B+树来存储索引呢?
答:一、从内存角度上说,数据库中的索引一般时在磁盘上,数据量大的情况可能无法一次性装入内存,B+树的设计可以允许数据分批加载。
二、从业务场景上说,如果只选择一个数据那确实是hash更快,但是数据库中经常会选中多条这时候由于B+树索引有序,并且又有链表相连,它的查询效率比hash就快很多了。
问题2:为什么不用红黑树或者二叉排序树?
答:树的查询时间跟树的高度有关,B+树是一棵多路搜索树可以降低树的高度,提高查找效率
问题3:既然增加树的路数可以降低树的高度,那么无限增加树的路数是不是可以有最优的查找效率?
答:这样会形成一个有序数组,文件系统和数据库的索引都是存在硬盘上的,并且如果数据量大的话,不一定能一次性加载到内存中。有序数组没法一次性加载进内存,这时候B+树的多路存储威力就出来了,可以每次加载B+树的一个结点,然后一步步往下找,
问题4:在内存中,红黑树比B树更优,但是涉及到磁盘操作B树就更优了,那么你能讲讲B+树吗?
B+树是在B树的基础上进行改造,它的数据都在叶子结点,同时叶子结点之间还加了指针形成链表。
下面是一个4路B+树,它的数据都在叶子结点,并且有链表相连。
问题5:为什么B+树要这样设计?
答:这个跟它的使用场景有关,B+树在数据库的索引中用得比较多,数据库中select数据,不一定只选一条,很多时候会选中多条,比如按照id进行排序后选100条。如果是多条的话,B树需要做局部的中序遍历,可能要跨层访问。而B+树由于所有数据都在叶子结点不用跨层,同时由于有链表结构,只需要找到首尾,通过链表就能把所有数据取出来了。
比如选出7到19只需要在叶子结点中就能找到。
携程
java的内存模型
线程通信:
线程的通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种共享内存和消息传递。
在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信。
在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在java中典型的消息传递方式就是wait()和notify()。
java内存模型(JMM)
Java线程之间的通信采用的是过共享内存模型,这里提到的共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。

volatile关键字不支持原子性操作
AtomicInteger底层原理是cas
producer生产者保证安全,什么情况下会丢失数据,什么情况下会多数据?
spark streaming消费kafka的两种方式
- Receiver是使用高层次的consumer Api来实现的。
receiver 接收的消息都是存储在spark Executor中的,然后spark启动job去处理那些消息;
然而,默认情况下,这种方式会因为底层的失败丢失数据。
如果要启用高可靠机制,让数据零丢失,就必须启用spark streaming的预写日志机制,(Write Ahead Log,WAL)。
该机制会同步的将kafka数据写入到分布式文件系统(如hdfs上)上的预写日志中。所以,即使底层节点出现问题,也可以
使用预写日志中的数据进行恢复,但是效率会下降。
- direct这种方式会周期性的查询kafka,来获取每个topic+partition的每个offset,从而定义每个batch的offset的范围。
当处理数据job启动时,就会使用简单的api来获取指定的offset范围的数据。
direct方式的优点:
1)简化并行读取
如果要读取多个partition,不需要创建多个输入Dstream然后对他们进行union操作,spark会创建跟kafka partition一样多的rdd partition,并行的从kafka读取数据。所以在kafka partition跟rdd partition之间,有一个一对一的映射。
2)高性能
如果要保证零数据丢失,在基于receiver的方式中需要开启WAL机制,这种方式效率低下,因为要保存两份数据,kafka本身就有高可靠的机制,会对数据复制一份,而这里又会复制一份到WAL中。而direct方式只要kafka中复制一份,就可以通过kafka的副本进行恢复。
3)一次且仅一次的事务机制
基于receiver的方式,是使用高层次api在zk中保存消费过的offset这是传统的消费方式。这种方式配合WAL实现数据零丢失,但是却无法保证数据消费仅一次,可能会处理两次。因为spark和zk之间可能是不同步的。direct方式自己追踪消费的offset,并保存在checkpoint中,spark自己一定是同步的,消息仅消费一次。
spark checkpoint
- 引入checkpoint机制原因
Spark 在生产环境下经常会面临 Transformation 的 RDD 非常多(例如一个Job 中包含1万个RDD) 或者是具体的 Transformation 产生的 RDD 本身计算特别复杂和耗时(例如计算时常超过1个小时) , 这个时候如果可以对计算的过程进行复用,就可以极大的提升效率,此时我们必需考虑对计算结果的持久化。
如果采用 persists 把数据持久化在内存中的话,虽然最快速但是也是最不可靠的(内存清理);如果放在磁盘上也不是完全可靠的,例如磁盘会损坏,系统管理员可能会清空磁盘。
Checkpoint 的产生就是为了相对而言更加可靠的持久化数据,在 Checkpoint 可以指定把数据放在本地并且是多副本的方式,在正常生产环境下通常放在 HDFS 上,借助HDFS 高可靠的特征来实现更可靠的数据持久化。
- checkpoint在spark的两块应用
(1)一块是在spark core中对RDD做checkpoint,将RDD数据保存到可靠存储(如HDFS)以便数据恢复;
通过将计算代价较大的 RDD checkpoint 一下,当下游 RDD 计算出错时,可以直接从 checkpoint 过的 RDD 那里读取数据继续算。
(2)应用在spark streaming中,使用checkpoint用来保存DStreamGraph以及相关配置信息,以便在Driver崩溃重启的时候能够接着之前进度继续进行处理(如之前waiting batch的job会在重启后继续处理)。
垃圾回收器g1,cms
涂鸦智能一面:
kafka不丢失数据?
1.生产者数据的不丢失
kafka的ack机制:在kafka发送数据的时候,每次发送消息都会有一个确认反馈机制,确保消息正常的能够被收到。
如果是同步模式:ack机制能够保证数据的不丢失,如果ack设置为0,风险很大,一般不建议设置为0
1 | producer.type=sync |
如果是异步模式:通过buffer来进行控制数据的发送,有两个值来进行控制,时间阈值与消息的数量阈值,如果buffer满了
数据还没有发送出去,如果设置的是立即清理模式,风险很大,一定要设置为阻塞模式.
结论:producer有丢数据的可能,但是可以通过配置保证消息的不丢失
1 | producer.type=async |
2.消费者数据的不丢失
通过offset commit 来保证数据的不丢失,kafka自己记录了每次消费的offset数值,下次继续消费的时候,接着上次的
offset进行消费即可。
kafka不重复消费?(如何保证消息消费时的幂等性)
幂等性:其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
(1)比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下好吧
(2)比如你是写redis,那没问题了,反正每次都是set,天然幂等性
(3)比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去比如redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个id写redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
Synchronized和ReentrantLock的区别
Synchronized原理:
Synchronized进过编译,会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。
ReentrantLock
由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项:
1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。
2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。公平锁、非公平锁的创建方式:
//创建一个非公平锁,默认是非公平锁Lock lock = new ReentrantLock();Lock lock = new ReentrantLock(false); //创建一个公平锁,构造传参trueLock lock = new ReentrantLock(true);
3.锁绑定多个条件,一个ReentrantLock对象可以同时绑定多个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。await() 和 signal():等待和唤醒
内存溢出,如何去定位?
说说项目中你觉得可以体现你能力的点?
秒杀场景的设计
什么是超卖?
多线程扣减库存(内存中,秒杀场景)时候,线程安全问题。
链接:https://blog.csdn.net/u010391342/article/details/84372342
阿里一面
你的研究方向?
java为什么可以“一次编译,到处运行”?
java的数据类型有哪些?
封装、继承、多态
链接:https://www.cnblogs.com/IzuruKamuku/p/14359762.html
类加载过程?
双亲委派机制?
哪里用到过线程池?
秒杀场景(队列化泄洪)
用一个newFixedThreadPool(20)
Thread和runnable的区别?
数据库有哪些索引?索引的结构?为什么最左匹配?
金山云一面:
vim命令相关、linux命令
Hadoop安装中遇到哪些问题
说说项目(大数据项目)怎么做的?
唯品会一面:
spark的yarn-client和yarn-cluster方式有什么区别
链接:https://lixiangbetter.github.io/2020/07/06/Spark笔记/
thread local的底层
非常好的一篇博客:https://www.cnblogs.com/tuyang1129/p/12713815.html
线程池核心参数解释
红黑树删除一个节点的过程
删除一个节点
红黑树的性质
where和having的区别
hadoop的shuffle和spark的shuffle的区别
jvm的g1垃圾回收器
你平时有用到哪些集合?concurrent hashmap原理?
链接:https://blog.csdn.net/weixin_30819085/article/details/95117136
链接:https://blog.csdn.net/qq_41737716/article/details/90549847
aop的动态代理具体怎么写的?
链接:https://blog.csdn.net/liguangix/article/details/80858807