Question
应用层:各种应用软件,包括web应用
表示层:数据格式标识,基本压缩加密功能
会话层:控制应用程序之间的会话能力,如不同软件数据分发给不同软件
传输层:端到端传输数据的基本功能,如TCP,udp
网络层:定义IP地址,定义路由功能,如不同设备的数据转发
数据链路层:定义数据的基本格式,如何传输,如何标志,如网卡Mac地址
物理层:底层数据传输,如网线、网卡标准
网络访问层
网络层
传输层
应用层
多线程对锁资源的竞争会引起上下文切换,还有锁竞争导致的线程阻塞越多,
上下文切换就越频繁,系统的性能开销也就越大。
由此可见,在多线程编程中,锁其实不是开销的根源,竞争锁才是!
减少锁的持有时间
锁的持有越长,就意味着越多的线程在等待竞争资源的释放,
如果是synchronized同步锁资源,就不仅仅是带来线程间的上下文切换,
还有可能增加进程间的上下文切换
手段:synchronized包裹尽量少的代码,只将有线程竞争的代码包裹起来。
降低锁的粒度
同步锁可以保证对象的原子性,我们可以将锁的颗粒度拆分的更小一些,
以此避免所有线程对一个锁资源的竞争过于激烈,手段:
1、锁分离:读写锁,分读锁和写锁,可以共享读,但不可以共享写
2、锁分段:我们在使用锁来保证集合或者大对象原子性时,可以考虑将所对象进一步分解,例如concurrentHashMap就用了分段锁
非阻塞乐观锁替代竞争锁
valatile关键字可以保证可见性和有序性,Volatile的读写操作不会导致上下文切换,
因此开销较少,但是Volatile不能保证操作的原子性,因为没有锁的排他性。
而cas是一个原子性的操作。
程序计数器:也称为PC寄存器,它保存的是程序当前执行的指令的地址,当CPU需要执行指令时,
需要从程序计数器中得到当前需要执行的指令所在的存储单元的地址,
然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动+1,
或者根据转移指针得到下一条指令的地址,如此循环直到执行完所有的指令。
虚拟机栈:
栈帧:
局部变量表:包括在方法中声明的非静态变量以及函数形参,对于基本类型的变量,则直接存储值,对于引用类型的变量,则指向对象的引用,局部变量的大小在编译期间就确定了,在程序执行期间局部变量值大小不会改变。
操作数栈:程序中的所有计算过程都是借助于操作数栈来完成的
指向运行时常量池的引用:因为在方法执行过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。
方法返回地址:当一个方法执行完毕之后,要返回之前调用它的地方,因此栈帧必须保存一个方法返回地址
Java栈中存放的是一个个栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量、操作数栈、指向当前方法所属的类的运行时常量池的引用,方法的返回地址和一些额外的附加信息,当线程执行一个方法时,就会随之创建一个对应的栈帧,因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。
本地方法栈:和虚拟机栈作用和原理非常相似,区别只不过是虚拟机栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法服务的。
栈存储什么?
引用
方法
8种基本数据类型
方法区:在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表现形式,
在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。
在运行时新的常量也会放入运行时常量池中。
方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程所共享的,在方法区中,
存储了每一个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等
堆:是用来存储对象本身以及数组,这部分空间也是Java垃圾收集器管理的主要区域,另外,堆是被所有线程所共享的。在JVM中只有一个堆。
每个线程独有:(程序计数器, 虚拟机栈, 本地方法栈)
所有线程共享:(方法区, 堆)
悲观锁
乐观锁
自旋锁
可重入锁(递归锁)
可中断锁
公平锁
非公平锁
偏向锁
轻量级锁
重量级锁
在1.6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在1.6里锁一共有4种状态,无锁状态、偏向锁状态、轻量级锁状态和重量级状态。它会随着竞争情况逐渐升级。只能升级不能降级,这种行为是为了提高获得锁和释放锁的效率。
(偏向锁, 轻量级锁, 重量级锁)
1、ibatis不是一个完全的orm框架,因为ibatis需要程序员自己编写SQL语句,
这样我么可以自行的编写高效的SQL语句。但是ibatis无法做到数据库的无关性,
如果需要实现多种数据库的软件支持,则需要自定义多套SQL映射文件,工作量大。
2、Hibernate 对象/关系映射能力强,数据库无关性好,
因为Hibernate自己生成SQL语句,但是我们无法控制语句,这就无法写特定的高效率的SQL。
1、spring是一个开源的业务框架,分模块,一站式框架,他能够整合各种其他主流框架。
2、spring的实质就是一个实现了工厂模式的工厂类。
3、spring的核心是:IOC/DI
IOC:控制反转,将对象的创建全交给spring去管理,然后spring通过依赖注入的方式,注入给调用者。这样做的好处是,让bean与bean之间解耦,而不是以硬编码的方式耦合在一起。
4、spring的核心:AOP
a、AOP:面向切面,可以在不修改源代码的前提下,对程序进行增强,例如假如日志,假如权限判断,加入异常处理等
b、AOP底层使用的是代理技术,分为JDK代理(面向接口)和CGLIB动态代理。
1、用户发送请求至前端控制器DispatcherServlet。
2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、处理器映射器根据请求URL找到具体的处理器,生成处理器对象及处理器拦截器一并返回给DispatcherServlet.
4、DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5、HandlerAdapter执行处理器(handler,也叫后端控制器)
6、Controller执行完成之后返回ModelAndView
7、HandlerAdapter将handle执行结果ModelAndView返回给DisPatcherServlet
8、DispatcherServlet将ModelAndView传给ViewReslovers视图解析器
9、ViewReslover解析后返回具体的View对象
10、DispatcherServlet对View进行渲染视图(即将模型数据填充到视图中)
11、DispatcherServlet响应用户
1、实例化一个Bean----也就是我们常说的new
2、按照spring上下文对实例化的Bean进行配置---也就是IOC注入
3、Spring调用Bean实现BeanNameAware 、BeanFactoryAware的方法
4、spring对Bean调用的方法进行增强
5、当Bean不再需要时,销毁
write once run anywhere
一次编写
将Java源代码编译成.class文件,.class文件就是可以到处运行的文件。
然后Java字节码文件会被转换成目标机器代码,这是由JVM来执行的,即Java的二次编译。
================
第一次编译:源代码---->>>.class文件
第二次编译:.class文件----->>>机器代码
到处运行
此处的关键和前提是JVM,在Linux和window平台都有对应的JDK,安装好JDK之后就有对应的Java运行环境,即JRE和JVM,在第二次编译中,JVM起到关键作用,在可以运行Java虚拟机的地方都含有一个JVM操作系统,从而使Java提供了各种平台上的虚拟机制。Java通过字节码和Java虚拟机这种跨平台的抽象,屏蔽了操作系统和硬件之间的细节。
何为解释?何为编译?
编译型语言是在编译过程中生成目标平台的指令。如c/c++,pascal/object
解释型语言是在运行的过程中生成目标平台的指令。如javascript,vb,python,ruby
java是否是解释型语言?
首先,Java先经过javac第一次编译成字节码,即.class文件,在运行的时候通过JVM内嵌的解释器将字节码转换成机器码。
现在常见的JVM都提供了jit(just in time)编译器,即动态编译器,具有缓存功能,会将编译过的代码放在缓冲区,能过在运行时将热点代码编译成机器代码,这部分热点代码就属于编译执行,而不是解释执行。
================
JVM不同的启动方式也会影响到第二次代码转换是编译还是解释。
-Xint告诉JVM只进行解释执行,不进行解释,但这样会屏蔽掉jit的优势。
-Xcomp:会让虚拟机关闭解释器,不要进行解释执行,或者叫做最大优化级别,但是不一定是最高效,这种模式会让JVM启动变慢很多,也会影响到jit编译器优化。
AOT:在运行前直接将字节码编译成机器码,避免jit预热等的开销。
Exception
算术异常类:ArithmeticExecption
空指针异常类:NullPointerException
类型强制转换异常:ClassCastException
数组负下标异常:NegativeArrayException
数组下标越界异常:ArrayIndexOutOfBoundsException
违背安全原则异常:SecturityException
文件已结束异常:EOFException
文件未找到异常:FileNotFoundException
字符串转换为数字异常:NumberFormatException
ClassNotFoundException(指定类不存在)
error
OutOfMemoryError
stackoverflowerror
Java.lang.OutOfMemeoryError:GC overhead limit exceeded
程序在垃圾回收上花费了98%的时间,却收集不回2%的空间,通常这样的异常伴随着CPU的冲高
Java.lang.OutOfMemeoryError:Direct buffer memory
Java.lang.OutOfMemeoryError:unable to create new native thread
服务器级别参数调优
Java.lang.OutOfMemeoryError:Metaspace
使用Java -XX:+PrintFlagsInitial命令查看本机的初始化参数,
-XX:MetaspaceSize为21810376B(约20M)
一个是error另一个是exception,我们可以从异常中恢复程序,但不应该尝试从错误中恢复程序
Java支持使用Class.forName()方法动态的加载类,任意一个类的类名如果被作为参数传递给这个方法都将导致该类被加载到JVM内存中。
如果这个类在路径中没有被找到,那么此时就会在运行时抛出classNotfoundException
如若这个类在编译的时候是存在的,在运行的时候却找不到了,那么此时就会抛出NoClassDefFoundError
造成此的原因可能是打包过程中漏掉了部分类,或者jar包损坏或被篡改。
用来修饰一个引用
如果引用为基本数据类型,则该引用为常量,该值无法修改;
如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。
如果引用时类的成员变量,则必须当场赋值,否则编译会报错。
( 如果引用为基本数据类型,则该引用为常量,该值无法修改;, 如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。, 如果引用时类的成员变量,则必须当场赋值,否则编译会报错。)
用来修饰一个方法
当使用final修饰方法时,这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承
(当使用final修饰方法时,这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承)
用来修饰类
该类成为最终类,无法被继承。简称为“断子绝孙类”
(该类成为最终类,无法被继承。简称为“断子绝孙类”)
代理模式
代理的定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接控制原对象,而是通过代理对象间接的控制原对象。
代理模式:通过代理静默地解决一些与业务无关的问题,比如安全、事务、日志、资源关闭……让应用开发者只关心他的业务。
静态代理
事先写好代理类,可以手工编写,也可以工具生成,缺点是每个业务都要有一个代理类,非常不灵活。
动态代理
运行时自动生成代理对象,缺点是生成代理对象和调用代理方法都要额外花费时间。
JDK动态代理:
基于Java反射机制实现,必须要实现了业务类才能用这种办法生成代理对象,新版本也可以基于ASM机制
CGLib动态代理:
基于AMS机制实现,通过生成业务类的子类作为代理类。
JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final
(JDK动态代理, CGLib动态代理)
通过反射我们可以不在编译时知道某个对象的类型,而在运行时通过提供的完整的
报名+类名.class 得到对象的定义,方法,属性
系统负载:单位时间内正在运行或等待的进程或线程数量,代表系统的繁忙程度
CPU利用率:代表单位时间内一个线程或进程实时占用CPU的百分比
TPS:单位时间内处理事务的数量
QPS:单位时间内请求的数量
如果要阻塞或者唤醒一个线程就需要操作系统介入,需要用户态与核心态之间的切换,这种切换会消耗系统大量的资源,因为用户态和核心态都有各自专用的空间、专用的寄存器等。用户态切换到内核态需要传许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器、变量等,以便内核态调用结束后切换到用户态继续工作。
1、如果线程状态切换是一个高频操作时,这将消耗很多CPU处理时间。
2、如果对于那些需要同步的简单代码块,获取锁挂起操作消耗的时间比用户代码执行的时间还长,这种策略显然是很糟糕的。