《天净沙·我·被字节血洗篇》 双非菜鸡奇葩,面试项目框架,java java,卑微学子去哪?


别问 问就是为了面试豁出了老命
总结了3.15日字节面试的一些问题,鲜血淋漓篇

高并发 - 为什么Synchronized可以保证线程的安全?

博客参考

JVM中对于同步方法和同步代码块是不一样的,本质上都有monitor对象,但是同步方法使用了monitorentre/monitorexit,而同步代码块则使用了ACC_SYNCHRONIZED标志(隐式)

对象头

Java中在JVM的处理上,每一个对象都会有一个对象头,monitor 存在于对象头的Mark Word 中(存储monitor引用指针)

Jvm对同步方法的处理(monitorenter/monitorexit)

monitorenter

  1. 首先一个对象要有自己的monitor,当对象的monitor被占用的时候,则monitor被锁定,之后当一个线程需要获取该对象的时候,则需要调用monitorexit指令,首先要判断monitor的进入数,如果进入数为0,那么monitorenter进入成功,线程进入monitor,然后将进入数设为1,该线程则成为monitor的所有者。
  2. 如果线程已经占有了monitor,只是重新进入,那么monitor的进入数+1
  3. 如果其它线程在monitor被占领的时候来获取monitor那么,该线程则自动进入到阻塞状态,知道monitor的进入数为0,再重新尝试获取monitor的权限

monitorexit

  1. 执行monitorexit必须是该对象monitor的所有者
  2. monitorexit执行成功的时候,则monitor的进入数-1

Jvm对同步代码块的处理(ACC_SYNCHRONIZED)

  1. 对于同步代码块,其常量池多了ACC_SYNCHRONIZED标志,也是flag的地方多了ACC_SYNCHRONIZED
  2. 同步代码块被调用的时候,JVM会调用指令去检查ACC_SYNCHRONIZED是否被设置,如果设置了则获取monitor,获取成功后则执行代码块中的内容,执行期间其它的线程将无法再获得monitor

wait/notify/notifyall

  1. wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因
  2. 要特别理解的一点是,与sleep方法不同的是wait方法调用完成后,线程将被暂停,但wait方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll方法后方能继续执行,而sleep方法只让线程休眠并不释放锁。同时notify/notifyAll方法调用后,并不会马上释放监视器锁,而是在相应的synchronized(){}/synchronized方法执行结束后才自动释放锁

Interrupt

  1. //中断线程(实例方法)
    public void Thread.interrupt();
  2. //判断线程是否被中断(实例方法)
    public boolean Thread.isInterrupted();
  3. //判断是否被中断并清除当前中断状态(静态方法)
    public static boolean Thread.interrupted();

Interrupt的作用

可以直接中断阻塞的线程,但是无法阻塞获取锁或者拿到锁的线程

JVM - private final a = 1; 请问他的创建的过程?

答:

  1. 加载
  2. 验证
  3. 准备

    在准备阶段就直接将将值进行替换,不占用内存空间

  4. 解析
  5. 初始化

深入:

static 静态变量在正常情况下是 在准备阶段在方法区分配空间,但是直到最后的初始化阶段才正确的赋值
例: public static int value = 5
在准备阶段 value 已在方法区获得空间,但是值为0
在最后的初始化阶段才真正的编程 5

加载,验证阶段的内容补充

加载

在加载阶段(可以参考java.lang.ClassLoader的loadClass()方法),虚拟机需要完成以下3件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流(并没有指明要从一个Class文件中获取,可以从其他渠道,譬如:网络、动态生成、数据库等);
  1. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
  1. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;

加载阶段和连接阶段(Linking)的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的内容,这两个阶段的开始时间仍然保持着固定的先后顺序。

验证

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
验证阶段大致会完成4个阶段的检验动作:

  1. 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以魔术0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
  1. 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
  1. 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  1. 符号引用验证:确保解析动作能正确执行。

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间

