Java 学习记录
Spring工作原理
内部最核心的就是IOC了,
1、动态注入,让一个对象的创建不用new了,可以自动的生产,这其实就是利用java里的反射(反射其实就是在运行时动态的去创建、调用对象,Spring就是在运行时,跟xml Spring的配置文件来动态的创建对象,和调用对象里的方法的 )。
2、Spring还有一个核心就是AOP这个就是面向切面编程,可以为某一类对象 进行监督和控制(也就是在调用这类对象的具体方法的前后去调用你指定的模块)从而达到对一个模块扩充的功能。这些都是通过配置类达到的。
``` 要记住:Spring是一个容器,凡是在容器里的对象才会有Spring所提供的这些服务和功能。
Spring里用的最经典的一个设计模式就是:模板方法模式。(这里我都不介绍了,是一个很常用的设计模式)Spring里的配置是很多的,很难都记住,但是Spring里的精华也无非就是以上的两点,把以上两点跟理解了 也就基本上掌握了Spring.
Spring AOP与IOC
一、 IoC(Inversion of control): 控制反转
1、IoC:
概念:控制权由对象本身转向容器;由容器根据配置文件去创建实例并创建各个实例之间的依赖关系
核心:bean工厂;在Spring中,bean工厂创建的各个实例称作bean
二、AOP(Aspect-Oriented Programming): 面向方面编程
1、 代理的两种方式:
静态代理:
针对每个具体类分别编写代理类;
针对一个接口编写一个代理类;
动态代理:
针对一个方面编写一个InvocationHandler,然后借用JDK反射包中的Proxy类为各种接口动态生成相应的代理类
Spring 优缺点
它是一个开源的项目,而且目前非常活跃;它基于IoC(Inversion of Control,反向控制)和AOP的构架多层j2ee系统的框架,但它不强迫你必须在每一层 中必须使用Spring,因为它模块化的很好,允许你根据自己的需要选择使用它的某一个模块;它实现了很优雅的MVC,对不同的数据访问技术提供了统一的 接口,采用IoC使得可以很容易的实现bean的装配,提供了简洁的AOP并据此实现Transcation Managment,等等优点
- Spring能有效地组织你的中间层对象,不管你是否选择使用了EJB。如果你仅仅使用了Struts或其他为J2EE的 API特制的framework,Spring致力于解决剩下的问题。
- Spring能消除在许多工程中常见的对Singleton的过多使用。根据我的经验,这是一个很大的问题,它降低了系统的可测试性和面向对象的程度。
- 通过一种在不同应用程序和项目间一致的方法来处理配置文件,Spring能消除各种各样自定义格式的属性文件的需要。曾经对某个类要寻找的是哪个魔法般的属性项或系统属性感到不解,为此不得不去读Javadoc甚至源编码?有了Spring,你仅仅需要看看类的JavaBean属性。Inversion of Control的使用(在下面讨论)帮助完成了这种简化。
- 通过把对接口编程而不是对类编程的代价几乎减少到没有,Spring能够促进养成好的编程习惯。
- Spring被设计为让使用它创建的应用尽可能少的依赖于他的APIs。在Spring应用中的大多数业务对象没有依赖于Spring。
- 使用Spring构建的应用程序易于单元测试。
- Spring能使EJB的使用成为一个实现选择,而不是应用架构的必然选择。你能选择用POJOs或local EJBs来实现业务接口,却不会影响调用代码。
- Spring帮助你解决许多问题而无需使用EJB。Spring能提供一种EJB的替换物,它们适用于许多web应用。例如,Spring能使用AOP提供声明性事务管理而不通过EJB容器,如果你仅仅需要与单个数据库打交道,甚至不需要一个JTA实现。
- Spring为数据存取提供了一个一致的框架,不论是使用的是JDBC还是O/R mapping产品(如Hibernate)。
Spring确实使你能通过最简单可行的解决办法来解决你的问题。而这是有有很大价值的。
缺点:jsp中要写很多代码、控制器过于灵活,缺少一个公用控制器
Spring MVC 五大组件
1、dispatcherServlet 前端控制器
2、HandleMapping 映射处理器
3、Controller 处理器
4、ModelAndView模型和试图
5、ViewResolver 试图解析器
提交到DispatcherServlet 由DispatcherServlet控制器查询一个或多个HandleMapping,找到处理请求的Controller DispatcherServlet 将请求提交到Controller Controller 调用业务逻辑处理后,返回ModelAndView DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图,视图负责将结果显示到客户
Spring 原理
- 内部最核心的就是IOC了,动态注入,让一个对象的创建不用New了,可以自动的生产,这其实就是利用Java里的反射,反射其实就是在运行时动态的去创建,调用对象,Spring 就是在运行时,跟xml Spring的配置文件来动态的创建对象,和调用对象里的方法。
- Spring 还有一个核心就是AOP,这个就是面向切面编程,可以为某一类对象进行监督和控制(也就是在调用这类对象的具体方法的前后去调用你指定的模块)从而达到对一个模块扩充的功能,这些都是通过配置类达到的。
- Spring 的目的: 就是让对象与对象(模块与模块)之间的关系没有通过代码关联,都是通过配置类声明管理的,(Spring根据这些配置,内部通过反射动态的组装对象)
要记住:Spring 是一个容器,凡是在容器里的对象才有Spring所提供的这些服务和功能。
Spring 里用的最经典的这个设计模式就是:模块方法模式。
SpringAop和IOC
一、IOC(inversion of Control)控制反转
概念:控制权由对象本身转向容器,有容器根据配置文件去创建实例并创建各个实例之间的依赖关系
核心:bean工厂,在Spring中,bean工厂创建的各个实例称作bean
二、AOP(Aspect-Oriented Programming):面向切面编程
1、代理的两种方式:
静态代理:
针对每个具体类分别编写代理类;
针对一个接口编写一个代理类;
动态代理:
针对一个方面编写一个InvocationHandler,然后借用JDK反射包中的Proxy类为各种接口动态生成相应的的代理类
什么的反射
什么是类的反射
通过类的声明可以得到类的父类,实现的接口,内部类,构造函数、方法,属性并可以根据构造实例化一个对象,唤起一个方法,取属性值,改属性值,
三、 Spring的三种注入方式是什么
Setter、interface、constructor
四、Spring 的核心接口及核类配置文件是什么
FactoryBean:工厂bean主要实现ioc/di
ApplicationContext ac = new FileXmlApplicationContext(“applicationContext.xml”);
五、Spring框架的7个模块
Spring Aop (面对切面编程),Spring ORM ,Spring Web, Spring DAO, Spring Context,Spring Web MVC, Spring Core
对jvm的理解,
jvm就是java虚拟机,jvm的内部体系结构分为三部分,分别是:类加载器(ClassLoader)子系统,运行区、执行引擎。
每一个Java虚拟机都由一个类加载器子系统,负责加载程序中的类型(类和接口),并赋予唯一的名字,每一个Java虚拟机都有一个执行引擎,负责执行被加载类中包含的指令。JVM的两种类装载起包括:启动类装载和用户自定义类装载器启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。```
运行时数据区:主要包括:方法区,堆,Java栈,PC寄存器,本地方法栈
JVM 内存模块划分
- 方法区
被所有方法线程共享的一块内存区域。用于存储已经被虚拟机加载的类信息,常量,静态变量等,这个区域的内存回收目标主要针对常量池的回收和堆类型的卸载。
- 虚拟机栈
java 虚拟栈也是私有的,每个方法在执行的时候也会创建一个栈帧,存储了局部变量,操作数, 动态链接,方法返回地址。每个方法从调用到执行完毕,对应一个帧在虚拟机栈中的入栈和出栈。
所以通常所说的的栈,一般是指在虚拟机栈中的局部变量部分。局部变量所需要的内存在编译期间完成分配,如果线程请求的栈深度大于虚拟机所允许的深度,则StackOverFlowError。
如果虚拟机长可以动态扩展,扩展到无法申请足够的内存,则OutOfMemoryError。
- 本地方法栈(线程私有)
和虚拟机栈类似,主要虚拟机使用到的Native方法服务。也会抛出StackOverFlowError和OutOfMemoryError
- 堆
被所有线程共享的一块内存区域,在虚拟机启动的时候创建,用于存放对象实例。对可以按照可扩展来实现(通过-Xmx和Xms来控制)当对中没有内存克分配给实例,也无法在扩展时,则抛出OutOfMemoryError异常。
- 程序计数器(线程私有)
程序计数器是当前线程锁执行行字节码的行号治时期,每条线程都有一个独立的线程计数器,这类内存也称为”线程私有“的内存。正在执行java的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是Native方法,则为空。
GC原理,性能调优
通过IDEA 运行JAVA代码,java代码执行过程
- 编译源代码
- 编译java文件生成字节码文件
- JVM 中的类加载器,加载字节码文件
- JVM 中的执行引擎找到入口方法main(),执行其中的方法
JVM 垃圾回收
JVM回收原理,把对象分为年青代、年老代,持久代,对不同生命周期的对象使用不同的算法。(基于对对象生命周期分析)
通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态生气分配的,所以以上对象的年青代和年老代都是指的JVM的Heap空间, 而持久代则是之前提到的MethodArea,不属于Head。
- GC 的基本原理:讲内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代,旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停
1)对新生代的对象的收集称为minor GC;
2)对旧生代的对象的收集称为Full GC;
3)程序中主动调用System.gc()强制执行的GC为Full GC
不同的对象引用类型,GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:
- 强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被销毁回收)
- 软引用:软引用是Java中提供的一种比较合适与缓存场景的应用(只有在内存不够的用的情况下才会被回收)
- 虚引用:由于虚引用只是用来得知对象是否被GC
JVM的对象分配规则
对象优先分配在Eden区【使用空间】,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC【垃圾回收】。
大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC(年轻代收集)那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,直到达到阀值对象进入老年区。
动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。
简要概括如下
- 对象先在Eden区,Eden区空间不够时进行新生代GC
- 大对象和长期存活的对象进入老年代
- JVM为每个对象设置了计数器,经过1次新生代GC则进入幸存者区,达到年龄阈值则进入老年区
- 幸存者区中年龄一致的对象所占内存大小,大于幸存者区空间一半时,则大于等于此年龄的对象全部进入老年代
- 老年代GC通常伴随着一次新生代GC,但不绝对
YOUNG(年轻代)
年轻代分为三个区,一个Eden区,两个Survivor区。大部分对象在Eden去中生成。当Eden区满时,还存活的对象将被复制到Survivor区,当这个Survivor区也满的时候,从第一个Survivor的两个区复制过来的并且此时还存活的对象,将被复制到年老区(Survivor的两个区是对称的,没有先后关系,所以同一个区可能同时存在从Eden复制过来的对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过去的对象,并且Survivor区总有一个是空的)。
Tenured 年老代
年老代存放从年轻代存活的对象,一般来说年老代存放的都是生命周期较长的对象。
持久代Perm
用于存放静态文件,如今Java类,方法等,持久代对垃圾回收没有明显的影响,但是有些应用可能动态生成活调用一些class,列如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程新增的类。持久代大小通过-XX:MaxPermSize 进行设置。
一、什么是类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
- 启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库
- 扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
- 应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器
三、JVM加载class文件的原理
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:
如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
如果类中存在初始化语句,就依次执行这些初始化语句。
类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。
从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明:
Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
四、Java对象创建过程
(1)JVM遇到一条新建对象的指令时首先去检查这个指令的参数是否能在常量池中定义到一个类的符号引用。然后加载这个类(类加载过程在后边讲)
(2)为对象分配内存。一种办法“指针碰撞”、一种办法“空闲列表”,最终常用的办法“本地线程缓冲分配(TLAB)”
(3)将除对象头外的对象内存空间初始化为0
(4)对对象头进行必要设置
五、类的生命周期
类的生命周期包括这几个部分,加载、连接、初始化、使用和卸载,其中前三部是类的加载的过程,如下图:
加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
连接,连接又包含三块内容:验证、准备、初始化。
(1)验证,文件格式、元数据、字节码、符号引用验证;
(2)准备,为类的静态变量分配内存,并将其初始化为默认值;
(3)解析,把类中的符号引用转换为直接引用
初始化,为类的静态变量赋予正确的初始值
使用,new出对象程序中使用
卸载,执行垃圾回收
六、Java对象结构
Java对象由三个部分组成:
对象头:由两部分组成,第一部分存储对象自身的运行时数据,第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。
自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)。
PS:如果是数组对象,则对象头中还有一部分用来记录数组长度。
实例数据:用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)
对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)
ZooKeeper学习(一)了解ZooKeeper
一、什么是ZooKeeper
ZooKeeper主要服务于分布式系统,可以用ZooKeeper来做:统一配置管理、统一命名服务、分布式锁、集群管理。
使用分布式系统就无法避免对节点管理的问题(需要实时感知节点的状态、对节点进行统一管理等等),而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够通用解决这些问题的中间件就应运而生了。
二、ZooKeeper的数据结构
ZooKeeper和Redis一样,也是C/S结构(分成客户端和服务端)。
ZooKeeper的数据结构,跟Unix文件系统非常类似,可以看做是一颗树,每个节点叫做ZNode。每一个节点可以通过路径来标识,结构图如下:
图片1
如图所示,Znode分为两种类型:
短暂/临时(Ephemeral):当客户端和服务端断开连接后,所创建的Znode(节点)会自动删除
持久(Persistent):当客户端和服务端断开连接后,所创建的Znode(节点)不会删除
三、ZooKeeper的监听器
前面了解了,我们可以通过ZooKeeper去通用的实现很多功能,那么实现原理是怎么样的呢?ZooKeeper配合了监听器,才能够做那么多事的。
常见的监听场景有以下两项:
监听Znode节点的数据变化
监听子节点的增减变化
通过监听+Znode节点(持久/短暂[临时]),ZooKeeper就可以解决很多分布式系统的问题了。
统一配置管理
比如我们现在有三个系统A、B、C,他们有三份配置,分别是ASystem.yml、BSystem.yml、CSystem.yml,然后,这三份配置又非常类似,很多的配置项几乎都一样。
痛点:如果我们要改变其中一份配置项的信息,很可能其他两份都要改。并且,改变了配置项的信息很可能就要重启系统
于是,我们希望把ASystem.yml、BSystem.yml、CSystem.yml相同的配置项抽取出来成一份公用的配置common.yml,并且即便common.yml改了,也不需要系统A、B、C重启。
pic2
做法:我们可以将common.yml这份配置放在ZooKeeper的Znode节点中,系统A、B、C监听着这个Znode节点有无变更,如果变更了,及时响应。
代码参考:基于zookeeper实现统一配置管理
统一命名服务
统一命名服务的理解其实跟域名一样,是我们为这某一部分的资源给它取一个名字,别人通过这个名字就可以拿到对应的资源。
例如一个域名可能对应多个IP地址的资源信息,也可能部署多个服务器集群,那么别人访问的时候,不是直接通过IP去访问对应的主机,而是通过一个域名就可以了。
例如:https://www.cnblogs.com/riches/对应了4台服务器:
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.4
pic3
分布式锁
我们可以使用ZooKeeper来实现分布式锁,那是怎么做的呢??下面来看看:系统A、B、C都去访问/locks节点
pic4
访问的时候会创建带顺序号的临时/短暂节点,比如,系统A创建了id_000000节点,系统B创建了id_000002节点,系统C创建了id_000001节点。
pic5
接着,拿到/locks节点下的所有子节点(id_000000,id_000001,id_000002),判断自己创建的是不是最小的那个节点
如果是,则拿到锁。
释放锁:执行完操作后,把创建的节点给删掉
如果不是,则监听比自己要小1的节点变化
集群管理
还是以我们三个系统A、B、C为例,在ZooKeeper中创建临时节点即可:
pic6
只要系统A挂了,那/groupMember/A这个节点就会删除,通过监听groupMember下的子节点,系统B和C就能够感知到系统A已经挂了。(新增也是同理)
除了能够感知节点的上下线变化,ZooKeeper还可以实现动态选举Master的功能。(如果集群是主从架构模式下)
原理:如果想要实现动态选举Master的功能,Znode节点的类型是带序号的临时节点就好了。
Zookeeper会每次选举最小编号的作为Master,如果Master挂了,自然对应的Znode节点就会删除。然后让新的最小编号作为Master,这样就可以实现动态选举的功能了。
如何保证redis和MySql 数据一致性
- 采用延时双删处理
具体步骤:先删除缓存,再写数据库,休眠一段时间(读数据业务耗时+redis和数据库主从同步耗时),再次删除缓存。
缺点:
-
- 休眠时间的估算准确性;
-
- 增加了写入请求的耗时;
- 异步消息处理
先读redis,然后写MySql,然后更新redis集群服务器数据。将更新数据发送到消息队列,redis服务器订阅更新数据,然后同步更新。
缺点:需要部署消费代码同步数据,有一定的开发量。
另一种情况
在代码层次执行完增删改后执行redis更新。基本没啥优点。代码侵入性高。在并发下还有可能数据不一致。
基于消息中间件,增删改后将增删改对应表和对应的数据唯一标识放入队列。然后在通过队列消息内容查询数据库更新redis。
监听数据库更改来实现redis更新。阿里canal可以做到。可以监听数据库日志来实现。
以上三种方案基于是数据极端事件的数据一致性。还有基于定时调度任务定时对在周期内更新或新增及删除的消息至redis个
优点:开发过程中一般会根据实际情况来选择,或组合使用
实现线程的三种方式:1. new Thread 2.Runable 接口 3. 线程池的方式启动