说说 JVM 的内存分区
线程私有的区域
程序计数器: JVM 中程序计数器相当于汇编语言中的 CPU 中的寄存器, 保存程序当前执行的指令的地址.
虚拟机栈: Java 方法执行的栈由许多个栈帧构成, 每个栈帧对应一个被调用的方法, 在栈帧中包括局部变量表 (Local Variables), 操作数栈(Operand Stack), 指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到) 的引用 (Reference to runtime constant pool), 方法返回地址(Return Address) 和一些额外的附加信息. 当线程执行一个方法时, 就会随之创建一个对应的栈帧, 并将建立的栈帧压栈. 当方法执行完毕之后, 便会将栈帧出栈.
本地方法栈: 与虚拟机栈相似, 本地方法栈是为执行本地方法服务的. 在 JVM 规范中, 并没有强制规定本地方法栈的具体实现方法以及数据结构. 在 HotSopt 虚拟机中直接就把本地方法栈和 Java 栈合二为一.
线程共享的区域
方法区: 存储了每个类的信息(包括类的名称, 方法信息, 字段信息), 静态变量, 常量以及编译器编译后的代码等.
堆内存: 用来存储对象本身的以及数组(数组引用是存放在 Java 栈中的). 只不过和 C 语言中的不同, 在 Java 中, 程序员基本不用去关心空间释放的问题, Java 的垃圾回收机制会自动进行处理. 因此这部分空间也是 Java 垃圾收集器管理的主要区域. 在 JVM 中只有一个堆.
Java 对象的回收算法
分代收集法: 它根据对象的生存周期, 将堆分为新生代和老年代. 在新生代中, 由于对象生存期短, 每次回收都会有大量对象死去, 那么这时就采用复制算法. 老年代里的对象存活率较高, 没有额外的空间进行分配担保, 所以可以使用标记整理或者标记清除.
标记清除法: 将需要回收的对象标记出来, 批量清除. 缺点是效率低, 产生碎片.
标记整理法: 将需要回收的对象标记出来, 移动到一端再清除, 避免了碎片的产生.
复制法: 将新生代内存划分为 8:1:1 三部分, 较大的叫 Eden 区, 其余是两块较小的 Survior 区. 每次都会优先使用 Eden 区, 若 Eden 区满, 就将对象复制到第二块内存区上, 然后清除 Eden 区, 如果此时存活的对象太多, 以至于 Survivor 不够时, 会将这些对象通过分配担保机制复制到老年代中.
CMS 和 G1 了解么, CMS 解决什么问题, 说一下回收的过程.
CMS, 全称 Concurrent Mark and Sweep, 用于对年老代进行回收, 目标是尽量减少应用的暂停时间, 减少 full gc 发生的机率, 利用和应用程序线程并发的垃圾回收线程来标记清除年老代. CMS 并非没有暂停, 而是用两次短暂停来替代串行标记整理算法的长暂停.
CMS 回收为什么要停顿两次
当虚拟机完成两次标记后, 便确认了可以回收的对象. 但是, 垃圾回收并不会阻塞我们程序的线程, 他是与当前程序并发执行的. 所以问题就出在这里, 当 GC 线程标记好了一个对象的时候, 此时我们程序的线程又将该对象重新加入了 "关系网" 中, 当执行二次标记的时候, 该对象也没有重写 finalize()方法, 因此回收的时候就会回收这个不该回收的对象. 虚拟机的解决方法就是在一些特定指令位置设置一些 "安全点", 当程序运行到这些 "安全点" 的时候就会暂停所有当前运行的线程(Stop The World 所以叫 STW), 暂停后再找到 "GC Roots" 进行关系的组建, 进而执行标记和清除.
Java 栈和堆什么时候会发生内存溢出
栈溢出: 递归调用超过栈帧的深度, 就会出现 StackOverflowError.
堆溢出: 堆内存不足, 且 GC 来不及或者无法回收内存时, 就会发生堆溢出, 比如在集合中大量创建对象直至堆内存耗尽.
软引用和弱引用, 虚应用的区别和使用场景.
软引用: 描述一些有用但非必需的对象, 用 java.lang.ref.SoftReference 类来表示. 对于软引用关联着的对象, 只有在内存不足的时候 JVM 才会回收该对象, 适合用来实现缓存.
弱引用: 弱引用的对象拥有更短暂的生命周期, 用 java.lang.ref.WeakReference 类表示. 在垃圾回收器线程扫描它所管辖的内存区域的过程中, 一旦发现了只具有弱引用的对象, 不管当前内存空间足够与否, 都会回收它的内存.
虚引用: 顾名思义就是形同虚设, 用 java.lang.ref.PhantomReference 类表示. 与其他几种引用都不同, 虚引用并不会决定对象的生命周期. 如果一个对象仅持有虚引用, 那么它就和没有任何引用一样, 在任何时候都可能被垃圾回收. 虚引用主要用来跟踪对象被垃圾回收的活动.
Java 的锁有哪些, 怎么使用, 实现原理是什么
synchronized: 用于方法或者对象的同步锁, 实现原理是操作系统的互斥锁, 属于重量级锁. 在 JDK6 以后加入锁升级, 提高了性能.
Lock: 基于 AQS 的可重入的轻量级锁, 实现类有 ReentrantLock,ReadWriteLock,ReentrantReadWriteLock. 相比 synchronized, 它的使用更加灵活, 且需要手动释放锁.
说说 synchronized 锁升级的过程
参考:
Tomcat 类加载器结构
参考: https://www.jianshu.com/p/c205a74aa4da
Spring 中如何让 A 和 B 两个 bean 按顺序加载
采用 dependOn 注解
beanfactory 和 applicationcontext 是什么关系, 有什么区别
BeanFactory: 是 Spring 里面最底层的接口, 包含了各种 Bean 的定义, 读取 bean 配置文档, 管理 bean 的加载, 实例化, 控制 bean 的生命周期, 维护 bean 之间的依赖关系. ApplicationContext 接口作为 BeanFactory 的派生, 除了提供 BeanFactory 所具有的功能外, 还提供了更完整的框架功能:
继承 MessageSource, 因此支持国际化.
统一的资源文件访问方式.
提供在监听器中注册 bean 的事件.
同时加载多个配置文件.
载入多个 (有继承关系) 上下文 , 使得每一个上下文都专注于一个特定的层次, 比如应用的 web 层.
BeanFactroy 采用的是延迟加载形式来注入 Bean 的, 即只有在使用到某个 Bean 时(调用 getBean()), 才对该 Bean 进行加载实例化. 这样, 我们就不能发现一些存在的 Spring 的配置问题. 如果 Bean 的某一个属性没有注入, BeanFacotry 加载后, 直至第一次使用调用 getBean 方法才会抛出异常.
ApplicationContext, 它是在容器启动时, 一次性创建了所有的 Bean. 这样, 在容器启动时, 我们就可以发现 Spring 中存在的配置错误, 这样有利于检查所依赖属性是否注入. ApplicationContext 启动后预载入所有的单实例 Bean, 通过预载入单实例 bean , 确保当你需要的时候, 你就不用等待, 因为它们已经创建好了.
相对于基本的 BeanFactory,ApplicationContext 唯一的不足是占用内存空间. 当应用程序配置 Bean 较多时, 程序启动较慢.
BeanFactory 通常以编程的方式被创建, ApplicationContext 还能以声明的方式创建, 如使用 ContextLoader.
BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor,BeanFactoryPostProcessor 的使用, 但两者之间的区别是: BeanFactory 需要手动注册, 而 ApplicationContext 则是自动注册.
MySQL 里如何做一条 SQL 的优化
采用 explain 查看 SQL 的执行计划, 比如执行
- explain select id from orders
- ,
出来的信息有 10 列, 分别是 id,select_type,table,type,possible_keys,key,key_len,ref,rows,Extra, 比较重要的列:
key: 表示实际使用的索引
ref: 列与索引的比较
rows: 扫描出的行数
通过查询计划的信息, 判断这条 SQL 是否需要增加索引, 修改写法等等.
MySQL 集群的主从复制怎么做的, 具体有哪些线程做哪些事情, 使用了哪些日志.
主库在事务提交时, 会把数据库变更作为事件记录在二进制文件 binlog 中; MySQL 主库上的 sys_binlog 控制 binlog 日志刷新到磁盘.
主库推送二进制文件 binlog 中的事件到从库的中继日志 relay log, 之后从库根据中继日志重做数据库变更操作. 通过逻辑复制, 以此来达到数据一致.
MySQL 通过 3 个线程来完成主从库之间的数据复制: 其中 BinLog Dump 线程跑在主库上, I/O 线程和 SQl 线程跑在从库上. 当从库启动复制 (start slave) 时, 首先创建 I/O 线程连接主库, 主库随后创建 Binlog Dump 线程读取数据库事件并发给 I/O 线程, I/O 线程获取到数据库事件更新到从库的中继日志 Realy log 中去, 之后从库上的 SQl 线程读取中继日志 relay log 中更新的数据库事件并应用.
CAP 定理了解吗, 为什么只能三选二, 为什么分区容忍性必须保证
Consistency(一致性): 多个节点之间的数据保持一致.
Availability(可用性): 只要收到用户的请求, 服务器就必须给出回应. 用户可以选择向 G1 或 G2 发起读操作. 不管是哪台服务器, 只要收到请求, 就必须告诉用户, 到底是 v0 还是 v1, 否则就不满足可用性
Partition tolerance(分区容错性): 多个节点之间必然出现无法通信, 分布式系统必须接收这种状况.
一般来说, 节点故障, 网络故障是常态, 分区容错无法避免, 因此 CAP 的 P 总是成立, 剩下的 C 和 A 无法同时做到.
参考: http://www.ruanyifeng.com/blog/2018/07/cap.html https://www.ruanyifeng.com/blog/2018/07/cap.html
哪些技术是牺牲了一致性来保证可用性
MySQL 主从复制就是典型的牺牲 C, 保证 A 的做法.
如何高效处理 10 亿个数字去重
采用 bitmap, 参考: https://www.jianshu.com/p/b39eb55d4670
如何在十亿个数找前 10 个最大的数
采用最小堆算法, 参考:
来源: http://www.bubuko.com/infodetail-3209824.html