JVM - GC回收的时候那些垃圾是怎么被标识的?

语义双关注意

可能面试官想问的是标记垃圾的方法(可达性分析和程序计数器法)
还可能是想问具体是怎么标记的?

相关的内容补充

OopMap

垃圾回收的时候需要对栈上的内存进行扫描,判断哪些位置存在Reference类型,但是如果是直接查找的话,会产生遍历的问题,这样是慢的。
所以HotSpot用OopMap对Reference进行存储,这样子就可以用空间换时间,避免了全栈的扫描

三色标记

并发标记

  1. 黑色:根对象,或者该对象与它的子对象都被扫描过(对象被标记了,且它的所有field也被标记完了)。
  2. 灰色:对象本身被扫描,但还没扫描完该对象中的子对象(它的field还没有被标记或标记完)。
  3. 白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,既垃圾对象(对象没有被标记到)。

    卡表

    (RememberSet)[https://img-blog.csdn.net/20170705174843116?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGZ6MDMzMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center]

以G1收集器为例,G1由于将内存空间分配成了Region,因此如果每个Region都去记录自己的对象引用了什么其它的对象,会造成很多不必要的开销,那么如果换个思维只记录自己的新生代被谁的老年代引用了就可以减小开销(因为新生代无论如何都是要被全部扫描的,记录不同Region新生代与新生代之间的引用是没有意义的,同时老年代很少,因此扫描老年代是不是引用其它新生代会更快),如果被记录的话,则直接假如到GcRoot中这样就可以避免一些不必要的扫描。
开始说卡表,每个Region被分成了若干个卡(Card),这些Card都会记录在全局卡表中,Card中每个元素对应着内存区域中一个特定大小的内存块,这个内存块则被称之为卡页,一个卡页一般里面不只一个对象,所以卡页存在着跨Region引用,那么这样的元素的值标识为1。
这样在Minor GC时,只需要将变脏的Region(写屏障,这个事情是并发标记的时候完成的,直接加入到GcRoot可以提升效率)中的那个卡页加入GC Roots一并扫描即可。比起扫描老年代的所有对象,大大减少了扫描的数据量,提升了效率

TCP 的连接过程(从网络层去分析)

TCP三次握手 四次招手

口述TCP是如何保证数据传输的?

线程共享的部分

  • 堆 由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;因此new出来的都是共享的(16位平台上分全局堆和局部堆,局部堆是独享的)

  • 全局变量 它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的

  • 静态变量 虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的

  • 文件等公用资源 这个是共享的,使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。

深入到操作系统线程之间什么什么是独享的资源

  • 栈 栈是独享的
  • 寄存器

    请问只要是加了Synchronized就安全了吗?

    静态方法 和 实例方法 的区别

    有关List集合的删改功能知道吗?

    就是利用System.copy的功能往前或者往后移动一位
    扩容机制就是利用调用EnsureCapacityInternal()方法去调用grow函数,再使用Arrays.copyOf进行扩容

Java 线程池的实现

线程池的优点

原帖
线程池能够对线程进行统一分配,调优和监控:

  • 降低资源消耗(线程无限制地创建,然后使用完毕后销毁)
  • 提高响应速度(无须创建线程)
  • 提高线程的可管理性

线程的执行过程

  1. 首先添加到核心线程池
  2. 核心线程池满以后,直接进入阻塞队列
  3. 阻塞队列满了以后判断最大核心数
  4. 如果未达到核心线程数目,则线程池进行扩容创建一个Worker执行提交任务,新建的Worker会被添加到线程集合workers中
  5. 如果超过maximumPoolSize会执行拒绝策略

    拒绝策略

  6. 调用线程执行 : 就是调用其它的线程
  7. 终止执行: 直接抛出RejectExecutionException异常
  8. 丢弃任务: 直接丢弃,不会抛异常
  9. 丢失老任务: 删除等待队列中最老的任务,然后重新执行

Mybatis缓存