JVM 的内存模型
JVM内存共分为虚拟机栈, 堆, 方法区,程序计数器, 本地方法栈五个部分
Java类加载机制
虚拟机把描述类的数据从class文件加载到内存中 ,并对数据进行校验,准备,解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程
具体加载加载过程
加载
加载时类加载的第一个过程
通过一个雷的全限定名获取该类的二进制流
将二进制流中的静态储存结构转化为方法区运行时的数据结构
在内存中生成该类的Class对象作为该类的数据访问入口
验证
验证class文件是否符合规范(看代码有没有错误)
准备
为类的静态变量分配内存并将其初始化为默认值, 这些内存都将在方法区中进行分配准备阶段不分配类中的实例变量的内存, 实例变量将会在对象实例化时随着对象还一起分配在java堆中
解析
该阶段主要完成符号引用到直接引用的转换动作, 解析动作不一定在初始化动作完成前 也可能在初始化之后
初始化
初始化阶段才开始执行类中定义的java代码
类加载机制的双亲委派模型机制
当一个类收到了类加载请求时, 不会自己先去加载这个类 而是将其委派给父类, 父类去加载 如果此时父类不能加载, 反馈给子类 , 由子类去娲女恒类的加载(好处: java 随着他的类加载器一起具备了一种带有优先级的层次关系)
程序计数器 (线程私有)
每个线程都有一个独立的程序计数器,这类内存也称为”线程私有”的内存.在执行java方法的时候,计数器记录的是虚拟机字节码指令的地址(当前指令的地址). 如果 是 Native方法 , 则为空
Java虚拟机栈(线程私有)
每个方法在执行的时候会创建一个栈帧 , 储存了局部变量表, 操作数, 动态链接, 方法返回地址(方法出入口).
每个方法从调用到执行完毕 , 对应一个栈帧从虚拟机栈中的入栈和出栈
通常说的栈一般指在虚拟机栈中的局部变量部分
局部变量所需内存在编译期间完成分配
如果线程请求的栈深度大于虚拟机所允许的深度 则会报 StackOVerflowError
如果虚拟机栈可以动态扩展 , 扩展到无法申请到足够的内存 则 会报OutOfMemoryError
方法调用时,创建栈帧,并压入虚拟机栈;方法执行完毕,栈帧出栈并被销毁
局部变量表
存放编译期间的各种基本数据类型,对象引用类型和函数返回地址
操作数栈
先进后出LIFO 最大深度由编译期确定 , 栈帧刚建立时, 操作数栈为空, 执行方法操作时, 操作数栈用于存放JVM从局部变量表复制的常量或者变量, 提供提取, 及结果入栈 , 也用于存放调用方法需要的参数及接受方法返回的结果
本地方法栈(线程私有)
本地方法栈(Native Method Stacks)与 Java 虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。
Java堆(线程共享)
被所有线程共享的一块内存区域, 在虚拟机启动的时候创建用于存放对象实例
当堆中没有内存可以分配给实例 也无法再扩展时 则抛出OutOfMemoryError异常
方法区(线程共享)
被所有方法线程共享的一块内存区域
用于储存已经被虚拟机加载的类的信息. 常量, 静态变量
这个区域的内存回收主要针对常量池的回收和堆类型的卸载
2.垃圾回收机制(Garbage Collection)
垃圾收集GC(Garbage Collection)是Java语言的核心技术之一, 在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理
为什么要进行垃圾回收
随着程序的运行,内存中存在的实例对象、变量等信息占据的内存越来越多,如果不及时进行垃圾回收,必然会带来程序性能的下降,甚至会因为可用内存不足造成一些不必要的系统异常
什么样的对象才是垃圾
对于Java对象来讲,如果说这个对象没有被其他对象所引用该对象就是无用的,此对象就被称为垃圾,其占用的内存也就要被销毁
什么时候进行垃圾回收
1> 该类的所有实例对象都已经被回收。2> 加载该类的ClassLoader已经被回收。3> 该类对应的反射类java.lang.Class对象没有被任何地方引用。
3.垃圾回收算法
标记-清除算法(Mark-Sweep)
o最基础的GC算法,将需要进行回收的对象做标记,之后扫描,有标记的进行回收,这样就产生两个步骤:标记和清除。这个算法效率不高,而且在清理完成后会产生内存碎片,这样,如果有大对象需要连续的内存空间时,还需要进行碎片整理,所以,此算法需要改进。
复制算法(Copying)
o新生代内存分为了三份,Eden区和2块Survivor区,一般Sun的JVM会将Eden区和Survivor区的比例调为8:1,保证有一块Survivor区是空闲的,这样,在垃圾回收的时候,将不需要进行回收的对象放在空闲的Survivor区,然后将Eden区和第一块Survivor区进行完全清理,这样有一个问题,就是如果第二块Survivor区的空间不够大怎么办?这个时候,就需要当Survivor区不够用的时候,暂时借持久代的内存用一下。此算法适用于新生代。
标记-整理(或叫压缩)算法(Mark-Compact)
o和标记-清楚算法前半段一样,只是在标记了不需要进行回收的对象后,将标记过的对象移动到一起,使得内存连续,这样,只要将标记边界以外的内存清理就行了。此算法适用于持久代。
4.String
String 不是基本数据类型 String类是final类型的所以不可变,不能修改和继承,
String s = “abc” s = s +”def” 此时的s为新创建的s=”abcdef” 放在字符串常量缓冲区
String一经创建,它所对应的字符序列就无法更改
我们一旦创建了一个string,在堆内存中为他分配了一块连续的内存空间,我们将不能以任何方式对这个string进行修改使之变长、变短、改变格式。所有对这个string进行各项操作(比如调用ToUpper获得大写格式的string)而返回的string,实际上另一个重新创建的string,其本身并不会产生任何变化。
它首先保证了对于一个既定string的任意操作不会造成对其的改变,同时还意味着我们不用考虑操作string时候出现的线程同步的问题
StringBuffer和StringBuilder类都表示内容可以被修改的字符串,
StringBuilder是线程不安全的,运行效率高
如果一个字符串变量是在方法里面定义,这种情况只可能有一个线程访问它,不存在不安全的因素了,则用StringBuilder。
如果要在类里面定义成员变量,并且这个类的实例对象会在多线程环境下使用,那么最好用StringBuffe
5.集合 集合的扩容 HashMap 1.7 1.8 ConcurrentHashMap的特点
List:
可以允许重复的对象
可以插入多个null元素
有序容器保持了每个元素的插入顺序, 输出的顺序就是输入的顺序(先进先出)
常用的实现类有ArrayList,LinkedList和Vector ArrayList提供使用索引,而Linkedlist则对于经常需要从List中添加或删除元素的场合更为合适
Set:
不允许重复
无序容器 无法保证每个元素的储存顺序
只允许一个null元素
Set接口最流行的寄个实现类是HashSet,LinkedHashSet以及TreeSet.
最流行的是基于HashMap实现HashSet;
TreeSet还是先了SortedSet接口,因此TreeSet是一个根据其compare(和compare To() 定义进行排序的有序容器
Map
Map不是collection的子接口或者实现类 Map是一个接口
Map的每个Entry都持有两个对象,也就是一个键一个值,Map可能会持有相同的值对象但键对象必须是唯一的
TreeMap 也通过Comparator 或者Comparable维护了一个排序顺序
Map里你可以拥有随意个null值但最多只能有一个null键
Map接口最流行的几个实现类是HashMap, LinkedHashMap ,hashtable和TreeMap
集合的扩容
ArrayList、Vector默认初始容量为10
Vector 线程安全 但是速度慢
底层数据结构是数组结构
加载因子为1 即当元素个数超过容量长度时 进行扩容(原容量2)
ArrayList: 线程不安全查询速度快
底层数据结构是数组结构
扩容增量:原容量的0.5倍+1 如ArrayList 初始为1 扩容之后为16
Set 无序 不可重复
HashSet: 线程不安全 存取速度快
底层实现是HashMap(保存数据),实现Set接口
默认初始容量为16 当元素个数超过容量的0.75倍时 进行扩容 (原始容量2)
Map是一个双列集合
HashMap: 默认初始容量为16(为什么是16 16是2的4次方 可以提高查询效率 另外 32=16 <<1)
当元素个数超过容量长度的0.75倍时 进行扩容 (原容量*2)
ConcurrentHashMap的特点
1.Hashtable
上锁非常粗暴,是对整个数组进行上锁,所有的并发操作就都变成串行操作
2.ConcurrentHashMap 1.7
使用了分段锁,不同的分段的线程可以并发,但是同一段中的线程只能串行,并且要先经过Segement然后再找到链表,要两次hash
3.ConcurrentHashMap 1.8
取消了分段锁,使用CAS自旋锁(乐观锁)来对数组中的每个元素进行上锁,降低了锁的粒度,提供了并发能力。
6.多线程 线程的创建 线程池 线程安全问题
并行与并发:
o并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
o并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力
多线程
多线程就要提到程序,进程,线程之间的关系
一个程序可以有多个进程 比如视频播放器程序可以有一个播放视频的进程,还可以在播放视频 的同时进行视频下载的进程
而进程中又可以调用多个线程
进程是系统质软分配调用的独立单位
如果程序只有一条执行路径就是单线程, 程序有多条路径 就是多线程
线程创建的方式?
Tread 唯一的线程类
方式一:
继承Thread类创建线程
定义Thread类的子类 并重写该类的run()方法 该方法的方法体就是线程需要完成的任务run方法也成为线程的执行体
创建Thread子类的实例,也就是创建了线程对象
启动线程, 即调用线程的start(方法)
方式二:
实现Runnable接口创建线程
定义Runnable接口的实现类 一样要重写run方法, 这个run方法和Thread中的run方法一样是线程的执行体
创建Runnable实现类的实例 并用这个实例作为Thread的target 来城建Thread对象 这个Thread对象才是真正的线程对象
通过调用线程对象的start方法来启动线程
第二种好点, 因为实现接口的方式比继承类的方式更加灵活, 也能减少程序间耦合度.
线程有什么用?
- 发挥多核CPU的优势 多段逻辑同时工作,性能更高
- 防止阻塞 多条线程同时运行 哪怕一条线程的代码执行读取数据阻塞, 也不会影响其他任务的执行
- 便于建模 多个任务分别简历程序模型 通过多线程分别运行这几个任务 就比较简单了
start()方法和run()方法的区别
只有调用了start方法才会表现出多线程的特性,不同线程的run方法里面的代码交替执行. 如果只是调用run方法那么代码还是同步执行的, 必须等待一个线程的run方法里面的代码全部执行完毕后 , 另外一个线程才可以执行其run方法里面的代码
Runnable接口和Callable接口的区别
Runnable接口中的run方法的返回值是void, 他做的事情只是纯粹的去执行run方法中的代码而已;Callable接口中的call()方法是有返回值的, 是个泛型 和Future,FutureTask配合可以用来获取异步执行的结果
这一特性 因为多线程充满未知性, 我们只能等待这条多线程的任务执行完毕 而Callable+ Future/FutureTask 却可以获取多线程运行的结果 , 可以再等待时间太长没获取到需要的数据的情况下取消该线程的任务, 非常有用
CyclicBarrier和CountDownLatch的区别
都在java.util.concurrent下 , 都可以用来表示代码运行到某个点上 区别在于
CyclicBarrier的某个线程运行到某个点上之后, 该线程即停止运行, 直到所有的线程都到达了这个点, 所有线程才重新运行; CountDownLatch则不是. 某县城运行到某个点上之后, 只是给某个数值-1而已, 该线程继续运行
CyclicBarrier只能唤起一个任务 CountDownLatch可以唤起多个任务
CyclicBarrier可重用, CountDownLatch不可重用, 计数值为0该CountDownLatch就不可以再用了
volatile关键字的作用
什么是线程安全
如果代码在多线程下执行和在单线程下执行永远都能获得一样的结果, 那么你的代码就是线程安全的
线程安全的级别:
1.不可变
像String, Integer, Long这些都是final类型的类, 任何一个线程都改变不了他们的值, 要改变除非新创建一个因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用
2.绝对线程安全
不管运行时环境如何, 调用者都不需要额外的同步措施, 要做到这一点通常需要付出额外的代价java中标注自己是线程安全的类, 大多数都不是线程安全的 不过绝对线程安全的类java也有, CopyOnWriteArrayList, CopyOnWriteArraySet
3.相对线程安全
相对线程安全就是通常意义上的线程安全 Vector这种 add remove 方法都是源自操作不会被打断 但也仅限于此
4.线程非安全
线程安全问题
多条线程操作同一数据的过程中,破坏了数据的原子性 线程安全产生的原因
- synchronized reentrantLock 的 区别 CAS 乐观锁机制 volatile关键字
8.bio nio aio( dubbo实现==》nio==》netty框架)
9.spring bean的创建原码 aop的原码
SpringMVC设计思想
M(Model模型) V(View视图) C(Controller控制)
M: 核心的数据层, 也就是程序最需要操作的数据或信息
V: 直接面向最终用户的的视图层, 他是提供给用户的操作界面 是程序的外壳
C: 负责根据用户从视图层输入的指令, 选取数据层的数据, 然后对其进行相应的操作, 产生最终结果
SpringMVC的运行流程
SpringMVC将所有的请求都提交给dispatcerServlet,它会委托应用系统的其他模块负责对请求进行真正的处理工作, dispatcherServlet查询一个或多个HandlerMapping, 找到处理请求的Controller, DispatcherServlet请求提交到目标Controller, Controller进行业务逻辑处理后, 会返回一个ModelAndView, Dispatcher查询一个或多个ViewResolver视图解析器, 找到ModelAndView对象指定的视图对象, 视图对象负责返回给客户端
说说Spring AOP
面向切面编程, 我们的应用中, 经常做一些与核心业务无关的事情, 通过AOP技术就可以在不修改核心业务的情况下完成该需求
AOP 的实现原理
SpringAOP有两种实现方式,JDK动态代理和CGLB动态代理, JDK动态代理通过反射来接收被代理的类, 并且要求被代理的类必须实现一个接口, 如果没有实现接口, 那么就会选择使用CGLB来动态代理目标类, CGLB是通过继承的方式做的动态代理 因此如果某个类被标记为final 那么他无法使用CGLB做动态代理
Spring Bean的生命周期
1.实例化 Instantiation
2.属性赋值 Populate
3.初始化 Initialization
4.销毁 Destruction
在一个bean实例被初始化时, 需要执行一系列的初始化操作以达到可用的状态, 同样的, 当一个bean不在被调用时需要进行相关的操作,并从bean容器中删除
SpringBean factory负责管理在spring容器中被创建的bean的生命周期Bean的生命周期有两组回调方法组成
初始化之后调用的回调方法
销毁前调用的回调方法
Spring框架提供了一下四种方法来管理bean的生命周期时间
InitializingBean和DisposableBean回调接口
针对特殊行为的其他Aware接口
Bean配置文件中的Custom init()方法和destroy()方法
@PostConstruct和@PreDestory注解方式
Spring IOC如何实现的
BeanFactory接口提供了一个先进的配置机制, 是的任何类型的对象的配置成为可能 ApplicationContext 接口对BeanFactory进行了拓展, 在BeanFactory的基础上添加了其他功能, 比如 与Spring的AOP耿荣哟继承, 也提供了处理message resource的机制, 事件传播以及应用层的特别配置, 比如针对Web应用的WebApplicationContext
Spring BeanFactory是Spring IOC容器的具体实现 , 用来包装和管理各种bean , BeanFactory接口是SpringIOC容器的核心接口
@Component 指定把一个对象加入IOC容器
@Repository 作用同@Component; 在持久层使用
@Service 作用同@Component; 在业务逻辑层使用
@Controller 作用同@Component; 在控制层使用
@Resource 属性注入
DI 依赖注入
DI是IOC的具体实现
什么是Spring IOC
Spring IOC指的是控制反转,有反转就有正转,自己创建对象正转,那么反转就是把对象的创建交给SpringIOC容器
所有的类都会在Spring容器中登记, 你告诉spring你需要什么, 然后spring会在系统运行到适当的时候吧你要的东西主动给你, 同时把你交给到 需要你的地方, 所有的类的创建,销毁都由spring来控制, 那么控制对象生命周期的不在是引用他的对象, 而是spring容器, 所以对于具体的对象而言, 以前是它控制其他对象, 现在是所有对象都被spring容器控制所以叫控制反转
10.设计模式 单例模式 工厂模式 模板模式 策略模式
单例模式:
只有一个实例
实现方式:
将被实现的类的构造方法设计成private
添加此类引用的静态成员变量, 并为其实例化
在被实现类中提供公共的CreateInstance函数
工厂模式
建立一个工厂类, 对实现了统一结构的一些类进行实例的创建, 简单工厂模式的实质是由一个工厂类根据传入的参数动态决定应该创建哪一个产品类的实例
实现方式:
抽象产品类也可以是接口
多个具体的产品类
工厂类(包括创建a的实例的方法)
模板方法模式
定义一个算法结构,而将一些不走盐池到子类实现
实现方式:
父类模板类(规定要执行的方法和顺序, 只关心方法及顺序, 不关心方法的实现)
子类实现类(实现a规定要执行的方法, 只关心方法实现, 不关心调用顺序)
策略模式
定义一系列算法, 把他们封装起来, 并且他的文法的一种表示, 并定义一个解释器
实现方式:
提供公共接口或抽象类, 定义需要使用的策略方法.
多个实现的策略抽象类的实现类
环境类, 对多个实现类的封装, 提供接口类型的成员量. 可以再客户端中切换
客户端调用环境类进行不同策略的切换
11.分布式相关
你怎么理解RPC框架
什么是RPC
RPC是指远程过程调用网络之间的数据调用
RPC的实现原理
首先需要有处理网络连接通讯的模块, 负责连接建立, 管理和消息的传输, 然后是编解码的模块, 因为网络通讯都是传输的字节码, 需要我们使用的对象序列化和反序列化, 剩下的 就是客户端和服务器端的部分, 服务器端暴露要开放的服务接口, 客户调用服务接口的一个代理实现, 这个代理实现负责收集数据, 编码并传输给服务器然后等待结果返回
dubbo 的九层模型dubbo 的通信架构dubbo的负载均衡策略
什么是dubbo
Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架
为什么要用Dubbo
内部使用了zookeeper保证了高性能高可用性, 使用 Dubbo 可以将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,可用于提高业务复用灵活扩展,使前端应用能更快速的响应多变的市场需求。
zookeeper 崩溃恢复(zk的选举策略) 数据同步 ===》 cap和base理论 数据的一致性 可用性 分区容错性
Zookeeper的崩溃恢复策略
当zookeeper 中的leader挂掉时, 进入崩溃恢复机制, 开始选举阶段, 根据事务ID的大小进行比较, 选举出事务ID最大的作为新的准leader, 因为网络原因可能存在多个准leader的情况, 然后进入确认阶段, 得出真正的leader, 新的leader事务Id最大, 也就是说它的数据是最新的, 那么就需要他同步给其他的所有的从节点
Zookeeper如何实现的数据同步
当客户端想从节点写数据时, 从节点把数据发送给leader, 并发起写数据的请求,leader把数据广播给所有的从节点, 所有的从节点把数据写到日子中, 并返回一个成功的back给leader, 如果主节点收到半数以上的back返回成功给客户端, 并将日志中的数据写到节点内存中
Zookeeper的应用场景
分布式锁, 利用Zookeeper的临时顺序节点, 可以轻松实现分布式锁
服务注册和发现, 利用Znode和Watcher 可以实现分布式服务的注册和发现, 例:RPC框架
共享配置和状态信息, 依靠Zookeeper同步节点信息,实现高可用
CAP和BASE理论
CAP: 一个分布式系统只能同时满足一致性(Consistency), 可用性(Availability)和分区容错性(Partition tolerance)中的两项
一致性(Consistency): 更新操作成功并返回客户端完成后, 所有节点在同一时间的数据完全一致
可用性(Availability): 即服务一致可用, 而且是正常响应时间
分区容错性(Partition tolerance) 分布式系统在遇到某节点或网络分区故障的时候, 仍能够对外提供满足一致性和可用性的服务
BASE: 应用可以采用合适的方式达到最终一致性
基本可用(Basically Available): 在分布式系统出现故障时, 允许损失部分可用性, 即保证核心可用
软状态(Soft State): 指允许系统存在中间状态, 而该中间状态不会影响系统整体可用性, 分布式储存中一般一份数据至少会有三个副本, 允许不同节点间副本同步的延时就是软状态的提现.
最终一致性(Eventual Consistency): 指系统中的所有数据经过一定时间后, 最终能够达到一致的状态, 弱一致性和强一致性相反, 最终一致性是弱一致性的一种特殊情况.
ACID和BASE的区别和联系
ACID是传统数据库常用的设计理念, 追求强一致性模型, BASE支持的是大型分布式系统, 提出通过牺牲强一致性获得高可用性
ACID和BASE代表了两种截然相反的设计. 在分布式场景中. 系统组件对一致性的要求是不同的
nginx 的三大功能, nginx的负载均衡策略
HTTP服务器: Nginx是一个HTTP服务器,可以将服务器上的静态文件(如HTML、图片)通过HTTP协议展现给客户端Nginx可以作为静态页面的服务器,
虚拟主机: 通过子域名实现多级域名, 只需要在dns服务器上注册一个域名.通过Nginx实现了多个域名
反向代理: 代理服务器, 通过反向代理可以实现服务器负载均衡的功能,