1. 网络部分
TCP和UDP的认识和区别
TCP为什么是三次握手,两次握手不行吗?
感觉是二次握手就可以了,但是当服务端发送syn/ack给客户端,告诉客户端,我可以接收信息时。客户端并没有告诉服务端,收了了服务端的确认,所以需要客户端发送确认信息,告诉服务端,我收到了,我知道你可以接收信息,我们这就建立连接,发送数据。
TCP四次挥手的意义
确保客户端和服务端数据能够完全传输
tcp三次握手,可靠传输如何实现
这个题很重要,是在一个网友字节跳动面试时遇到
参考资料-01
参考资料-02
自我总结:
- 三次握手四次保证数据传输链接的可靠建立和完整的传输完数据,销毁连接。
- 校验保证传输数据的是没有被修改的
- 序列号和应答机制规定了数据传输的基本流程,也规定了字节流大小。应答机制也是重传机制,快速回复机制的基本元素。
- 重传机制,保证数据的完整性,其中较为可行的是基于数据的快速重传机制
- 滑动窗口机制,是流量控制的元素,接受方返回的报文中加入了可使用窗口大小,发送方根据窗口大小来控制数据发送速度,防止一大波数据发过去,接收方来不及接受
- 拥塞控制,通过使用窗口协议(cwnd),通过慢启动防止一开始大量数据导致阻塞,通过拥塞避免状态尽量避免阻塞,当发生阻塞,通过快重传,快恢复来保证数据完整性。
HTTP和HTTPS的区别
HTTPS和HTTP建立过程
HTTPS建立过程成需要加入SSL认证。
重定向和转发的区别
协程是什么?
2. Linux系统
3. Java基础
3.1 Java多态的实现原理
3.2 Java HashMap的详细介绍
-
关于1.8的HashMap是个难点,加通过数组+链表+红黑树来实现。
-
请认真思考为什么HashMap的容量一定是2^n ,而不会是其他,这与扩容有很大关系,方便扩容,要联系(b.hash & oldCap) == 0 这个判断。 这里是在扩容时原来的元素在扩容后重新定位的关键。
-
为什么jdk1.7扩容时头插法会引起死循环?不是线程安全的,会导致死锁问题。
3.3 正则表达式的书写
3.4 JDK 动态代理的原理,JDK 动态代理为什么只能针对接口
3.5 面向对象三大基本特征
这个知识点是比较基础,但是可有拓展点。就继承而言,
-
有一条设计原则,组合优先于继承。可以好好的了解一下为什么,以及组合的实际应用。
-
哪些情况下用组合,哪些情况下用继承?
判定标准是:是否需要从新类上溯造型会基类,如果必须上溯,则用继承,否则用组合。
Java线程有哪些状态,各个状态之间怎么转换?
synchronized的实现原理
锁升级机制是怎么样的流程,这其中关键知识
线程池实战,用过哪些线程池,线程池工作流程
关于Java初始化问题
首先变量初始化会在任何方法调用之前(包括静态方法)
初始化顺序: 父类静态代码块 -->子类静态代码块–>父类非静态便令–>父类构造函数–>子类非静态变量–>子类构造函数
注意:
通过子类来引用父类中的定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。
当编译阶段通过时,通过常量传播优化,已经将static final 类型的变量加入到(调用该常量的,而不是声明该常量的类)类的常量池中,这不会触发类的初始化
. JVM
注意,本内容全是基于《深入理解JVM》第三版进行学习,在某个知识点标记了页号,默认该这本书。
介绍下垃圾收集机制(在什么时候,对什么,做了什么)。
垃圾回收主要回收的是线程共享的区域,堆和方法去,其中因为堆存放程序运行时的所有实例,这里进行垃圾回收也更加频繁。
回收的对象主要是那些jvm认为没有用的对象,对其进行回收,对于如何判断java对象无用,采用根搜索算法进行判定。
堆内存分类新生代和老年代,采用分代收集算法,新生代和老年代采用不同的垃圾回收方法。新生代主要采用标记复制的方法进行垃圾回收,老年代采用标记整理算法进行垃圾回收。
垃圾收集有哪些算法,各自的特点。
- 标记-清除: 简单,好操作,但是会产生内存碎片
- 标记-复制::主要应用于新生代,用牺牲一半内存来保证没有内存碎片参数,空间代价大
- 标记-整理: 主要用于老年代,克服了标记-复制的缺点,也不会产生内存碎片
- 分代收集算法: 新生代和老年代应用不同的垃圾收集算法
类加载的过程
加载–>验证–>准备–>解析–>初始化–>使用–>卸载。
初始化前,必须经历加载、验证和准备阶段。
验证阶段主要是验证文件格式等是否符合当前虚拟机。
其中解析可以不按照顺序来,主要是为了动态绑定。
准备阶段会将类变量赋值为0值,真正进行赋值阶段是解析阶段。通过调用类加载器自动生成的<>client()方法进行赋值。 方法一定是在子类初始化之前完成的,而且不需要子类显示调用。
双亲委派模型
在加载一个方法是,子类一开始不自己调用自身的类加载器,而是转向父类进行类加载,当父类反馈找不到该方法时,子类才会执行自己的类加载。
双薪委派模型是基于三层类加载器基础上,这样做的好处是,对满足条件下,执行类加载,父类控制类加载过程,防止不同子类进行类加载,导致产生不同的类实例。
有哪些类加载器
- 启动类加载器
- 拓展类加载器
- 应用类加载器
- 自定义类加载器
怎么判断一个对象已经死了,可以回收?
通过两次标记,第一次标记不需要暂停攻错线程,第二次为了标记第一次程序正在运行,导致第一次有的对象标记发生变化,所以暂停线程进行第二次标记。
什么情况之下把新生代对象放到老年代去?
当 to suivivor 的空间不足时,将其中的对象存储到老年代中。默认年龄达到15岁的对象会被移到老年代中。
什么情况下不会执行类初始化?
双亲委派类加载
当一个类收到类加载请求是,他首先不会尝试自己去加载这个类,二货思把这个请求委派给父类去完成,每一个层次的类加载器都是这个样子。当父类加载器反馈不能完成此次类加载时,子类加载器才会尝试自己去加载。
类加载层次如下:
运算指令
类型转换指令
大范围类型向小范围类型转换时,不需要显示支出转换指令
小范围向大范围类型转换时,需要显示支出转换指令
指令如下:
注意:
窄转换指令永远不可能导致虚拟机抛出运行时异常,也就是说转换不会抛出异常,即使是精度受损,这要靠编程人员自己排查。这一点不注意,很可能出现bug
对象创建与访问指令
操作数栈管理指令
控制转移指令理解
JVM提供了十分丰富的int类型的条件分支比较。
因为各个类型的比较最终都会转化为int类型的比较操作,所以JVM提供了丰富的int类型条件分支比较。
控制转移指令如下:
方法调用和返回指令
- invokevirtual 调用对象的实例方法
- invokeinterface
- invokespecial
- invokestatic
- invokedynamic
返回指令:
- ireturn : 适用于boolean、byte、char、short、int
- lreturn
- freturn
- dreturn
- areturn :返回的是对象引用
同步指令
和好奇,看似高大上的并发、同步在JVM层面用的是什么指令。
一看还真简单,这个指令之前在阅读《Java并发编程艺术》一书中看到过。这个指令是成对使用的,上锁肯定也有解锁。
- Monitor
关于synchronized指令的支持:
- monitorenter : 进
- monitorexit : 出
类加载中为什么解析阶段有时靠一放在初始化之后
这一个特性是为了支持Java语言的运行时动态绑定特征,一直知道运行时动态绑定,但不知道其具体如何实现的,这里提供了突破口。
有且仅有六种情况必须立即对类进行初始化
P264**《深入理解JVM》第三版**
非数组类型的加载阶段是开发人员可控性最强的阶段
在这个阶段,开发人员可以定义自己的类加载器去控制二进制字节流的获取方式。如常用的代理模式也是这么弄得。 P267**《深入理解JVM》第三版**
验证阶段内容
验证阶段是JVM自我保护阶段,防止恶意攻击。因为可.clsse文件是可以改动的。
之前我在编译代码时就遇到一个问题,说我的.class文件版本号不适应当前虚拟机。在之前了解.class格式时,就了解到,字节码的版本号和虚拟机的版本号是有关系的。
验证阶段包括内容如下:
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证 :验证调用的外部类是否合法
准备阶段
为类变量分配内存兵设置类变量初始值。JDK8之后类变量会随着Class对象的一起存放在Java堆中。
注意:
-
记住一点,在这个阶段只有类变量被分配内存,实例变量并没有,实例变量将会咋对象实例化时随着对象一起分配在Java堆中。
-
这里对类变量的初始化不是设置类中给定的值,而是设置每个类型的零值。如int 类型为0,引用类型为null。 private static int value = 123; 这里初始化value= 0 而不是123. 真正赋值要等到初始化阶段。
P272《深入理解JVM》第三版 -
关于第二条也有特殊情况,如果是这样:public static final int value = 123 这里就会赋值 value = 123.
解析阶段
主要针对类或者接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限制符
对于上面解析的具体流程,看书
P276 《深入理解JVM》第三版
初始化阶段
初始化阶段就是执行类构造器()方法过程。()Java程序中直接编写的方法,是Javac编译器的自动生成物。
之前在准备阶段,关于类变量真正初始化阶段就是在这个阶段,通过()方法进行初始化。
-
()是编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的
-
()编译器收集的顺序是根据在类中定义的顺序来决定的
-
()也有父类,但是不需要显示的调用父类的(),父类在子类执行()前已经执行完该方法,所以之前的初始化题原理就是这样了。 第一个执行的()方法一定是java.lang.Object
-
()对于类或者接口不是必须的,没有类变量和静态代码块编译器就不会自动产生该方法。
-
在多线程下,JVM必须保证()正确的加锁和同步。及时阻塞,也是隐蔽的,不可见。
P279 《深入理解JVM》第三版
类加载器
类加载器和类本身一起共同确认了其在JVM中的唯一性,每一个类加载器都拥有一个独立的类名称空间。
由不同类加载器加载同一个类,在JVM中所得到的的不是同一个类,是不同的。这个点要引起重视。当自定义了类加载器,然而有默认的类加载器,对于加载同一个类,在JVM会得到两个类。
Java对象是如何在堆中存在的?
首先是Java对象创建过程 ,关于对象内存分配,有两种选择,指针碰撞和空闲列表。具体要看垃圾收集齐是否有压缩整理功能。
参考资料
当对象创建之后,在内存中是怎样存在的呢?
主要包含三分部分,对象头、实例数据和对齐填充
参考资料
CAS 和 AQS的实现原理
CAS: Compare and Swap
AQS: AbstractQuentsynchronizer - 抽象队列同步器
线程是如何获取锁的
首先要知道一点,线程获取的锁是对象的锁,而不是代码块的锁。每一个对象在对象头都留有对锁信息记录的空间。
那线程如何获取锁的呢?
- 在当前线程的栈帧中简历一个名为 Lock Record 的空间,将对象头中的Mark Work拷贝到 Lock Record中
- 虚拟机使用CAS尝试将Mark Record更新指向该线程的Lock Record,并将Lock Record的owner指向obhect mark recoed 如果成功,那么线程就获得了该对象的锁。
- 接下来就是锁升级了,这个流程不在赘述,需要了解的查看相关文章。被阻塞的线程自旋来获取锁,当再次被阻塞时,将锁升级为重量级锁。
关于静态分派问题-重载
在执行重载方法是,具体调用哪个方法是由静态类型决定的,而不是实际类型。 静态类型在编译器就已经确定了,而实际类型要在运行期间才能确定。
静态分派有个问题,有时候重载版本不是唯一的,执行哪个版本,是通过确定一个小队更合适的版本来执行,这里就有点凌磨两可了。
P305
动态分派-重写
根据实际类型决定调用哪个版本的方法。这里其实就是子类重写父类方法,将子类实例向上转型给父类,通过动态绑定,调用方法时,实际上调用的是子类重写父类的方法。
关于动态分配最关键的就是:invokevirtual指令
Java动态语言支持,关于动态性
在方法调用时加入了新的调用方法:invokedynamic
动态语言就是:类型检查的主体过程是运行期间而不是编译期间进行。变量无类型,变量值才有类型
Refalection和MethodHandel的区别
两个动能实现动态的调用方法,但是反射是在Java代码层面的方法调用,而MethodHandel是在字节码层面的方法调用。 反射所获取到目标方法的信息很多,而后者指获取到了执行该方法的相关信息,信息很少,那怎么才能正确实现方法调用呢?
P320
JVM参数
-Xmx 和 - Xms
这两个参数是用来设定堆内存大小的,前者为最大值,后者为最小值
总结
JVM的学习其实很早就开始了,阅读《深入理解JVM》这本书也有一两遍了,以前只是了解了内存模型,垃圾回收器等知识。在后来了解JVM发现,其实类加载这方面的知识其实更重要,对于了解Java原理还是很有帮助,这次重点看的是类加载。
此次学习是通过查看别人总结的面试题目,有了大概的了解,在通过阅读《深入理解JVM》深入理解。
做学习笔记,其实我自我感觉这种程度还是不够的,获取还需要阅读相关部分一两遍才会有更深入的理解。
以下是感觉不错的参考资料:
参考资料-01