java
2022-04-22 11:03:33 0 举报
AI智能生成
自己总结
作者其他创作
大纲/内容
Java中的代理
jdk的动态代理
特点:1.动态代理要求代理的目标累必须实现接口 否则不能使用jdk的动态代理
2.动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
2.动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
静态代理
静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
cglib动态代理
是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
特点:1.JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。
如果想代理没有实现接口的类,就可以使用CGLIB实现。
2.CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。
它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦 截)。
3.CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
特点:1.JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。
如果想代理没有实现接口的类,就可以使用CGLIB实现。
2.CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。
它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦 截)。
3.CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
总结
1.使用动态代理的对象必须实现一个或多个接口
2.使用cglib代理的对象则无需实现接口,达到代理类无侵入。
3.动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的 数量,使用更灵活。
4.cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标 对象,需要重写方法,所以目标对象不能为final类。
2.使用cglib代理的对象则无需实现接口,达到代理类无侵入。
3.动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的 数量,使用更灵活。
4.cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标 对象,需要重写方法,所以目标对象不能为final类。
反射
1.Java反射是在运行状态中 获取类 的方法属性 以及执行类的方法
2.主要后去反射类Class 以及menthod执行反射类的方法
2.主要后去反射类Class 以及menthod执行反射类的方法
spring
springboot
Spring
spring事务
隔离级别
未提交读
允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读和幻读问题
优点:并发能力高
缺点:脏读
优点:并发能力高
缺点:脏读
读写提交
只允许事务读取已经被其他事务提交的变更数据,可避免脏读,仍会出现不可重复读和幻读问题。
一般会选择这个为默认
一般会选择这个为默认
可重复读
确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
串行化
数据库最高的隔离级别,要求sql按照顺序执行,克服所有问题,保证数据的一致性
脏读,幻读
脏读:一个事务读取到另一个事务未提交的更新数据,且另一事务失败回滚导致一个事务的数据错误。
不可重复读:在同一个事务中,多次读取同一个数据返回的结果有所不同(后续读取可以读到另一事物已提交的更新数据)。
幻读:一个事务读取到另一个事务已提交的insert数据。
不可重复读:在同一个事务中,多次读取同一个数据返回的结果有所不同(后续读取可以读到另一事物已提交的更新数据)。
幻读:一个事务读取到另一个事务已提交的insert数据。
传播行为7种
REQUIRED(0),
默认的spring事务传播级别,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。
SUPPORTS(1),
如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作。应用场景较少。
MANDATORY(2),
该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。
REQUIRES_NEW(3),
从字面即可知道,new,每次都要一个新事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。
怎么处理整个业务需求呢?就是通过这个PROPAGATION_REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。
这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。
怎么处理整个业务需求呢?就是通过这个PROPAGATION_REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。
NOT_SUPPORTED(4),
not supported ,不支持,当前级别的特点就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。
这个级别有什么好处?可以帮助你将事务极可能的缩小。我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了。用当前级别的事务模板抱起来就可以了。
这个级别有什么好处?可以帮助你将事务极可能的缩小。我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了。用当前级别的事务模板抱起来就可以了。
NEVER(5),
该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行!这个级别上辈子跟事务有仇。
NESTED(6);
字面也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
事务特性
原子性
事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用
一致性
一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
隔离性
可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
持久化
一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
嵌套事务
说明
嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于那个save point
如果子事务回滚,会发生什么?
父事务会回滚到进入子事务前建立的save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。
如果父事务回滚,会发生什么?
父事务回滚,子事务也会跟着回滚!为什么呢,因为父事务结束之前,子事务是不会提交的,我们说子事务是父事务的一部分,正是这个道理。
事务的提交,是什么情况?
是父事务先提交,然后子事务提交,还是子事务先提交,父事务再提交?答案是第二种情况,还是那句话,子事务是父事务的一部分,由父事务统一提交。
子主题
注解
@Autowirde:根据by type注入
@requestmapping 容器启动的时候就把他保存到handlermapping中
@requestParam 获取参数与前后端参数映射(默认是不能为空,如何可以为空设置require 为false)可传递 各种类 型的参数包括数组与json
@requestBody 将前端json实体转换为对象要求属性名称一样
@pathVariablle 可获取url中的参数
@requestBoddy:为什么可以返回json,标记为返回类型为json,执行完控制器返回后,处理器会启动结果解释器resultResolver 解释这个 结果,它回去轮询springmvc的httpmessageConverter 接口的实现类,因为mappingJackson2HttpMessageConvert er已经被mvc注册 所以就匹配上了,所以它在内存就将结果转换成了josn,如果没有扫描到httpMessageConverter就交 给后续去处理
@requestmapping 容器启动的时候就把他保存到handlermapping中
@requestParam 获取参数与前后端参数映射(默认是不能为空,如何可以为空设置require 为false)可传递 各种类 型的参数包括数组与json
@requestBody 将前端json实体转换为对象要求属性名称一样
@pathVariablle 可获取url中的参数
@requestBoddy:为什么可以返回json,标记为返回类型为json,执行完控制器返回后,处理器会启动结果解释器resultResolver 解释这个 结果,它回去轮询springmvc的httpmessageConverter 接口的实现类,因为mappingJackson2HttpMessageConvert er已经被mvc注册 所以就匹配上了,所以它在内存就将结果转换成了josn,如果没有扫描到httpMessageConverter就交 给后续去处理
子主题
springBoot
优点
快速构建
简化依赖
一键部署
应用监控
简化依赖
一键部署
应用监控
核心组件
基础组件
自动装配
启动器
健康检查/j监控
开发者工具
命令交互
自动装配
启动器
健康检查/j监控
开发者工具
命令交互
测试
快速创建测试:选择要测试的类或者方法 右击选择 goto-test自动创建测试类
热部署devtools
核心原理
BaseClassloader加载器 负责加载那些不会改变的类 如第三方jar包
RestartClassLoader加载器,负责加载那些正在开发可变的类,每次修改的时候只加载修改的类就可以了
工作流程:devTools会监听classPath下的文件变动,文件修改时会重新编译,触发Restar加载器加载变动的类 从而实现了热部署
也可以指定那些可以不被加载
RestartClassLoader加载器,负责加载那些正在开发可变的类,每次修改的时候只加载修改的类就可以了
工作流程:devTools会监听classPath下的文件变动,文件修改时会重新编译,触发Restar加载器加载变动的类 从而实现了热部署
也可以指定那些可以不被加载
系统配置文件
YAML文件
优点:结构清晰易读,支持数组,map,json对象
propertiesw文件
区别
1.yaml对数据扩展非常好
2.properties对格式要求没那么严格 yaml文件对格式要求很严格
3.properties支持@PropetySource注解
4.YAML支持多问挡块使用
2.properties对格式要求没那么严格 yaml文件对格式要求很严格
3.properties支持@PropetySource注解
4.YAML支持多问挡块使用
加载配置文件的优先级
在不指定要加载文件时,默认的加载顺序:yml > yaml > properties,因为properties是最后被加载 如果有 相同的属性 会覆盖前面的属性
系统文件配置的加载顺序
1.根目录下的config目录
2.项目根目录
3.classPath下的config目录
4.clasaPath目录
这4个位置的application.properties按照优先级依次降低。如果同一个属性出现在在这4个文件中则以优先级高的为准
2.项目根目录
3.classPath下的config目录
4.clasaPath目录
这4个位置的application.properties按照优先级依次降低。如果同一个属性出现在在这4个文件中则以优先级高的为准
自定义启动图案
在项目的resource目录下新建banner.txt文件,在配置文件中指定:spring.banner.location=/banner.txt
获取自定义配置项
@Value
作用:获取配置文件中的属性
注意:1.所在类必须是spring管理的类
2.@value获取的是默认配置文件 ,@PropertySource可以指定配置文件
注意:1.所在类必须是spring管理的类
2.@value获取的是默认配置文件 ,@PropertySource可以指定配置文件
Environment
概念:一个抽象接口,获取所有的配置参数,系统环境,随机数等
取值:environment.getProperty("com.fxc.name")
注意:获取所有配置文件的加载项 不需要指定文件
取值:environment.getProperty("com.fxc.name")
注意:获取所有配置文件的加载项 不需要指定文件
@ConfiguartionPrepartion
可以将配置文件信息注入到自定义对对象中
多环境配置
开发环境可多配资 命名规则为:application-环境名称.properties,
例如你的多环境有:application-dev.propertie,application-test.propertie等
指定配置环境:1 可在默认配置文件中指定spring.profiles.active=dev
2.通过idea启动指定环境 run/debug中配置
vm options:-Dspring.profiles.active=dev
Program argument:--spring.profiles.active=dev
3.通过命令来启动:java -jar 项目.jar --spring.profiles.active=prod
例如你的多环境有:application-dev.propertie,application-test.propertie等
指定配置环境:1 可在默认配置文件中指定spring.profiles.active=dev
2.通过idea启动指定环境 run/debug中配置
vm options:-Dspring.profiles.active=dev
Program argument:--spring.profiles.active=dev
3.通过命令来启动:java -jar 项目.jar --spring.profiles.active=prod
web开发
spring-boot-starter-web
概念
主要包括,web json tomcat webmvc等基础组件,提供web开发场景的底层依赖
@controller和@RestController区别
如果请求的是页面和数据就用@controller,如果只是请求数据就用@RestController
拦截器HandlerInterceptor
过滤器FileRegistrationBean
过滤器FileRegistrationBean
拦截器
对请求拦截 例如:有些请求必须登录后才能查看的
主要应用在 权限控制 日志记录,监控等
主要应用在 权限控制 日志记录,监控等
过滤器
在http请求发送给servlet之前对request和response进行检查和修改 从而起到过滤的作用 例如过滤一些:xss 敏感词等
区别
功能类似 但是实现不一样
1.过滤器属于servlet容器,拦截器是独立存在
2.过滤器由servlet回调 ,拦截器则是动态代理方式执行
3.过滤器的生命周期由servlet管理,拦截器由spring容器管理
1.过滤器属于servlet容器,拦截器是独立存在
2.过滤器由servlet回调 ,拦截器则是动态代理方式执行
3.过滤器的生命周期由servlet管理,拦截器由spring容器管理
CORS跨域访问
是一种机器 告诉那边的请求可以访问
可以将cros设置成全局配置加载到spring中
WebMvcConfigurer 配置类中的addCorsMappings()是专门为了解决跨域存在的
可以将cros设置成全局配置加载到spring中
WebMvcConfigurer 配置类中的addCorsMappings()是专门为了解决跨域存在的
全局异常处理
@ControllerAdvic和来实现全局异常处理
@ControllerAdvic指定异常处理类,@ExceptionHandler执行异常类型
@ControllerAdvic指定异常处理类,@ExceptionHandler执行异常类型
模板引擎Thymeleaf
简介
服务器页面模板引擎,适用于web和独立环境,能处理HTML xml javascript和文本
实现机制
RestFul风格
概念
1.每一个请求url代表一种资源
2.get获取资源 post新建资源 put更新资源,deletes删除资源
2.get获取资源 post新建资源 put更新资源,deletes删除资源
数据库连接
JbcTemplate
概述
其实这玩意就是对JDBC做了一层封装 没啥好说的 但是可聊
特点
1.速度快 相对有orm框架来说
2.配置简单,除了连接数据库配置 没有其他配置了
3.使用方便
2.配置简单,除了连接数据库配置 没有其他配置了
3.使用方便
提供的方法
1.execut() 执行任何sql
2.update(),batchUpdate()方法修改语句,新增 删除
3.query() 查询
4.call() 执行存储过程和函数
2.update(),batchUpdate()方法修改语句,新增 删除
3.query() 查询
4.call() 执行存储过程和函数
多数据源配置
描述
单数据源配置spring-datasource-url配置项,多数据源用spring-datasource.*.jdbc-url
步骤
第一步:在配置文件中配置多个数据源配置链接信息 例如 spring.datasource.kobe.jdbc-url,spring.dataso urce.track.jdbc-url
第二步:@Configuration 注解到datasouconfig类上 配置多个数据源和jdbc链接实例 详见1-1图
第三步:使用@resource和@authwired注解 来使用jdbc链接实
第二步:@Configuration 注解到datasouconfig类上 配置多个数据源和jdbc链接实例 详见1-1图
第三步:使用@resource和@authwired注解 来使用jdbc链接实
代码片段
MyBatis
什么是ORM
对象关系映射,ORM通过使用描述对象和数据库之间的映射的元数据将程序中的对象自动持久化到关系型数据库
mybatis
描述集成
spring boot集成mybaits 需要集成这个包mybatis-spring-boot-starter
核心概念
mybatis由mapper配置文件,mapper接口,执行器,会话等组成
1.mapper配置文件 基于xml的配置文件来实现
2.mapper接口 是指自定义的操作接口 也就是dao接口,与xml文件对应
3.executor执行器 执行sql 是mybatis的核心接口之一
4.sqlsession mybatis的关键对象,类似于jdbc的链接(connection),sqlsession完全包含数据库所有的执行sql操作方法,底层封装 了jdbc
5.sqlsessionFactory(会话工厂)mybatis的关键对象 sqlsessionFactory可通过sqlsessionFactoryBuilder获取
6.sqlsessionFactoryBuilder构造器 用于解析配置文件 属性配置,别名配置 拦截器配置 数据源事务
1.mapper配置文件 基于xml的配置文件来实现
2.mapper接口 是指自定义的操作接口 也就是dao接口,与xml文件对应
3.executor执行器 执行sql 是mybatis的核心接口之一
4.sqlsession mybatis的关键对象,类似于jdbc的链接(connection),sqlsession完全包含数据库所有的执行sql操作方法,底层封装 了jdbc
5.sqlsessionFactory(会话工厂)mybatis的关键对象 sqlsessionFactory可通过sqlsessionFactoryBuilder获取
6.sqlsessionFactoryBuilder构造器 用于解析配置文件 属性配置,别名配置 拦截器配置 数据源事务
启动和工作流程
1.加载mapper映射的sql配置文件,或者注解sql的相关内容
2.创建会话工厂,通过读取数据源配置来创建会话工厂sqlsessionFactory
3.创建会话 通过会话工厂来创建会话对象 会话对象是一个接口包含 增删改查
4.创建执行器 会话不能操作数据库 只能通过执行器来操作数据库
5.封装sqld对象 将待执行的slq封装成一个对象 该对象包括sql语句 输入参数和返回结果信息
6.操作数据库 使用执行器执行sql 将sql结果返回
特别重点:sqlsessionFactory创建会话工厂 mapper提供sql映射
2.创建会话工厂,通过读取数据源配置来创建会话工厂sqlsessionFactory
3.创建会话 通过会话工厂来创建会话对象 会话对象是一个接口包含 增删改查
4.创建执行器 会话不能操作数据库 只能通过执行器来操作数据库
5.封装sqld对象 将待执行的slq封装成一个对象 该对象包括sql语句 输入参数和返回结果信息
6.操作数据库 使用执行器执行sql 将sql结果返回
特别重点:sqlsessionFactory创建会话工厂 mapper提供sql映射
Spring Data Jpa
描述
一种对象/关联映射工具来管理Java应用中的关系数据,通过xml或者注解来描述对象-关系表之间的映射关系,并将实体持久化到数据库中
配置文件详解
spring.jpa.properties.hibernate.hbm2ddl.auto
=create 启动删除上一次生成的表和数据
=update 常用表示当前实体类变化时 表结构跟着更新
=create-Drop 启动时根据类生成表 sessionFactory关闭时表会被删除
=validate 表示启动时验证类和表是否一致
none=什么也不做
=create 启动删除上一次生成的表和数据
=update 常用表示当前实体类变化时 表结构跟着更新
=create-Drop 启动时根据类生成表 sessionFactory关闭时表会被删除
=validate 表示启动时验证类和表是否一致
none=什么也不做
JpaRepository
描述
继承spring data中的Repository实现了增删改查
具体方法描述
1.save() 新增和修改都用这个方法
2.查询用find
3.删除用delete
4.属性查询 findByUserName
5.组合查询 findByUserNameOrPassword
6.只用HQL自定义查询 使用@query("select * from user ")
注意user是实体名字 不是表名,查询条件中的字段是实体字段不是表字段
7.使用sql查询@query(value = "select * from user " nativeQuery=true)
nativeQuery=true表示执行原生sql,如果要删除或者修改还需要加上一个@modifying
8.分页查询使用page jpa内置分配的功能直接用
2.查询用find
3.删除用delete
4.属性查询 findByUserName
5.组合查询 findByUserNameOrPassword
6.只用HQL自定义查询 使用@query("select * from user ")
注意user是实体名字 不是表名,查询条件中的字段是实体字段不是表字段
7.使用sql查询@query(value = "select * from user " nativeQuery=true)
nativeQuery=true表示执行原生sql,如果要删除或者修改还需要加上一个@modifying
8.分页查询使用page jpa内置分配的功能直接用
@Scheduled定时任务
@Scheduled是单线程模式的 如果有多个的话 会产生线程阻塞需要加一个线程池
1.增加多线程配置类 配置定时任务
1.增加多线程配置类 配置定时任务
Quartz定时任务
基本概念
分布式Quartz定时任务
1.注意集群环境下时钟必须同步
2.初始化quzrtz数据库,可以在官网中查找
3修改配置文件
4.定义定时任务增加了两个注解,@PersistJobDataAfterExecution(持久化任务信息)和@DisallowConcurrenExecution(禁止并发执行)
2.初始化quzrtz数据库,可以在官网中查找
3修改配置文件
4.定义定时任务增加了两个注解,@PersistJobDataAfterExecution(持久化任务信息)和@DisallowConcurrenExecution(禁止并发执行)
redis
支持类型
string list set hash,sortedSet
连接客户端程序
jedis,lettuce
1.jedis 是直连redis的 非线程安全(可使用连接池对其进行物理链接)
2.lettuce 是基于netty nio链接实例 线程安全 可有效的管理多个连接
1.jedis 是直连redis的 非线程安全(可使用连接池对其进行物理链接)
2.lettuce 是基于netty nio链接实例 线程安全 可有效的管理多个连接
redis数据缓存
步骤详解
1.添加redisCache配置类继承CacheConfigurerSupport 并且重写keyGenerator,如果不配置就默认使用参数名作为主键key
@Configuration 标识他是配置类
@EnableCaching 缓存注解 也可以配置在启动类上
2 在读取数据的方法添加@Cacheable注解,这样就可以将该方法的返回结果放入缓存(先读取缓存如果没有命中就去查询d b)
@Configuration 标识他是配置类
@EnableCaching 缓存注解 也可以配置在启动类上
2 在读取数据的方法添加@Cacheable注解,这样就可以将该方法的返回结果放入缓存(先读取缓存如果没有命中就去查询d b)
redis实现session共享
分布式缓存的共享决绝方案
1.客户端储存用cookie 不安全 不可靠
2.session绑定 使用nginx的ip绑定策略,同一个ip指定访问同一台机器,容易造成单点故障 如果一台机器宕机则session丢失
3.tomcat中的session同步 可能会延迟
4. session共享redis中
(1.session共享第一步就是添加配置类sessionConfig @Configuration标识该类是配置类
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)开启redissession 并且设置失效时间为一月
如果使用该注解 则配置文件中的server.session.timeout失效
)
2.session绑定 使用nginx的ip绑定策略,同一个ip指定访问同一台机器,容易造成单点故障 如果一台机器宕机则session丢失
3.tomcat中的session同步 可能会延迟
4. session共享redis中
(1.session共享第一步就是添加配置类sessionConfig @Configuration标识该类是配置类
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)开启redissession 并且设置失效时间为一月
如果使用该注解 则配置文件中的server.session.timeout失效
)
rabbitMQ消息队列
spring-boot性能监控Actuauor
监控端点分成了两类 内置端点和自定义端点 内置端点提供web接口
监控端点分成了两类 内置端点和自定义端点 内置端点提供web接口
子主题
概述
Actuauor是spring boot应用系统监控的框架也就是应用程序内部监控,它的核心是端点 内置了很多组件例如 health env beans等
还可以与外部系统应用进行整合
还可以与外部系统应用进行整合
端点
Actuator内置端点
提供了web请求访问接口 如 actuator/ ( actuator/health actuator/health 这两个是默认开启)
内置端点分成了三类:
1.应用配置类:可以查看配置信息 bean信息 yml信息 环境信息 请求映射信息
2.度量指标类:运行的动态信息堆栈 请求链接 健康状态
3.操作控制类:主要是shutdown用户可以发送一个请求关闭应用监控
/health(比较常用) 健康状态检测 主要是检查应用的运行状态 如数据链接,磁盘不够等。默认只显示了up down
主要有4中 也是设置状态的顺序(down,outofservice ,up,unknow)
通过以下几个健康指数来检测:DataSourceHealthIthindictor,DiskSpaceHealthIthindictor
MongoHealthIthindictor,RedisHealthIthindictor等
/info 查看系统配置文件等信息
/beans 查看spring容器加载的信息 可以查看类 是否是单例 别名 地址啥的
/conditions 查看应用运行的代码、返回类的组件和自动装配信息
/configrops 获取配置文件信息 先整理这么多吧 太多了懒得整理
内置端点分成了三类:
1.应用配置类:可以查看配置信息 bean信息 yml信息 环境信息 请求映射信息
2.度量指标类:运行的动态信息堆栈 请求链接 健康状态
3.操作控制类:主要是shutdown用户可以发送一个请求关闭应用监控
/health(比较常用) 健康状态检测 主要是检查应用的运行状态 如数据链接,磁盘不够等。默认只显示了up down
主要有4中 也是设置状态的顺序(down,outofservice ,up,unknow)
通过以下几个健康指数来检测:DataSourceHealthIthindictor,DiskSpaceHealthIthindictor
MongoHealthIthindictor,RedisHealthIthindictor等
/info 查看系统配置文件等信息
/beans 查看spring容器加载的信息 可以查看类 是否是单例 别名 地址啥的
/conditions 查看应用运行的代码、返回类的组件和自动装配信息
/configrops 获取配置文件信息 先整理这么多吧 太多了懒得整理
自定义端点
使用@Endpoint @Jmxpoint @Webendpoint 等实现对应的方法就可以作为一个端点
@Endpoint 支持jmx和http
@Jmxpoint 支持jmx
@Webendpoint 支持http
注解作用在一个类上 这个类必须是交给spring管理的 设置端点的id,将注解@ReadOperation作用在方法上
访问路径为前缀+id形式 例如:/actuator/systemtime
@Endpoint 支持jmx和http
@Jmxpoint 支持jmx
@Webendpoint 支持http
注解作用在一个类上 这个类必须是交给spring管理的 设置端点的id,将注解@ReadOperation作用在方法上
访问路径为前缀+id形式 例如:/actuator/systemtime
子主题
RestTemplate
微服务之间的调用,底层是通过HttpURLConnecion,服务模块化之间的调用
spring MVC
流程图以及环节
流程图
环节
第一步: 用户发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找
第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)
第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)也就是执行Han dlerExecutionChain中的内容
第五步:处理器适配器去执行Handler
第六步:Handler执行完给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView
第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析
第九步:视图解析器像前端控制器返回View
第十步:前端控制器对视图进行渲染
第十一步:前端控制器向用户响应结果
第一步: 用户发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找
第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)
第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)也就是执行Han dlerExecutionChain中的内容
第五步:处理器适配器去执行Handler
第六步:Handler执行完给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView
第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析
第九步:视图解析器像前端控制器返回View
第十步:前端控制器对视图进行渲染
第十一步:前端控制器向用户响应结果
子主题
jvm
1.7版本内存分配
程序计数器
1.(线程私有)当前线 程所执行的字节码的行号指示器。通过改变这个计数器来执行下一条指令
分支、循环、跳转、异常处理、线程恢复等基础功能都需 要依赖这个计数器来完成。
2.线程被挂起的时候 如果这个线程获取到时间片的时候它想从被挂起的地方执行,程序计数器就是记录线程挂起位置的
分支、循环、跳转、异常处理、线程恢复等基础功能都需 要依赖这个计数器来完成。
2.线程被挂起的时候 如果这个线程获取到时间片的时候它想从被挂起的地方执行,程序计数器就是记录线程挂起位置的
Java虚拟机栈
1.(线程私有)生命周期生命周期和线程一致,也就是线程结束了,该虚拟机栈也销毁了
2.作用: 描述Java方法执行的内存模型每个方法在执行的同时都会创建一个栈帧(栈帧是方法运行时的基础数据结构),用于存储局部 变量表、操作数栈、动态链接、方法出口 等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
3.局部变量表存放了编译期可知的各种 基本数据类型、对象引用类型和returnAddress类型(指向了一条字节码指令的地址)。
2.作用: 描述Java方法执行的内存模型每个方法在执行的同时都会创建一个栈帧(栈帧是方法运行时的基础数据结构),用于存储局部 变量表、操作数栈、动态链接、方法出口 等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
3.局部变量表存放了编译期可知的各种 基本数据类型、对象引用类型和returnAddress类型(指向了一条字节码指令的地址)。
本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间 的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚 拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用方式 与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如 Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法 栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
Java堆
1.(线程共享)此内存区域的唯一目的就 是存放对象实例以及数组实例,几乎所有的对象实例都在这里分配内存。
2.Java堆是垃圾收集器管理的主要区域
3.在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。
2.Java堆是垃圾收集器管理的主要区域
3.在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。
本地方法区
1.(线程共享)方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚 拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规 范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应 该是与Java堆区分开来。
运行时常量区
(方法区的一部分)用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到常量池中
1.8以前放在方法区,大小受限于方法区。1.8以后放在堆中,主要由字面量(字符串,基本数据类型的值,常量值)和符合引用组成。。
1.8以前放在方法区,大小受限于方法区。1.8以后放在堆中,主要由字面量(字符串,基本数据类型的值,常量值)和符合引用组成。。
直接内存
直接内存不是运行时数据区第一部分,也不是jvm规范定义的内存,它的分配不会受到堆的限制,但他受本机内存总大小的和处理器寻址空间的限制。
访问流程
虚拟机类加载机制
类的加载时机
生命周期:加载-》验证-》准备-》解析-》初始化-》使用-》卸载
java类加载 会初始化的情况有且仅有以下五种:(也称为主动引用)
1..遇到new(用new实例对象),getStatic(读取一个静态字段),putstatic(设置一个静态字段),invokeStatic(调用一个类的静态方法)这四条指令字节码命令时
2.使用Java.lang.reflect包的方法对类进行反射调用时,如果此时类没有进行init,会先init。
3.当初始化一个类时,如果其父类没有进行初始化,先初始化父类
4.jvm启动时,用户需要指定一个执行的主类(包含main的类)虚拟机会先执行这个类
5.当使用JDK1.7的动态语言支持的时候,当java.lang.invoke.MethodHandler实例后的结果是REF-getStatic/REF_putstatic/REF_invokeStatic的句柄,并且这些句柄对应的类没初始化的话应该首先初始。
生命周期:加载-》验证-》准备-》解析-》初始化-》使用-》卸载
java类加载 会初始化的情况有且仅有以下五种:(也称为主动引用)
1..遇到new(用new实例对象),getStatic(读取一个静态字段),putstatic(设置一个静态字段),invokeStatic(调用一个类的静态方法)这四条指令字节码命令时
2.使用Java.lang.reflect包的方法对类进行反射调用时,如果此时类没有进行init,会先init。
3.当初始化一个类时,如果其父类没有进行初始化,先初始化父类
4.jvm启动时,用户需要指定一个执行的主类(包含main的类)虚拟机会先执行这个类
5.当使用JDK1.7的动态语言支持的时候,当java.lang.invoke.MethodHandler实例后的结果是REF-getStatic/REF_putstatic/REF_invokeStatic的句柄,并且这些句柄对应的类没初始化的话应该首先初始。
类加载过程5个阶段
加载
作用
将外部的class文件加载到虚拟机,存储到方法区内 即二进制字节流
具体流程
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据 的访问入口。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据 的访问入口。
注
数组类通过java虚拟机直接创建,不通过类加载器创建
验证
作用
确保加载进来的class文件包含的信息符合Java虚拟机的要求
具体流程
文件格式的验证
1.验证class文件格式 是否以魔数0xCAFEBABE开头。 主、次版本号是否在当前虚拟机处理范围之内。
2.常量池的常量中是否有不被支持的常量类型(检查常量tag标志)
3.Class文件中各个部分及文件本身是否有被删除的或附加的其他信息。
文件格式的验证不止这些 这只是其中的一部分 因为本大爷懒得翻资料 所以就这样了
2.常量池的常量中是否有不被支持的常量类型(检查常量tag标志)
3.Class文件中各个部分及文件本身是否有被删除的或附加的其他信息。
文件格式的验证不止这些 这只是其中的一部分 因为本大爷懒得翻资料 所以就这样了
元数据验证
1.对字节码描述的信息进行语义分析,是否符合java规范
2.这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)。
3.这个类的父类是否继承了不允许被继承的类(被final修饰的类)。
4.如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。
5.类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合 规则的方法重载,例如方法参数都一致,但返回值类型却不同等)。
2.这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)。
3.这个类的父类是否继承了不允许被继承的类(被final修饰的类)。
4.如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。
5.类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合 规则的方法重载,例如方法参数都一致,但返回值类型却不同等)。
字节码验证
这个阶段最复杂主要目的是通过数据流和控制流分析, 确定程序语义是合法的、符合逻辑的
符号引用验证
1.符号引用中通过字符串描述的全限定名是否能找到对应的类。
2.在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
3.符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被 当前类访问。
2.在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
3.符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被 当前类访问。
准备
作用
为类变量设置内存,设置初始化值
具体流程
1.正式为类变量分配内存(仅是被static修饰的变量,不包括实例变量)
2.正式为类变量设置初始值 这些变量所使用的的内存都在方法区中进行分配
2.正式为类变量设置初始值 这些变量所使用的的内存都在方法区中进行分配
注
实例变量内存不在此处分配
则变量为常量(被final修饰)则赋值开发者定义的值
则变量为常量(被final修饰)则赋值开发者定义的值
解析
作用
将常量池中的符号引用转换为直接引用。
什么事符号引用 直接引用
符号应用
符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可 以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的 内存布局无关,引用的目标并不一定已经加载到内存中。各种虚拟机实现的内存布局可以各 不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义 在Java虚拟机规范的Class文件格式中。
直接引用
直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是 一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引 用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目 标必定已经在内存中存在。
具体流程
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点 限定符7类符号引用进行,分别对应于常量池的CONSTANT_Class_info、 CONSTANT_Fieldref_info、CONSTANT_Methodref_info、 CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、 CONSTANT_MethodHandle_info和CONSTANT_InvokeDynamic_info 7种常量类型。
注
初始化
作用
初始化类变量,静态语句块
流程
初始化是指为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,
主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
(1)声明类变量时指定初始值;
(2)使用静态代码块为类变量指定初始值。
JVM初始化步骤:
(1)假如这个类还没有被加载和连接,则程序先加载并连接该类;
(2)假如该类的直接父类还没有被初始化,则先初始化其直接父类;
(3)假如类中有初始化语句,则系统依次执行这些初始化语句。
类初始化时机:只有当对类主动使用的时候才会导致类的初始化,
类的主动使用包括以下6种:
创建类的实例,也就是new的方式;
访问某个类或接口的静态变量,或者对该静态变量赋值;
调用类的静态方法;
反射(如Class.forName("…"));
初始化某个类的子类,则其父类也会被初始化;
Java虚拟机启动时被标明为启动类的类,直接使用java.exe命令来运行某个主类。
主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
(1)声明类变量时指定初始值;
(2)使用静态代码块为类变量指定初始值。
JVM初始化步骤:
(1)假如这个类还没有被加载和连接,则程序先加载并连接该类;
(2)假如该类的直接父类还没有被初始化,则先初始化其直接父类;
(3)假如类中有初始化语句,则系统依次执行这些初始化语句。
类初始化时机:只有当对类主动使用的时候才会导致类的初始化,
类的主动使用包括以下6种:
创建类的实例,也就是new的方式;
访问某个类或接口的静态变量,或者对该静态变量赋值;
调用类的静态方法;
反射(如Class.forName("…"));
初始化某个类的子类,则其父类也会被初始化;
Java虚拟机启动时被标明为启动类的类,直接使用java.exe命令来运行某个主类。
类加载器
概述
只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现[1],是虚拟机自身的一部分;另 一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且 全都继承自抽象类java.lang.ClassLoader。
在jvm类加载过程中,有一步叫做加载的流程
加载 : 根据类的全限定名获取到其定义的二进制字节流,并将其加载到内存中. 此时需要借助类加载器来帮助完成
全限定名 : 包名 + 类名
类加载器分为4类 :
%JAVA_HOME% : 为JDK设置的环境变量路径. 如环境变量里设置了%java_home%=C:\jdk1.8.0
1. Bootstrap Classloader : 启动类加载器,用来加载 %JAVA_HOME%/jre/lib 下的, 如 rt.jar中的class文件 或者 xbootclasspath选项指定的jar包
2. Extension Classloader : 扩展类加载器 , 用来加载 %JAVA_HOME%/jre/ext 中的class文件 或者 -Djava.ext.dirs指定目录下的jar包,开发者可以直接使用扩展类加载器。
3. Application Classloader : 应用类加载器 , 用来加载classpath下的class文件
4. Custom Classloader : 用户自定义类加载器,用来加载自定义内容.此加载器需要用户自己继承Classloader类
在jvm类加载过程中,有一步叫做加载的流程
加载 : 根据类的全限定名获取到其定义的二进制字节流,并将其加载到内存中. 此时需要借助类加载器来帮助完成
全限定名 : 包名 + 类名
类加载器分为4类 :
%JAVA_HOME% : 为JDK设置的环境变量路径. 如环境变量里设置了%java_home%=C:\jdk1.8.0
1. Bootstrap Classloader : 启动类加载器,用来加载 %JAVA_HOME%/jre/lib 下的, 如 rt.jar中的class文件 或者 xbootclasspath选项指定的jar包
2. Extension Classloader : 扩展类加载器 , 用来加载 %JAVA_HOME%/jre/ext 中的class文件 或者 -Djava.ext.dirs指定目录下的jar包,开发者可以直接使用扩展类加载器。
3. Application Classloader : 应用类加载器 , 用来加载classpath下的class文件
4. Custom Classloader : 用户自定义类加载器,用来加载自定义内容.此加载器需要用户自己继承Classloader类
双亲委派模型
双亲委派模型图
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当 有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系 来实现,而是都使用组合(Composition)关系来复用父加载器的代码。
工作过程
如果一个类加载器收到了类加载的请求,它首先不会自己 去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是 如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈 自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自 己去加载。
优点
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着 它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在 rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加 载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有 使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为 java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object 类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。如果读者 有兴趣的话,可以尝试去编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编 译,但永远无法被加载运行[2]。
代码分析
双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现却非常简单,实现双亲
委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中,如代码清单7-10所示, 逻辑清晰易懂:先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方 法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出 ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
方法说明
getParent() 返回该类加载器的父类加载器。
loadClass(String name) 加载名称为 二进制名称为name 的类,返回的结果是 java.lang.Class 类的实例。
findClass(String name) 查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。
findLoadedClass(String name) 查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。
resolveClass(Class<?> c) 链接指定的 Java 类。
委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中,如代码清单7-10所示, 逻辑清晰易懂:先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方 法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出 ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
方法说明
getParent() 返回该类加载器的父类加载器。
loadClass(String name) 加载名称为 二进制名称为name 的类,返回的结果是 java.lang.Class 类的实例。
findClass(String name) 查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。
findLoadedClass(String name) 查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。
resolveClass(Class<?> c) 链接指定的 Java 类。
破坏双亲委派
JNDI
1.双亲委派很好地解 决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载),基础类 之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完 美,如果基础类又要调用回用户的代码,那该怎么办?
例如:JNDI已经是一个标准的服务 它的代码是交给启动类来加载的,但JNDI的目的就是对 资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的 JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能“认识”这些 代码啊!那该怎么办?
解决:为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载 器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的 setContextClassLoaser()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承 一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序 类加载器。
有了线程上下文类加载器,就可以做一些“舞弊”的事情了,JNDI服务使用这个线程上下 文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动 作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经 违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动 作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
热部署(JSR- 291(即OSGi R4.2))
2.热部署也就说不用重启就可以直接使用。
目前OSGi已经成为了业界“事实上”的Java模块化标准[3OSGi实现模块化热部署的关键则是 它自定义的类加载器机制的实现。每一个程序模块(OSGi中称为Bundle)都有一个自己的类 加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替 换。
在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加 复杂的网状结构,当收到类加载请求时,OSGi将按照下面的顺序进行类搜索:
1)将以java.*开头的类委派给父类加载器加载。
2)否则,将委派列表名单内的类委派给父类加载器加载。
3)否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。
4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的 类加载器加载。
6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
7)否则,类查找失败。
上面的查找顺序中只有开头两点仍然符合双亲委派规则,其余的类查找都是在平级的类 加载器中进行的。
3.自定义类加载器打破双亲委派,不委派双亲
classLoader.loadClass的时候会进行双亲委派进行加载,如果双亲都找不到指定类会调用findClass方法。
classLoader类中的loadClass有默认的实现就是双亲委派逻辑,findClass没有默认的实现需要自定义类加载器来实现。
所以如果只是使用一个自定义类加载器而不打破双亲委派,只要继承ClassLoader来重写findClass。如果想打破双亲委派,也要重写loadClass方法了,做到不委派。
1.双亲委派很好地解 决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载),基础类 之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完 美,如果基础类又要调用回用户的代码,那该怎么办?
例如:JNDI已经是一个标准的服务 它的代码是交给启动类来加载的,但JNDI的目的就是对 资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的 JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能“认识”这些 代码啊!那该怎么办?
解决:为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载 器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的 setContextClassLoaser()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承 一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序 类加载器。
有了线程上下文类加载器,就可以做一些“舞弊”的事情了,JNDI服务使用这个线程上下 文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动 作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经 违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动 作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
热部署(JSR- 291(即OSGi R4.2))
2.热部署也就说不用重启就可以直接使用。
目前OSGi已经成为了业界“事实上”的Java模块化标准[3OSGi实现模块化热部署的关键则是 它自定义的类加载器机制的实现。每一个程序模块(OSGi中称为Bundle)都有一个自己的类 加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替 换。
在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加 复杂的网状结构,当收到类加载请求时,OSGi将按照下面的顺序进行类搜索:
1)将以java.*开头的类委派给父类加载器加载。
2)否则,将委派列表名单内的类委派给父类加载器加载。
3)否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。
4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的 类加载器加载。
6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
7)否则,类查找失败。
上面的查找顺序中只有开头两点仍然符合双亲委派规则,其余的类查找都是在平级的类 加载器中进行的。
3.自定义类加载器打破双亲委派,不委派双亲
classLoader.loadClass的时候会进行双亲委派进行加载,如果双亲都找不到指定类会调用findClass方法。
classLoader类中的loadClass有默认的实现就是双亲委派逻辑,findClass没有默认的实现需要自定义类加载器来实现。
所以如果只是使用一个自定义类加载器而不打破双亲委派,只要继承ClassLoader来重写findClass。如果想打破双亲委派,也要重写loadClass方法了,做到不委派。
JVM参数设置
参数设置参照表
jvm垃圾回收
概述
程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随 线程而灭;
栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个 栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由JIT编译器 进行一些优化,但在本章基于概念模型的讨论中,大体上可以认为是编译期可知的),因此 这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问 题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆和方法区则不一 样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也 可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配 和回收都是动态的,垃圾收集器所关注的是这部分内存,本章后续讨论中的“内存”分配与回 收也仅指这一部分内存。
栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个 栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由JIT编译器 进行一些优化,但在本章基于概念模型的讨论中,大体上可以认为是编译期可知的),因此 这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问 题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆和方法区则不一 样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也 可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配 和回收都是动态的,垃圾收集器所关注的是这部分内存,本章后续讨论中的“内存”分配与回 收也仅指这一部分内存。
判断对象是否存活算法
引用计数法
概述:给每个对象添加一个计数器,当有地方引用该对象时计数器加1,当引用失效时计数器减1。用对象计数器是否为0来判断对象是否可被回收
优点:实现简单 执行效率高
缺点:它很难解决对象 之间相互循环引用的问题。
例子:对象objA和objB都有字段 instance,赋值令objA.instance=objB及objB.instance=objA,除此之外,这两个对象再无任何引 用,实际上这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引 用计数都不为0,于是引用计数算法无法通知GC收集器回收它们。
优点:实现简单 执行效率高
缺点:它很难解决对象 之间相互循环引用的问题。
例子:对象objA和objB都有字段 instance,赋值令objA.instance=objB及objB.instance=objA,除此之外,这两个对象再无任何引 用,实际上这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引 用计数都不为0,于是引用计数算法无法通知GC收集器回收它们。
可达性分析算法
概述
这个算法的基本思 路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所 走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连 (用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如 图3-1所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达 的,所以它们将会被判定为是可回收的对象。
哪些属于GC root
虚拟机栈中引用的对象
此时的s,即为GC Root,当s置空时,localParameter对象也断掉了与GC Root的引用链,将被回收。
虚拟机栈中的引用的对象,我们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种情况是最常见的。
虚拟机栈中的引用的对象,我们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种情况是最常见的。
方法区中类静态属性引用的对象
此时的s,即为GC Root,s置为null,经过GC后,s所指向的properties对象由于无法与GC Root建立关系被回收。而m作为类的静态属性,也属于GC Root,parameter 对象依然与GC root建立着连接,所以此时parameter对象并不会被回收。
使用了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。
使用了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。
使用了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。
方法区中常量引用的对象
m即为方法区中的常量引用,也为GC Root,s置为null后,final对象也不会因没有与GC Root建立联系而被回收。
本地方法栈中JNI(即一般说的Native方法)引用的对象。
任何native接口都会使用某种本地方法栈,实现的本地方法接口是使用C连接模型的话,那么它的本地方法栈就是C栈。当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入Java栈。然而当它调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。
对象的生存或者死亡判定
java 并不通过引用计数器来判定对象的存活 而是通过可达性分析算法判定对象的存活。即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。
标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。
1).第一次标记并进行一次筛选。
筛选的条件是此对象是否有必要执行finalize()方法。
当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象被回收。
2).第二次标记
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。
Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。
标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。
1).第一次标记并进行一次筛选。
筛选的条件是此对象是否有必要执行finalize()方法。
当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象被回收。
2).第二次标记
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。
Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。
引用
概念
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引 用链是否可达,判定对象是否存活都与“引用”有关。在JDK 1.2以前,Java中的引用的定义很 传统:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块 内存代表着一个引用。这种定义很纯粹,但是太过狭隘,一个对象在这种定义下只有被引用 或者没有被引用两种状态,对于如何描述一些“食之无味,弃之可惜”的对象就显得无能为 力。我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存 空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这 样的应用场景。
jdk1.2之后分为4个引用
强引用
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 ps:强引用其实也就是我们平时A a = new A()这个意思。
软引用
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用
只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用
虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引 用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一 个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在 JDK 1.2之后,提供了PhantomReference类来实现虚引用。
垃圾收集算法
概述
在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。这里我们讨论几种常见的垃圾收集算法的核心思想。
具体算法
标记清除法
分为标记和清除两个阶段
步骤:首先标记出所有需要回收的对象,在标记完成后统一回收所有 被标记的对象
标记清除法是最基础的算法 所有的算法都是在此基础上是因为后续的收集算法都是基于这种思路并对其不足进行改进而得到 的
存在的问题:1.标记和清除效率不高
2.空间问题 标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程 序运行过程中需要分配较大对 象时,无法找到足够的连续内存而不得不提前触发另一次垃圾 收集动作。
上图中等方块的假设是2M,小一些的是1M,大一些的是4M。等我们回收完,内存就会切成了很多段。我们知道开辟内存空间时,需要的是连续的内存区域,这时候我们需要一个2M的内存区域,其中有2个1M是没法用的。这样就导致,其实我们本身还有这么多的内存的,但却用不了。
步骤:首先标记出所有需要回收的对象,在标记完成后统一回收所有 被标记的对象
标记清除法是最基础的算法 所有的算法都是在此基础上是因为后续的收集算法都是基于这种思路并对其不足进行改进而得到 的
存在的问题:1.标记和清除效率不高
2.空间问题 标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程 序运行过程中需要分配较大对 象时,无法找到足够的连续内存而不得不提前触发另一次垃圾 收集动作。
上图中等方块的假设是2M,小一些的是1M,大一些的是4M。等我们回收完,内存就会切成了很多段。我们知道开辟内存空间时,需要的是连续的内存区域,这时候我们需要一个2M的内存区域,其中有2个1M是没法用的。这样就导致,其实我们本身还有这么多的内存的,但却用不了。
复制算法
复制算法将内存划分为两个区间,在任意时间点,所有动态分配的对象都只能分配在其中一个区间(称为活动区间),而另外一个区间(称为空闲区间)则是空闲的。
当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程。接下来GC线程会将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。
此时,空闲区间已经与活动区间交换,而垃圾对象现在已经全部留在了原来的活动区间,也就是现在的空闲区间。事实上,在活动区间转换为空间区间的同时,垃圾对象已经被一次性全部回收。
优点:实现简单 效率高
缺点: (1)缩小了原来内存的一般
(2)如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址 重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视。
复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费。
,所以在老年代一般不能直接选用这种算法。
当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程。接下来GC线程会将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。
此时,空闲区间已经与活动区间交换,而垃圾对象现在已经全部留在了原来的活动区间,也就是现在的空闲区间。事实上,在活动区间转换为空间区间的同时,垃圾对象已经被一次性全部回收。
优点:实现简单 效率高
缺点: (1)缩小了原来内存的一般
(2)如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址 重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视。
复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费。
,所以在老年代一般不能直接选用这种算法。
复制算法图例:
标记整理方法
标记/整理算法与标记/清除算法非常相似,它也是分为两个阶段:标记和整理。
标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。
不过任何算法都会有其缺点,标记/整理算法唯一的缺点就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上来说,标记/整理算法要低于复制算法。
标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。
不过任何算法都会有其缺点,标记/整理算法唯一的缺点就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上来说,标记/整理算法要低于复制算法。
标记整理法图例
分代收集算法
当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算 法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆 分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代 中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付 出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间 对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
效率:复制算法>标记/整理算法>标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。
内存整齐度:复制算法=标记/整理算法>标记/清除算法。
内存利用率:标记/整理算法=标记/清除算法>复制算法。
效率:复制算法>标记/整理算法>标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。
内存整齐度:复制算法=标记/整理算法>标记/清除算法。
内存利用率:标记/整理算法=标记/清除算法>复制算法。
垃圾收集器
概念
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现
首先理解垃圾收集器要理解一下的概念:
1)并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态
2)并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
Minor GC 和 Full GC
新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
吞吐量
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。
虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
首先理解垃圾收集器要理解一下的概念:
1)并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态
2)并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
Minor GC 和 Full GC
新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
吞吐量
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。
虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
介绍
各个收集器图例
图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。
Serial(串行)收集器
特性:
这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。Stop The World
应用场景:
Serial收集器是虚拟机运行在Client模式下的默认新生代收集器。
串行的垃圾收集器有两种,Serial与Serial Old,一般两者搭配使用。新生代采用Serial,是利用复制算法;老年代使用Serial Old采用标记-整理算法。Client应用或者命令行程序可以,通过-XX:+UseSerialGC可以开启上述回收模式。
优势:
简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。Stop The World
应用场景:
Serial收集器是虚拟机运行在Client模式下的默认新生代收集器。
串行的垃圾收集器有两种,Serial与Serial Old,一般两者搭配使用。新生代采用Serial,是利用复制算法;老年代使用Serial Old采用标记-整理算法。Client应用或者命令行程序可以,通过-XX:+UseSerialGC可以开启上述回收模式。
优势:
简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
Serial(串行) Old收集器
特性:Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整 理”算法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用。
应用场景:如果在Server模式 下,那么它主要还有两大用途:一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge 收集器搭配使用[1],另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
特性:Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整 理”算法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用。
应用场景:如果在Server模式 下,那么它主要还有两大用途:一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge 收集器搭配使用[1],另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
ParNew收集器
ParNew收集器
特性:ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之 外,其余行为包括Serial收集器可用的所 有控制参数(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、 Stop The World、对 象分配规则、回收策略等都与Serial收集器完全一样。
应用场景:ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。
很重要的原因是:除了Serial收集器外,目前只有它能与CMS收集器配合工作。
在JDK 1.5时期,HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器——CMS收集器,这款收 集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。
不幸的是,CMS作为老年代的收集器,却无法与JDK 1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作, 所以在JDK 1.5中使用CMS来收集老年代的时候,新生代只能选择ParNew或者Serial收集器中的一个。
总结:
ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在 线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保 证可以超越Serial收集器。当然,随着可以使用的CPU的数量的增加,它对于GC时系统资源 的有效利用还是很有好处的。它默认开启的收集线程数与CPU的数量相同,在CPU非常多 (譬如32个,现在CPU动辄就4核加超线程,服务器超过32个逻辑CPU的情况越来越多了)的 环境下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
应用场景:ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。
很重要的原因是:除了Serial收集器外,目前只有它能与CMS收集器配合工作。
在JDK 1.5时期,HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器——CMS收集器,这款收 集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。
不幸的是,CMS作为老年代的收集器,却无法与JDK 1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作, 所以在JDK 1.5中使用CMS来收集老年代的时候,新生代只能选择ParNew或者Serial收集器中的一个。
总结:
ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在 线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保 证可以超越Serial收集器。当然,随着可以使用的CPU的数量的增加,它对于GC时系统资源 的有效利用还是很有好处的。它默认开启的收集线程数与CPU的数量相同,在CPU非常多 (譬如32个,现在CPU动辄就4核加超线程,服务器超过32个逻辑CPU的情况越来越多了)的 环境下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
Parallel Scavenge收集器
特性:
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。
Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点 是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到 一个可控制的吞吐量(Throughput)。
适用场景:停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高 吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不 需要太多交互的任务。
Parallel Scavenge收集器提供了两个参数来用于精确控制吞吐量,一是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis参数,二是控制吞吐量大小的 -XX:GCTimeRatio参数;
“ -XX:MaxGCPauseMillis” 参数允许的值是一个大于0的毫秒数,收集器将尽可能的保证内存垃圾回收花费的时间不超过设定的值(但是,并不是越小越好,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的,如果设置的值太小,将会导致频繁GC,这样虽然GC停顿时间下来了,但是吞吐量也下来了)。
“ -XX:GCTimeRatio”参数的值是一个大于0且小于100的整数,也就是垃圾收集时间占总时间的比率,默认值是99,就是允许最大1%(即1/(1+99))的垃圾收集时间。
“-XX:UseAdaptiveSizePolicy”参数是一个开发,如果这个参数打开之后,虚拟机会根据当前系统运行情况收集监控信息,动态调整新生代的比例、老年大大小等细节参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略。
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。
Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点 是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到 一个可控制的吞吐量(Throughput)。
适用场景:停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高 吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不 需要太多交互的任务。
Parallel Scavenge收集器提供了两个参数来用于精确控制吞吐量,一是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis参数,二是控制吞吐量大小的 -XX:GCTimeRatio参数;
“ -XX:MaxGCPauseMillis” 参数允许的值是一个大于0的毫秒数,收集器将尽可能的保证内存垃圾回收花费的时间不超过设定的值(但是,并不是越小越好,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的,如果设置的值太小,将会导致频繁GC,这样虽然GC停顿时间下来了,但是吞吐量也下来了)。
“ -XX:GCTimeRatio”参数的值是一个大于0且小于100的整数,也就是垃圾收集时间占总时间的比率,默认值是99,就是允许最大1%(即1/(1+99))的垃圾收集时间。
“-XX:UseAdaptiveSizePolicy”参数是一个开发,如果这个参数打开之后,虚拟机会根据当前系统运行情况收集监控信息,动态调整新生代的比例、老年大大小等细节参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略。
Parallel Old收集器
特性:Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
应用场景:在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。
这个收集器是在JDK 1.6中才开始提供的,在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态。原因是,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old收集器外别无选择(Parallel Scavenge收集器无法与CMS收集器配合工作)。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS的组合“给力”。直到Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的应用组合
应用场景:在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。
这个收集器是在JDK 1.6中才开始提供的,在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态。原因是,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old收集器外别无选择(Parallel Scavenge收集器无法与CMS收集器配合工作)。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS的组合“给力”。直到Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的应用组合
CMS收集器
特性:CMS收集器是基于“标记—清除”算法实现,目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重 视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常 符合这类应用的需求。
优点::并发收集、低停 顿
分为4步骤:初始标记(CMS initial mark) 初始标记仅仅只是 标记一下GC Roots能直接关联到的对象,速度很快
并发标记(CMS concurrent mark) 并发标记阶段就是进行GC RootsTracing 的过程
重新标记(CMS remark) 修正并发标记期间因用户程序继续运作而导致标记产生变 动的那一部分对象的标记记 录, 这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远 比并发标记的时间短。
并发清除(CMS concurrent sweep)并发清除阶段会清除你标记的对象。
缺点:1)CMS收集器无法处理浮动垃圾(什么是浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴 随程序运行自然就还会有 新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法 在当次收集中处理掉它 们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃 圾)
2)CMS是一款基于“标记—清除”算法实现的收集 会产生空间碎片过多 将会给分配大对象带来很多麻烦 往往会出现老年代还 有 很大空间剩余,但是无法找到足够大的连续空间来分配当前对象
3)CMS对cpu资源非常敏感(面向并发设计的程序都对CPU资源比较敏感在并发阶段,它虽然不会导致用户线程停顿,但是会因 为占用了一部分线程(或者说CPU资 源)而导致应用程序变慢,总吞吐量会降低。CMS默认启动 的回收线程数是(CPU数量 +3)/4,也)
优点::并发收集、低停 顿
分为4步骤:初始标记(CMS initial mark) 初始标记仅仅只是 标记一下GC Roots能直接关联到的对象,速度很快
并发标记(CMS concurrent mark) 并发标记阶段就是进行GC RootsTracing 的过程
重新标记(CMS remark) 修正并发标记期间因用户程序继续运作而导致标记产生变 动的那一部分对象的标记记 录, 这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远 比并发标记的时间短。
并发清除(CMS concurrent sweep)并发清除阶段会清除你标记的对象。
缺点:1)CMS收集器无法处理浮动垃圾(什么是浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴 随程序运行自然就还会有 新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法 在当次收集中处理掉它 们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃 圾)
2)CMS是一款基于“标记—清除”算法实现的收集 会产生空间碎片过多 将会给分配大对象带来很多麻烦 往往会出现老年代还 有 很大空间剩余,但是无法找到足够大的连续空间来分配当前对象
3)CMS对cpu资源非常敏感(面向并发设计的程序都对CPU资源比较敏感在并发阶段,它虽然不会导致用户线程停顿,但是会因 为占用了一部分线程(或者说CPU资 源)而导致应用程序变慢,总吞吐量会降低。CMS默认启动 的回收线程数是(CPU数量 +3)/4,也)
G1收集器
概述:
优点:1)并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者 CPU核心)来缩短Stop-The-World 停顿的 时间,部分其他收集器原本需要停顿Java线程执行的 GC动作,G1收集器仍然可以通过并发的方式让Jav a程序继续行。
2)分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其 他收集器配合就能独立管理整个GC堆 但它能够采用不同的方式去处理新创建的对象和已 经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效 果。
3)空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实 现的收集器,从局部(两个Region 之间)上来看是基于“复制”算法实现的,但无论如何,这 两种算法都意味着G1运作期间不会产生内存空间碎片, 收集后能提供规整的可用内存。这种 特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而 提前触发下一 次GC。
4)可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关 注点,但G1除了追求低停顿外,还能 建立可预测的停顿时间模型,能让使用者明确指定在一 个长度为M毫秒的时间片段内,消耗在垃圾收集上的 时间不得超过N毫秒,这几乎已经是实 时Java(RTSJ)的垃圾收集器的特征了。
如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下几个步骤:
初始标记(Initial Marking)
并发标记(Concurrent Marking)
最终标记(Final Marking)
筛选回收(Live Data Counting and Evacuation)
开发人员仅仅需要声明以下参数即可:
-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200
其中-XX:+UseG1GC为开启G1垃圾收集器,-Xmx32g 设计堆内存的最大内存为32G,-XX:MaxGCPauseMillis=200设置GC的最大暂停时间为200ms。如果我们需要调优,在内存大小一定的情况下,我们只需要修改最大暂停时间即可。
其次,G1将新生代,老年代的物理空间划分取消了。取而代之的是,G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。
在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
PS:在java 8中,持久代也移动到了普通的堆内存空间中,改为元空间。
优点:1)并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者 CPU核心)来缩短Stop-The-World 停顿的 时间,部分其他收集器原本需要停顿Java线程执行的 GC动作,G1收集器仍然可以通过并发的方式让Jav a程序继续行。
2)分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其 他收集器配合就能独立管理整个GC堆 但它能够采用不同的方式去处理新创建的对象和已 经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效 果。
3)空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实 现的收集器,从局部(两个Region 之间)上来看是基于“复制”算法实现的,但无论如何,这 两种算法都意味着G1运作期间不会产生内存空间碎片, 收集后能提供规整的可用内存。这种 特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而 提前触发下一 次GC。
4)可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关 注点,但G1除了追求低停顿外,还能 建立可预测的停顿时间模型,能让使用者明确指定在一 个长度为M毫秒的时间片段内,消耗在垃圾收集上的 时间不得超过N毫秒,这几乎已经是实 时Java(RTSJ)的垃圾收集器的特征了。
如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下几个步骤:
初始标记(Initial Marking)
并发标记(Concurrent Marking)
最终标记(Final Marking)
筛选回收(Live Data Counting and Evacuation)
开发人员仅仅需要声明以下参数即可:
-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200
其中-XX:+UseG1GC为开启G1垃圾收集器,-Xmx32g 设计堆内存的最大内存为32G,-XX:MaxGCPauseMillis=200设置GC的最大暂停时间为200ms。如果我们需要调优,在内存大小一定的情况下,我们只需要修改最大暂停时间即可。
其次,G1将新生代,老年代的物理空间划分取消了。取而代之的是,G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。
在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
PS:在java 8中,持久代也移动到了普通的堆内存空间中,改为元空间。
总结jvm的问题:1.jvm是什么时候触发?触发条件是什么
(1)程序调用System.gc时可以触发,也不是立即触发,只是发了个通知要触发,时机由jvm 自己把握
(2)系统自身来决定GC触发的时机(根据Eden区和From Space区的内存大小来决定。当内 存大小不足时,则会启动GC线程并停止应用线程)
GC又分为 minor GC 和 Full GC (也称为 Major GC )
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
a.调用System.gc时,系统建议执行Full GC,但是不必然执行
b.老年代空间不足
c.方法区空间不足
d.通过Minor GC后进入老年代的平均大小大于老年代的可用内存
e.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
2.老年代青年代
3.G1垃圾收集器
(1)程序调用System.gc时可以触发,也不是立即触发,只是发了个通知要触发,时机由jvm 自己把握
(2)系统自身来决定GC触发的时机(根据Eden区和From Space区的内存大小来决定。当内 存大小不足时,则会启动GC线程并停止应用线程)
GC又分为 minor GC 和 Full GC (也称为 Major GC )
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
a.调用System.gc时,系统建议执行Full GC,但是不必然执行
b.老年代空间不足
c.方法区空间不足
d.通过Minor GC后进入老年代的平均大小大于老年代的可用内存
e.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
2.老年代青年代
3.G1垃圾收集器
子主题
JVM调优
JVM调优的最终目的是减少 stop-the-word的停顿 或者停顿时间
分支主题
子主题
子主题
子主题
Java线程
守护线程
守护线程是在进程运行时提供某种后台服务的线程,比如垃圾回收(GC)线程。
使用Callable和FutureTask创建线程
使用Callable和FutureTask创建线程
callable接口
用于获取线程异步结果
1.不能作为Thread的targe属性类型来使用,因为callable和runnable没有任何继承关系
1.不能作为Thread的targe属性类型来使用,因为callable和runnable没有任何继承关系
RunnableFuture接口
1.可以作为callable和Thread的搭桥接口来使用,这个接口实现了两个目标 1.获取异步执行结果,2.可作为Thread的target来使用调用线程,因为runnableFuture接口 继承了runnable接口(可作为Thread的targe来执行)和future(获取异步结果)接口
Future接口
1.能够取消异步任务,获取异步结果,判断异步任务是否完成
FutureTask类
1.是future的实现类,提供了异步任务的具体操作实现
2.实现了Runnable接口 或者更加准确地说,FutureTask类实现了RunnableFuture接口。所以说,FutureTask类才是真正的在Thread与Callable之间搭桥的类。
2.实现了Runnable接口 或者更加准确地说,FutureTask类实现了RunnableFuture接口。所以说,FutureTask类才是真正的在Thread与Callable之间搭桥的类。
通过线程池创建线程
1.为什么要用线程池,因为Thread创建的线程执行后都被销毁了,还要频繁的创建销毁线程,资源耗费高 线程得不到复用
通过Executors工厂类创建一个线程池,一个简单的示例如下:
private static ExecutorService pool = Executors.newFixedThreadPool(4);
ExecutorService是Java提供的一个线程池接口。ExecutorService实例负责对池中的线程进行管理和调度,并且可以有效控制最大并发线程数,提高系统资源的使用率,同时提供定时执行、定频执行、单线程、并发数控制等功能。
ExecutorService线程池的execute(...)与submit(...)方法的区别如下。
1.submit()有返回值,而execute()没有。 2.接受参数不一样 submit 可以接收runnable,callable, executesubmit()可以接收两种入参:无返回值的Runnable类型的target执行目标实例和有返回值的Callable类型的target执行目标实例。而execute()仅仅接收无返回值的target执行目标实例,或者无返回值的Thread实例。
通过Executors工厂类创建一个线程池,一个简单的示例如下:
private static ExecutorService pool = Executors.newFixedThreadPool(4);
ExecutorService是Java提供的一个线程池接口。ExecutorService实例负责对池中的线程进行管理和调度,并且可以有效控制最大并发线程数,提高系统资源的使用率,同时提供定时执行、定频执行、单线程、并发数控制等功能。
ExecutorService线程池的execute(...)与submit(...)方法的区别如下。
1.submit()有返回值,而execute()没有。 2.接受参数不一样 submit 可以接收runnable,callable, executesubmit()可以接收两种入参:无返回值的Runnable类型的target执行目标实例和有返回值的Callable类型的target执行目标实例。而execute()仅仅接收无返回值的target执行目标实例,或者无返回值的Thread实例。
线程的基本操作
线程的优先级
1.在Thread类中 int Priority 设置线程优先级 1-10 值越大 获取cpu时间片的几率就越高
线程的6种状态
在Thread.State定义的6种状态中,有4种是比较常见的状态,它们是:NEW(新建)状态、RUNNABLE(可执行)状态、TERMINATED(终止)状态、TIMED_WAITING(限时等待)状态。
线程的interrupt操作
线程的stop 不建议使用 不管你线程在什么状态 在干什么 他立马终止 就类似于强制关机 非常不友好 不安全
1.如果线程被Object.wait()、Thread.join()和Thread.sleep()三种方法之一阻塞,此时调用该线程的interrupt()方法,该线程将抛出一个InterruptedException中断异常
2.如果此线程正处于运行之中,线程就不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。所以,程序可以在适当的位置通过调用isInterrupted()方法来查看自己是否被中断,并执行退出操作。
https://blog.csdn.net/a837199685/article/details/55846746
1.如果线程被Object.wait()、Thread.join()和Thread.sleep()三种方法之一阻塞,此时调用该线程的interrupt()方法,该线程将抛出一个InterruptedException中断异常
2.如果此线程正处于运行之中,线程就不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。所以,程序可以在适当的位置通过调用isInterrupted()方法来查看自己是否被中断,并执行退出操作。
https://blog.csdn.net/a837199685/article/details/55846746
线程的join操作(合并)
假设有两个线程A和B。现在线程A在执行过程中对另一个线程B的执行有依赖,具体的依赖为:线程A需要将线程B的执行流程合并到自己的执行流程中(至少表面如此),这就是线程合并,被动方线程B可以叫作被合并线程
线程的yield操作(让步)
让目前正在执行的线程放弃当前的执行,处于让步状态的JVM层面的线程状态仍然是RUNNABLE状态,只是暂停不会阻塞
线程的daemon操作(守护线程)
1.守护线程也称为后台线程,专门指在程序进程运行过程中,在后台提供某种通用服务的线程GC就是守护线程
守护线程存在被JVM强行终止的风险,所以在守护线程中尽量不去访问系统资源,如文件句柄、数据库连接等。守护线程被强行终止时,可能会引发系统资源操作不负责任的中断,从而导致资源不可逆的损坏。
守护线程存在被JVM强行终止的风险,所以在守护线程中尽量不去访问系统资源,如文件句柄、数据库连接等。守护线程被强行终止时,可能会引发系统资源操作不负责任的中断,从而导致资源不可逆的损坏。
ThreadLocal原理与实战
在Java的多线程并发执行过程中,为了保证多个线程对变量的安全访问,可以将变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立值,不会出现一个线程读取变量时被另一个线程修改的现象。ThreadLocal类通常被翻译为“线程本地变量”类或者“线程局部变量”类。
优点
线程隔离,跨函数传数据
线程隔离
应用场景的典型应用为“数据库连接独享
跨函数传数据
传递用户id session httprequest 等
内部结构
jdk1.8之前
ThreadLocal内部是一个map,每一个线程实例作为key value为你当前线程绑定的值
1.8之后
新的Key为ThreadLocal实例。
区别
(1)拥有者发生了变化:新版本的ThreadLocalMap拥有者为Thread,早期版本的ThreadLocalMap拥有者为ThreadLocal。
(2)Key发生了变化:新版本的Key为ThreadLocal实例,早期版本的Key为Thread实例。与早期版本的ThreadLocalMap实现相比,新版本的主要优势为:
(1)每个ThreadLocalMap存储的“Key-Value对”数量变少。早期版本的“Key-Value对”数量与线程个数强关联,若线程数量多,则ThreadLocalMap存储的“Key-Value对”数量也多。新版本的ThreadLocalMap的Key为ThreadLocal实例,多线程情况下ThreadLocal实例比线程数少。
(2)早期版本ThreadLocalMap的拥有者为ThreadLocal,在Thread(线程)实例销毁后,ThreadLocalMap还是存在的;新版本的ThreadLocalMap的拥有者为Thread,现在当Thread实例销毁后,ThreadLocalMap也会随之销毁,在一定程度上能减少内存的消耗。
(2)Key发生了变化:新版本的Key为ThreadLocal实例,早期版本的Key为Thread实例。与早期版本的ThreadLocalMap实现相比,新版本的主要优势为:
(1)每个ThreadLocalMap存储的“Key-Value对”数量变少。早期版本的“Key-Value对”数量与线程个数强关联,若线程数量多,则ThreadLocalMap存储的“Key-Value对”数量也多。新版本的ThreadLocalMap的Key为ThreadLocal实例,多线程情况下ThreadLocal实例比线程数少。
(2)早期版本ThreadLocalMap的拥有者为ThreadLocal,在Thread(线程)实例销毁后,ThreadLocalMap还是存在的;新版本的ThreadLocalMap的拥有者为Thread,现在当Thread实例销毁后,ThreadLocalMap也会随之销毁,在一定程度上能减少内存的消耗。
使用原则
1.尽量使用private static final修饰ThreadLocal实例。使用private与final修饰符主要是为了尽可能不让他人修改、变更ThreadLocal变量的引用,使用static修饰符主要是为了确保ThreadLocal实例的全局唯一。
2.使用完之后要remove() 避免内存泄漏
2.使用完之后要remove() 避免内存泄漏
Java内置锁
线程安全问题
i++ 自增是线程不安全的
因为:一个自增运算符是一个复合操作,至少包括三个JVM指令:“内存取值”“寄存器增加1”和“存值到内存”。这三个指令在JVM内部是独立进行的,中间完全可能会出现多个线程并发进行。
比如在amount=100时,假设有三个线程同一时间读取amount值,读到的都是100,增加1后结果为101,三个线程都将结果存入amount的内存,amount的结果是101,而不是103。J20181211110408645XQB
“内存取值”“寄存器增加1”和“存值到内存”这三个JVM指令本身是不可再分的,它们都具备原子性,是线程安全的,也叫原子操作。但是,两个或者两个以上的原子操作(也就是线程)合在一起进行操作就不再具备原子性了。比如先读后写,就有可能在读之后,其实这个变量被修改了,出现读和写数据不一致的情况。
因为:一个自增运算符是一个复合操作,至少包括三个JVM指令:“内存取值”“寄存器增加1”和“存值到内存”。这三个指令在JVM内部是独立进行的,中间完全可能会出现多个线程并发进行。
比如在amount=100时,假设有三个线程同一时间读取amount值,读到的都是100,增加1后结果为101,三个线程都将结果存入amount的内存,amount的结果是101,而不是103。J20181211110408645XQB
“内存取值”“寄存器增加1”和“存值到内存”这三个JVM指令本身是不可再分的,它们都具备原子性,是线程安全的,也叫原子操作。但是,两个或者两个以上的原子操作(也就是线程)合在一起进行操作就不再具备原子性了。比如先读后写,就有可能在读之后,其实这个变量被修改了,出现读和写数据不一致的情况。
synchronized关键字
java每个对象都有一把锁 称之为内置锁,调用相当于获取syncObject的内置锁
例如:i++的问题 就可以使用该关键字 因为i++多线程的话属于临界区 多个线程访问临界区回出现线程安全问题 使用该关键字可对该方法 代码块加锁 每次只要一个线程来操作 其他线程都得等到当前线程操作完成之后才能进入
synchronized修饰不加static的方法,锁是加在单个对象上,不同的对象没有竞争关系;修饰加了static的方法,锁是加载类上,这个类所有的对象竞争一把锁.
执行过程:偏向锁是在没有发生锁争用的情况下使用的;一旦有了第二个线程争用锁,偏向锁就会升级为轻量级锁;如果锁争用很激烈,轻量级锁的CAS自旋到达阈值后(10次),轻量级锁就会升级为重量级锁
例如:i++的问题 就可以使用该关键字 因为i++多线程的话属于临界区 多个线程访问临界区回出现线程安全问题 使用该关键字可对该方法 代码块加锁 每次只要一个线程来操作 其他线程都得等到当前线程操作完成之后才能进入
synchronized修饰不加static的方法,锁是加在单个对象上,不同的对象没有竞争关系;修饰加了static的方法,锁是加载类上,这个类所有的对象竞争一把锁.
执行过程:偏向锁是在没有发生锁争用的情况下使用的;一旦有了第二个线程争用锁,偏向锁就会升级为轻量级锁;如果锁争用很激烈,轻量级锁的CAS自旋到达阈值后(10次),轻量级锁就会升级为重量级锁
Java对象结构与内置锁
Java对象结构
Java对象包括三部分 对象头,对象体,对齐字节
内置锁
无锁
ava对象刚创建时还没有任何线程来竞争,说明该对象处于无锁状态(无线程竞争它),这时偏向锁标识位是0,锁状态是01
偏向锁
一段同步代码块一直被一个线程访问,表示内置锁偏爱这个线程 偏向锁状态的Mark Word会记录内置锁自己偏爱的线程I,不需要做人核检查和切换(内置锁回记录该线程的Id) 在线程竞争不激烈的时候 偏向锁的效率很高
为什么效率高?:线程获取锁时判断一下线程ID和标志位,就可以直接进入同步块,连CAS操作都不需要,这样就省去了大量有关锁申请的操作,从而也就提升了程序的性能。
为什么效率高?:线程获取锁时判断一下线程ID和标志位,就可以直接进入同步块,连CAS操作都不需要,这样就省去了大量有关锁申请的操作,从而也就提升了程序的性能。
轻量级锁
当有两个线程开始竞争这个锁对象时,情况就发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象,锁对象的Mark Word就指向哪个线程的栈帧中的锁记
偏向锁升级为轻量级锁:当锁处于偏向锁,又被另一个线程企图抢占时,偏向锁就会升级为轻量级锁。企图抢占的线程会通过自旋的形式尝试获取锁,不会阻塞抢锁线程,以便提高性能。
线程之间的抢锁 如果一个线程还未抢到锁 则该线程自旋 不是阻塞状态 只是需要等一等,等持有锁的线程释放锁后即可立即获取锁,这样就避免了用户线程和内核切换的消耗。
如果持有锁的线程执行的时间超过自旋等待的最大时间仍没有释放锁 就会进入阻塞状态
自旋默认为10次
偏向锁升级为轻量级锁:当锁处于偏向锁,又被另一个线程企图抢占时,偏向锁就会升级为轻量级锁。企图抢占的线程会通过自旋的形式尝试获取锁,不会阻塞抢锁线程,以便提高性能。
线程之间的抢锁 如果一个线程还未抢到锁 则该线程自旋 不是阻塞状态 只是需要等一等,等持有锁的线程释放锁后即可立即获取锁,这样就避免了用户线程和内核切换的消耗。
如果持有锁的线程执行的时间超过自旋等待的最大时间仍没有释放锁 就会进入阻塞状态
自旋默认为10次
重量级锁
重量级锁会让其他申请的线程之间进入阻塞,性能降低。重量级锁也叫同步锁,这个锁对象MarkWord再次发生变化,会指向一个监视器对象,该监视器对象用集合的形式来登记和管理排队的线程
线程之间的通信
wait()等待
wait()方法的核心原理对象的wait()方法的核心原理大致如下:
(1)当线程调用了locko(某个同步锁对象)的wait()方法后,JVM会将当前线程加入locko监视器的WaitSet(等待集),等待被其他线程唤醒。
(2)当前线程会释放locko对象监视器的Owner权利,让其他线程可以抢夺locko对象的监视器。
(3)让当前线程等待,其状态变成WAITING。在线程调用了同步对象locko的wait()方法之后,同步对象locko的监视器内部状态大致如图2-15所示。
为什么Wait()后的线程状态为WAITING呢?此时WaitThread处于locko的监视器的WaitSet(等待集)中,等待被唤醒。
(1)当线程调用了locko(某个同步锁对象)的wait()方法后,JVM会将当前线程加入locko监视器的WaitSet(等待集),等待被其他线程唤醒。
(2)当前线程会释放locko对象监视器的Owner权利,让其他线程可以抢夺locko对象的监视器。
(3)让当前线程等待,其状态变成WAITING。在线程调用了同步对象locko的wait()方法之后,同步对象locko的监视器内部状态大致如图2-15所示。
为什么Wait()后的线程状态为WAITING呢?此时WaitThread处于locko的监视器的WaitSet(等待集)中,等待被唤醒。
notify()通知
(1)当线程调用了locko(某个同步锁对象)的notify()方法后,JVM会唤醒locko监视器WaitSet中的第一条等待线程。
(2)当线程调用了locko的notifyAll()方法后,JVM会唤醒locko监视器WaitSet中的所有等待线程。
(3)等待线程被唤醒后,会从监视器的WaitSet移动到EntryList,线程具备了排队抢夺监视器Owner权利的资格,其状态从WAITING变成BLOCKED。
(4)EntryList中的线程抢夺到监视器的Owner权利之后,线程的状态从BLOCKED变成Runnable,具备重新执行的资格。在线程调用了同步对象locko的wait()或者notifyAll()方法之后,同步对象locko的监视器内部状态大致
(2)当线程调用了locko的notifyAll()方法后,JVM会唤醒locko监视器WaitSet中的所有等待线程。
(3)等待线程被唤醒后,会从监视器的WaitSet移动到EntryList,线程具备了排队抢夺监视器Owner权利的资格,其状态从WAITING变成BLOCKED。
(4)EntryList中的线程抢夺到监视器的Owner权利之后,线程的状态从BLOCKED变成Runnable,具备重新执行的资格。在线程调用了同步对象locko的wait()或者notifyAll()方法之后,同步对象locko的监视器内部状态大致
子主题
总结
wait()和notify方法一定要放到synchronized代码块中
为什么一定要放到synchronized代码块中呢? 是因为对象锁监视器 JVM会将当前线程移入监视器的WaitSet队列所以必须通过synchronized()方法成为对象锁的监视器的Owner notify唤醒线程的时候需要将waitset里面的线程加入到entryset中 所以也是需要对象锁监视器
为什么一定要放到synchronized代码块中呢? 是因为对象锁监视器 JVM会将当前线程移入监视器的WaitSet队列所以必须通过synchronized()方法成为对象锁的监视器的Owner notify唤醒线程的时候需要将waitset里面的线程加入到entryset中 所以也是需要对象锁监视器
java 线程的三种特性
原子性
可见性
概念
cpu处理的数据都是放到三级缓存(高速缓存) cpu计算完成后把数据回写到高速缓存中 高速缓存把数据回写到主内存中
由于每个线程可能会运行在不同的CPU内核中,因此每个线程拥有自己的高速缓存。同一份数据可能会被缓存到多个CPU内核中,在不同CPU内核中运行的线程看到同一个变量的缓存值就会不一样,就可能发生内存的可见性问题。
由于每个线程可能会运行在不同的CPU内核中,因此每个线程拥有自己的高速缓存。同一份数据可能会被缓存到多个CPU内核中,在不同CPU内核中运行的线程看到同一个变量的缓存值就会不一样,就可能发生内存的可见性问题。
解决内存的可见性问题方式
总线锁
缓存锁
volatile关键字
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
volatile不具备原子性
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
volatile不具备原子性
有序性
乐观锁,悲观锁
乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
悲观锁
悲观锁总是假设会发生最坏的情况,每次线程读取数据时,也会上锁。这样其他线程在读取数据时就会被阻塞,直到它拿到锁。传统的关系型数据库用到了很多悲观锁,比如行锁、表锁、读锁、写锁等。
存在的问题:1.多线程竞争加锁 线程上下文切换 影响性能|
2.当前线程获取到锁 其他线程则只能挂起等待
3.如果一个优先级高的线程等待一个优先级低的线程释放锁,就会导致线程的优先级倒置,从而引发性能风险。
存在的问题:1.多线程竞争加锁 线程上下文切换 影响性能|
2.当前线程获取到锁 其他线程则只能挂起等待
3.如果一个优先级高的线程等待一个优先级低的线程释放锁,就会导致线程的优先级倒置,从而引发性能风险。
死锁
死锁是指两个或者两个以上的线程因抢占锁造成的相互等待现象 ,常见的 AB-BA模式
解决:Java8中提供了ThreadMAXBean接口来监控线程的,包括一下两个死锁的线程方法
1.findDeadlocakedThreads用于检测由于抢占JUC显示锁,Java内置锁引起的死锁线程
2.findMonitorDeadlockedThreads 仅仅用于检测内置锁引起的死锁线程
3.监视到死锁后可中断线程
解决:Java8中提供了ThreadMAXBean接口来监控线程的,包括一下两个死锁的线程方法
1.findDeadlocakedThreads用于检测由于抢占JUC显示锁,Java内置锁引起的死锁线程
2.findMonitorDeadlockedThreads 仅仅用于检测内置锁引起的死锁线程
3.监视到死锁后可中断线程
非公平锁
java核心卷轴
Java基础知识
数据类型
Java基本数据类型分为 整型和浮点型
1.整型(int(4个字节) long(8个字节) byte(1个字节) short(2个字节)
2.浮点型 (double(8个字节) float(4个字节) )
1.整型(int(4个字节) long(8个字节) byte(1个字节) short(2个字节)
2.浮点型 (double(8个字节) float(4个字节) )
Java nio netty
IO主要的四种模型
同步阻塞IO
同步非阻塞IO
IO多路复用
异步IO
数据库
mysql
基础篇
sql分类
DDL
数据库定义语言,常用关键字有create,drop, alter
DML
数据操作语句,常用关键字 insert update delete select 只对表内数据操作
DCL
数据控制语句,定义了库,表,字段用户的访问权限和安全级别 关键字grant,revoke等
默认4个数据库
information_schema
存储数据库对象信息,用户表信息,列信息,权限信息,字符信息,分区信息
cluster
存储了系统集群信息
mysql
存储系统用户权限信息
test
系统自建测试库 任何人都可以使用
DDL语句
1.mysql -u 连接用户 -p 密码
2.show databases; 显示连接数据库
3.show tables; 显示所有表
4.use database; 选择数据库。
5.drop database dbname; 删除数据库
6.desc tablename \G; 查看表定义
7.show create table tableName \G; 查看建表语句
8.drop table tableNmae; 删表语句
9.alert等
10. ? 关键字; 可快速查询
2.show databases; 显示连接数据库
3.show tables; 显示所有表
4.use database; 选择数据库。
5.drop database dbname; 删除数据库
6.desc tablename \G; 查看表定义
7.show create table tableName \G; 查看建表语句
8.drop table tableNmae; 删表语句
9.alert等
10. ? 关键字; 可快速查询
DML中的各种操作
聚合函数
1.having和where的区别:having是对聚合后的结果进行条件的过滤,而where是在聚合前就过滤,尽可能先试用where先过滤 这样 结 果集减少 聚合效率提高
记录联合
将要两个表的数据按照条件查询 并且将两个sql的结果直接合并在一起 这个时候就要用到 union,union all
区别:union all 是把结果集直接合并在一起 而 union 是将union all结果集去重了一遍。
区别:union all 是把结果集直接合并在一起 而 union 是将union all结果集去重了一遍。
数据类型
INT类型
1.每个整型都有一个可选属性(无符号)如果你保存的值大于上限值 他的取值范围是 正常值的下限取0 上限取原值的2倍
如果一个列指定为zerofill类型 那这个列就是无符号列
如果一个列指定为zerofill类型 那这个列就是无符号列
注意事项
1.使用order by排序多列时 如果第一列没有相同值 则不会对第二列排序
2.在group by 中使用 with rollup 在所查询的分组之后增加一条记录,增加总和和统计数量
2.在group by 中使用 with rollup 在所查询的分组之后增加一条记录,增加总和和统计数量
数据库引擎
命令
show engines; 查看当前数据库支持的存储引擎
show variables like '%storage_engine%'; 查看你当前默认的数据库引擎
在创建表的时候可以使用ENGINE指定引擎 engine = Innodb default charset=gbk
show variables like '%storage_engine%'; 查看你当前默认的数据库引擎
在创建表的时候可以使用ENGINE指定引擎 engine = Innodb default charset=gbk
概述
支持的数据库引擎包括 myISAM InnoDB,BDB,NDB,CSV等.... 其中innoDB和BDB提供事务安全表,其他引擎都是非事务安全表
mysql 默认引擎5.5之前myISAM >=5.5之后默认的是InnoDB
mysql 默认引擎5.5之前myISAM >=5.5之后默认的是InnoDB
各个引擎
MyISAM
缺点:不支持事务表,也不支持外键
优点:访问速度快 如果以select或者insert为主的可以选择这个存储引擎
每个MyISAM存储3个文件文件名和表明相同 但是扩展名不一样
.frm (存储表定义)
.MYD (存储数据)
.MYI (存储索引)
数据库文件和索引文件的路径可以通过命令放置不同的位置平均分布io可以获得更快的速度
提供了check table来检查表的状态 并且用repair table可以修复损坏的表
有三种不通的存储方式:
1.静态固定长度表(默认存储格式):
优点:存储速度,容易缓存,出现故障容易恢复。
缺点:占用的空间比动态表多,静态表会按照列宽度 补空格,但是在应用访问的时候并不会得到这些空格, 在返回之前就已经将这些空格去除了
2.动态表:
优点:占用空间少
缺点:频繁的删除和更新会产生碎片 可以通过定期执行命令来改善性能
3.压缩表
优点:访问速度快 如果以select或者insert为主的可以选择这个存储引擎
每个MyISAM存储3个文件文件名和表明相同 但是扩展名不一样
.frm (存储表定义)
.MYD (存储数据)
.MYI (存储索引)
数据库文件和索引文件的路径可以通过命令放置不同的位置平均分布io可以获得更快的速度
提供了check table来检查表的状态 并且用repair table可以修复损坏的表
有三种不通的存储方式:
1.静态固定长度表(默认存储格式):
优点:存储速度,容易缓存,出现故障容易恢复。
缺点:占用的空间比动态表多,静态表会按照列宽度 补空格,但是在应用访问的时候并不会得到这些空格, 在返回之前就已经将这些空格去除了
2.动态表:
优点:占用空间少
缺点:频繁的删除和更新会产生碎片 可以通过定期执行命令来改善性能
3.压缩表
InnoDB
特点:1.自动增长列,如果列为自增,在插入0或者空值的时候 则实际上市插入自动增长后的值
2.支持外键 支持事务,行及锁定 更加注重数据的完整性和安全性
理解独立表空间和共享表空间
1.独立表空间:每个表都会生成以独立的文件方式来存储,每个表都一个.frm(表结构文件,仅存储了表的结构、元数据(meta),包括表结 构定义信息等)的描述文件,还有一个.ibd文件 其中这个文件包括了单独一个表的数据及索引内容,默认情 况下它的存储在mysql指定的目录下。
2.支持外键 支持事务,行及锁定 更加注重数据的完整性和安全性
理解独立表空间和共享表空间
1.独立表空间:每个表都会生成以独立的文件方式来存储,每个表都一个.frm(表结构文件,仅存储了表的结构、元数据(meta),包括表结 构定义信息等)的描述文件,还有一个.ibd文件 其中这个文件包括了单独一个表的数据及索引内容,默认情 况下它的存储在mysql指定的目录下。
数据结构与算法
B+Tree(平衡树)
二分查找法
1.例如我们查找52这个元素 开始时 low =0 high=10 mid=(low+high)/2 比较目标元素和中间元素48<52表示目标元素存在 这个目标在 这个序列的后半部分
2. 此时,low=原来的low+1=6,high=10,mid=(low + high) / 2 = 8。
3.同样的 再次比较 52<69 则目标存在 此时high=mid-1 也就是7 然后low=6不变 mid=(low + high) / 2 = 6。
4.比较新的中间元素和目标元素,52等于52。查找成功,返回该中间元素的索引6并退出算法。
如果查找不到的情况下
如果我们查询70这个记录 但是没有这个数据 到最后long high mid 都指向同一个索引 比较新的中间元素和目标元素,61小于70。这说明若目标元素存在,那么它一定在当前待查序列的后半部分。让low=mid + 1 = 8而high不变。
此时,low=8,high=7,low大于high说明待查序列已为空,也就说明查找不到目标元素。此时,返回-1并退出算法,表示查找不成功;
二分查找法的前提是 必须有序而且顺序(升序或者降序都可以)
2. 此时,low=原来的low+1=6,high=10,mid=(low + high) / 2 = 8。
3.同样的 再次比较 52<69 则目标存在 此时high=mid-1 也就是7 然后low=6不变 mid=(low + high) / 2 = 6。
4.比较新的中间元素和目标元素,52等于52。查找成功,返回该中间元素的索引6并退出算法。
如果查找不到的情况下
如果我们查询70这个记录 但是没有这个数据 到最后long high mid 都指向同一个索引 比较新的中间元素和目标元素,61小于70。这说明若目标元素存在,那么它一定在当前待查序列的后半部分。让low=mid + 1 = 8而high不变。
此时,low=8,high=7,low大于high说明待查序列已为空,也就说明查找不到目标元素。此时,返回-1并退出算法,表示查找不成功;
二分查找法的前提是 必须有序而且顺序(升序或者降序都可以)
二叉查找树和二叉平衡树
二叉查找树
1.本身是有序树;
2.树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2;
3.二叉树是由根结点、左子树、右子树这三部分递归地组合而成的 一把都是左侧节点比根节点小 右侧比根节点大
遍历二叉树递归规则
根结点 >> 左子树 >> 右子树,称为先序(根)遍历
左子树 >> 根结点 >> 右子树,称为中序(根)遍历
左子树 >> 右子树 >> 根结点,称为后序(根)遍历
约定好之后,只需要按照顺序递归地来就好了,就像找族谱一样。
例如:图上的树 想要查找5的元素 先找到根6>5 在找到3 3<5 在找右子树 5=5找到后返回
2.树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2;
3.二叉树是由根结点、左子树、右子树这三部分递归地组合而成的 一把都是左侧节点比根节点小 右侧比根节点大
遍历二叉树递归规则
根结点 >> 左子树 >> 右子树,称为先序(根)遍历
左子树 >> 根结点 >> 右子树,称为中序(根)遍历
左子树 >> 右子树 >> 根结点,称为后序(根)遍历
约定好之后,只需要按照顺序递归地来就好了,就像找族谱一样。
例如:图上的树 想要查找5的元素 先找到根6>5 在找到3 3<5 在找右子树 5=5找到后返回
二叉平衡树
定义:1. 是「二叉排序树」
2. 任何一个节点的左子树或者右子树都是「平衡二叉树」(左右高度差小于等于 1)
这个文章写得好https://blog.csdn.net/jarvan5/article/details/112428036
通过左旋和右旋来平衡树
2. 任何一个节点的左子树或者右子树都是「平衡二叉树」(左右高度差小于等于 1)
这个文章写得好https://blog.csdn.net/jarvan5/article/details/112428036
通过左旋和右旋来平衡树
通过左旋和右旋来平衡树:
左旋
左旋:
根结点:最新的根结点为原根结点的右孩子
左子树:左子树的根即为原来的根,左子树的左子树即为原来的左子树,左子树的 右子树为原来右子 树的左子树
右子树:右子树为原根结点右孩子的右子树
根结点:最新的根结点为原根结点的右孩子
左子树:左子树的根即为原来的根,左子树的左子树即为原来的左子树,左子树的 右子树为原来右子 树的左子树
右子树:右子树为原根结点右孩子的右子树
右旋
右旋(当rightHeight-leftHeight>1)
旋转后
根结点:最新的根结点为原根结点的左孩子
左子树:左子树为原根结点左孩子的左子树
右子树:右子树的根即为原来的根,右子树的右子树即为原来的右子树,右子树的左子树为原来左子树的右子树
旋转后
根结点:最新的根结点为原根结点的左孩子
左子树:左子树为原根结点左孩子的左子树
右子树:右子树的根即为原来的根,右子树的右子树即为原来的右子树,右子树的左子树为原来左子树的右子树
B+Tree
B树
平衡二叉树可是每个节点只存储一个键值和数据的。那说明什么?说明每个磁盘块仅仅存储一个键值和数据!那如果我们要存储海量的数据呢?可以想象到二叉树的节点将会非常多,高度也会极其高,我们查找数据时也会进行很多次磁盘 IO,我们查找数据的效率将会极低!
B树是:单个节点可以存储多个键值和数据的平衡树
1.图中的每个节点称为页,页就是我们上面说的磁盘块,在 MySQL 中数据读取的基本单位都是页,所以我们这里叫做页更符 合 MySQL 中索引的底层数据结构
2.从上图可以看出,B 树相对于平衡二叉树,每个节点存储了更多的键值(key)和数据(data),并且每个节点拥有更多的子 节点,子节点的个数一般称为阶,上述图中的 B 树为 3 阶 B 树,高度也会很低。
基于这个特性,B 树查找数据读取磁盘的次数将会很少,数据的查找效率也会比平衡二叉树高很多。
假如我们要查找 id=28 的用户信息,那么我们在上图 B 树中查找的流程如下:
先找到根节点也就是页 1,判断 28 在键值 17 和 35 之间,那么我们根据页 1 中的指针 p2 找到页 3。
将 28 和页 3 中的键值相比较,28 在 26 和 30 之间,我们根据页 3 中的指针 p2 找到页 8。
将 28 和页 8 中的键值相比较,发现有匹配的键值 28,键值 28 对应的用户信息为(28,bv)。
B树是:单个节点可以存储多个键值和数据的平衡树
1.图中的每个节点称为页,页就是我们上面说的磁盘块,在 MySQL 中数据读取的基本单位都是页,所以我们这里叫做页更符 合 MySQL 中索引的底层数据结构
2.从上图可以看出,B 树相对于平衡二叉树,每个节点存储了更多的键值(key)和数据(data),并且每个节点拥有更多的子 节点,子节点的个数一般称为阶,上述图中的 B 树为 3 阶 B 树,高度也会很低。
基于这个特性,B 树查找数据读取磁盘的次数将会很少,数据的查找效率也会比平衡二叉树高很多。
假如我们要查找 id=28 的用户信息,那么我们在上图 B 树中查找的流程如下:
先找到根节点也就是页 1,判断 28 在键值 17 和 35 之间,那么我们根据页 1 中的指针 p2 找到页 3。
将 28 和页 3 中的键值相比较,28 在 26 和 30 之间,我们根据页 3 中的指针 p2 找到页 8。
将 28 和页 8 中的键值相比较,发现有匹配的键值 28,键值 28 对应的用户信息为(28,bv)。
B+Tree
B树是对B+树的一种优化
1.B+ 树非叶子节点上是不存储数据的,仅存储键值(好处就是io次数减少,查询效率高),而 B 树节点中不仅存储键值,也会存储数据。之所以这么做是因为在数据库中页的大小是固定的,InnoDB 中页的默认大小是 16KB。
如果我们的 B+ 树一个节点可以存储 1000 个键值,那么 3 层 B+ 树可以存储 1000×1000×1000=10 亿个数据。
一般根节点是常驻内存的,所以一般我们查找 10 亿数据,只需要 2 次磁盘 IO。
2.因为 B+ 树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。
那么 B+ 树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而 B 树因为数据分散在各个节点,要实现这一点是很不容易的。有心的读者可能还发现上图 B+ 树中各个页之间是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。
3.在 InnoDB 中,我们通过数据页之间通过双向链表连接以及叶子节点中数据之间通过单向链表连接的方式可以找到表中所有的数据。
4.MyISAM 中的 B+ 树索引实现与 InnoDB 中的略有不同。在 MyISAM 中,B+ 树索引的叶子节点并不存储数据,而是存储数据的文件地址。
5.B+ 树索引就是 InnoDB 中 B+ 树索引真正的实现方式,准确的说应该是聚集索引(聚集索引和非聚集索引下面会讲到)。
mysql innodb最小存储单元是页 每页最大小是16kb
1.B+ 树非叶子节点上是不存储数据的,仅存储键值(好处就是io次数减少,查询效率高),而 B 树节点中不仅存储键值,也会存储数据。之所以这么做是因为在数据库中页的大小是固定的,InnoDB 中页的默认大小是 16KB。
如果我们的 B+ 树一个节点可以存储 1000 个键值,那么 3 层 B+ 树可以存储 1000×1000×1000=10 亿个数据。
一般根节点是常驻内存的,所以一般我们查找 10 亿数据,只需要 2 次磁盘 IO。
2.因为 B+ 树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。
那么 B+ 树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而 B 树因为数据分散在各个节点,要实现这一点是很不容易的。有心的读者可能还发现上图 B+ 树中各个页之间是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。
3.在 InnoDB 中,我们通过数据页之间通过双向链表连接以及叶子节点中数据之间通过单向链表连接的方式可以找到表中所有的数据。
4.MyISAM 中的 B+ 树索引实现与 InnoDB 中的略有不同。在 MyISAM 中,B+ 树索引的叶子节点并不存储数据,而是存储数据的文件地址。
5.B+ 树索引就是 InnoDB 中 B+ 树索引真正的实现方式,准确的说应该是聚集索引(聚集索引和非聚集索引下面会讲到)。
mysql innodb最小存储单元是页 每页最大小是16kb
InnoDB聚集索引和非聚集索引
聚集索引
1.聚集索引(聚簇索引):以 InnoDB 作为存储引擎的表,表中的数据都会有一个主键,即使你不创建主键,系统也会帮你创建一个隐 的主键。这是因为 InnoDB 是把数据存放在 B+ 树中的,而 B+ 树的键值就是主键,在 B+ 树的叶子节点中, 存储了表中所有的数据。
这种以主键作为 B+ 树索引的键值而构建的 B+ 树索引,我们称之为聚集索引。
利用聚集索引查找数据:现在假设我们要查找 id>=18 并且 id<40 的用户数据。对应的 sql 语句为:select * from user where id>=1 8 and id <40
其中 id 为主键,具体的查找过程如下:
①一般根节点都是常驻内存的,也就是说页 1 已经在内存中了,此时不需要到磁盘中读取数据,直接从内存中读取即可。
从内存中读取到页 1,要查找这个 id>=18 and id <40 或者范围值,我们首先需要找到 id=18 的键值。
从页 1 中我们可以找到键值 18,此时我们需要根据指针 p2,定位到页 3。
②要从页 3 中查找数据,我们就需要拿着 p2 指针去磁盘中进行读取页 3。
从磁盘中读取页 3 后将页 3 放入内存中,然后进行查找,我们可以找到键值 18,然后再拿到页 3 中的指针 p1,定位到页 8。
③同样的页 8 页不在内存中,我们需要再去磁盘中将页 8 读取到内存中。
将页 8 读取到内存中后。因为页中的数据是链表进行连接的,而且键值是按照顺序存放的,此时可以根据二分查找法定位到键值 18。
此时因为已经到数据页了,此时我们已经找到一条满足条件的数据了,就是键值 18 对应的数据。
因为是范围查找,而且此时所有的数据又都存在叶子节点,并且是有序排列的,那么我们就可以对页 8 中的键值依次进行遍历查找并匹配满足条件的数据。
我们可以一直找到键值为 22 的数据,然后页 8 中就没有数据了,此时我们需要拿着页 8 中的 p 指针去读取页 9 中的数据。
④因为页 9 不在内存中,就又会加载页 9 到内存中,并通过和页 8 中一样的方式进行数据的查找,直到将页 12 加载到内存中,发现 41 大于 40,此时不满足条件。那么查找到此终止。
最终我们找到满足条件的所有数据,总共 12 条记录:
(18,kl), (19,kl), (22,hj), (24,io), (25,vg) , (29,jk), (31,jk) , (33,rt) , (34,ty) , (35,yu) , (37,rt) , (39,rt) 。
下面看下具体的查找流程图
这种以主键作为 B+ 树索引的键值而构建的 B+ 树索引,我们称之为聚集索引。
利用聚集索引查找数据:现在假设我们要查找 id>=18 并且 id<40 的用户数据。对应的 sql 语句为:select * from user where id>=1 8 and id <40
其中 id 为主键,具体的查找过程如下:
①一般根节点都是常驻内存的,也就是说页 1 已经在内存中了,此时不需要到磁盘中读取数据,直接从内存中读取即可。
从内存中读取到页 1,要查找这个 id>=18 and id <40 或者范围值,我们首先需要找到 id=18 的键值。
从页 1 中我们可以找到键值 18,此时我们需要根据指针 p2,定位到页 3。
②要从页 3 中查找数据,我们就需要拿着 p2 指针去磁盘中进行读取页 3。
从磁盘中读取页 3 后将页 3 放入内存中,然后进行查找,我们可以找到键值 18,然后再拿到页 3 中的指针 p1,定位到页 8。
③同样的页 8 页不在内存中,我们需要再去磁盘中将页 8 读取到内存中。
将页 8 读取到内存中后。因为页中的数据是链表进行连接的,而且键值是按照顺序存放的,此时可以根据二分查找法定位到键值 18。
此时因为已经到数据页了,此时我们已经找到一条满足条件的数据了,就是键值 18 对应的数据。
因为是范围查找,而且此时所有的数据又都存在叶子节点,并且是有序排列的,那么我们就可以对页 8 中的键值依次进行遍历查找并匹配满足条件的数据。
我们可以一直找到键值为 22 的数据,然后页 8 中就没有数据了,此时我们需要拿着页 8 中的 p 指针去读取页 9 中的数据。
④因为页 9 不在内存中,就又会加载页 9 到内存中,并通过和页 8 中一样的方式进行数据的查找,直到将页 12 加载到内存中,发现 41 大于 40,此时不满足条件。那么查找到此终止。
最终我们找到满足条件的所有数据,总共 12 条记录:
(18,kl), (19,kl), (22,hj), (24,io), (25,vg) , (29,jk), (31,jk) , (33,rt) , (34,ty) , (35,yu) , (37,rt) , (39,rt) 。
下面看下具体的查找流程图
非聚集索引
非聚集索引(非聚簇索引):以主键以外的列值作为键值构建的 B+ 树索引,我们称之为非聚集索引。
非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键, 想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们 称为回表。
明白了聚集索引和非聚集索引的定义,我们应该明白这样一句话:数据即索引,索引即数据。
保存了主键并且保存了指向聚集索引的主键
非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键, 想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们 称为回表。
明白了聚集索引和非聚集索引的定义,我们应该明白这样一句话:数据即索引,索引即数据。
保存了主键并且保存了指向聚集索引的主键
哈希索引
哈希索引就是采用一定的hash算法,把键值换算成新的hash值,检索时不需要类似的B+那样从根节点到叶子节点逐级查找,只需要一次hash算法就可以立即定位到相应位置,速度非常快。
子主题
索引
概念
常用的存储引擎最每个表最多支持16个索引,总索引长度为256个字节
myISAM和innoDB 默认创建的都是B+tree索引
myISAM和innoDB 默认创建的都是B+tree索引
设计原则
1.最适合的做引列是 where 后面的列 而不是查询中的列
2.唯一索引,索引列基数越大 效果越好 例如:生日列每个人的生日不同很容易区分开 但是性别对于唯一所以就不好了
3.使用段索引 例如varchar(200) 前10到20个字符唯一 就可以对
4.索引不是越多越好,一个表中有大量的索引 会占用空间 而且insert delete update会影响性能 因为表数据更改时索引也会调整
5.尽量避免对update操作很多的表 进行增加很多索引
2.唯一索引,索引列基数越大 效果越好 例如:生日列每个人的生日不同很容易区分开 但是性别对于唯一所以就不好了
3.使用段索引 例如varchar(200) 前10到20个字符唯一 就可以对
4.索引不是越多越好,一个表中有大量的索引 会占用空间 而且insert delete update会影响性能 因为表数据更改时索引也会调整
5.尽量避免对update操作很多的表 进行增加很多索引
索引类型
普通索引和唯一索引
普通索引允许空值和重复值
唯一索引允许空值
主键索引也是唯一索引 不允许空值和重复值
普通索引:index()
唯一索引:unique index()
单列索引 key 名称 (字段(20));
组合索引:index 名称(字段1,字段2);遵从左前缀规则 例如 组合索引中字段 a,b,c 可以使用的是 abc ab 不可使用的是b bc
全文索引:fulltext index name();
唯一索引允许空值
主键索引也是唯一索引 不允许空值和重复值
普通索引:index()
唯一索引:unique index()
单列索引 key 名称 (字段(20));
组合索引:index 名称(字段1,字段2);遵从左前缀规则 例如 组合索引中字段 a,b,c 可以使用的是 abc ab 不可使用的是b bc
全文索引:fulltext index name();
全文索引
只能在char varchat text类型上创建 只有myisam支持 对整个列支持 不支持前缀
索引命名
1.可以使用EXPLAIN查看索引是否在使用
2.show index from table查看索引
2.show index from table查看索引
不命中索引
1.以%开口的like查询不能使用B+Tree索引
2.数据类型出现隐式转换的时候不会命中索引 例如:name是字符串类型的 where name=1是不会命中索引的 只有name='1'才可以
3.复合索引不满足左前缀条件下 不能命中索引 例如:索引中(a,b,c) b,或者bc都是不能命中索引
4.mysql使用索引比全表扫描更慢的时候 不会使用索引(索引优化器自行选择)
5.or条件 如果or前面的条件带有索引后边的没有索引 则会全表扫描 不使用索引。
6.不要在列中运算
7.不使用NOT IN <>操作 NOT EXISTS代替NOT IN,id<>3则可使用id>3 or id<3来代替。
2.数据类型出现隐式转换的时候不会命中索引 例如:name是字符串类型的 where name=1是不会命中索引的 只有name='1'才可以
3.复合索引不满足左前缀条件下 不能命中索引 例如:索引中(a,b,c) b,或者bc都是不能命中索引
4.mysql使用索引比全表扫描更慢的时候 不会使用索引(索引优化器自行选择)
5.or条件 如果or前面的条件带有索引后边的没有索引 则会全表扫描 不使用索引。
6.不要在列中运算
7.不使用NOT IN <>操作 NOT EXISTS代替NOT IN,id<>3则可使用id>3 or id<3来代替。
优化
sql优化
1.show status 了解sql的执行效率 主要看下面的参数:
com_select 执行select查询操作每次加1
com_update 执行update次数
com_inert 执行insert次数 批量insert只加1
com_delete 执行删除次数
这个命令可以查看所有的状态(事务状态,数据库状态)并且根据状态可以查看出是依查询为主还是以更新为主
2.通过慢查询查询那些执行效率低的sql 但是不能定位问题 需要使用show processlist 查看当前进城 线程状态,是否锁表等
3.通过EXPIAIN sql语句分析执行计划
4.通过show profile分析sql
com_select 执行select查询操作每次加1
com_update 执行update次数
com_inert 执行insert次数 批量insert只加1
com_delete 执行删除次数
这个命令可以查看所有的状态(事务状态,数据库状态)并且根据状态可以查看出是依查询为主还是以更新为主
2.通过慢查询查询那些执行效率低的sql 但是不能定位问题 需要使用show processlist 查看当前进城 线程状态,是否锁表等
3.通过EXPIAIN sql语句分析执行计划
4.通过show profile分析sql
子主题
redis
Java23种设计模式
单例模式
概念
系统中只存在一个实例 同时提供一个对外的访问接口(spirng中的bean都是单例模式)
饿汉式
懒汉式
原型模式
以原型实例创建副本实例,
目的:从原型实例clone出来新的实例,对于那些非常复杂的初始化过程的对象或者消耗大量资源的情况原型模式是最好的选择。主要是 为了减少类实例化的过程 得已复用。
具体操作:在类上实现Cloneable接口 并且重写clone() 方法即可
例如:打印机第一次的是我们可以把电子文档打印到A4纸张上(原型实例化过程) 再次使用的话就不需要重新打印了 是需要复印就可以了
注意事项:深拷贝和浅拷贝
浅拷贝:也就是说原始数据类型(基本数据类型)是可以直接复制拷贝值的
深拷贝:引用类型也可以拷贝 注意的是拷贝的是引用类型的地址 也就是说多个副本中引用类型指向的是同一个地址
可以修改方式:enemyPlan类中实现了clone()方法也对bullet进行了clone 前提是Bullet也要实现Cloneable接口这 样就可以实现深拷贝了 每个副本中的bullet都是不一样的
目的:从原型实例clone出来新的实例,对于那些非常复杂的初始化过程的对象或者消耗大量资源的情况原型模式是最好的选择。主要是 为了减少类实例化的过程 得已复用。
具体操作:在类上实现Cloneable接口 并且重写clone() 方法即可
例如:打印机第一次的是我们可以把电子文档打印到A4纸张上(原型实例化过程) 再次使用的话就不需要重新打印了 是需要复印就可以了
注意事项:深拷贝和浅拷贝
浅拷贝:也就是说原始数据类型(基本数据类型)是可以直接复制拷贝值的
深拷贝:引用类型也可以拷贝 注意的是拷贝的是引用类型的地址 也就是说多个副本中引用类型指向的是同一个地址
可以修改方式:enemyPlan类中实现了clone()方法也对bullet进行了clone 前提是Bullet也要实现Cloneable接口这 样就可以实现深拷贝了 每个副本中的bullet都是不一样的
工厂模式
允许子类工厂决定具体制造 哪类产品的实例 最后减低系统的耦合度
工厂方法模式是弥补简单工厂模式中产品单一,扩展难的缺点。比如一个生产汽车的简单工厂如果去生产自行车,就需要去更改生产线,而工厂方法模式就是在创建工厂的时候创建生产自行车的工厂,这样需要时直接用就可以了。
工厂方法模式由四部分组成:抽象产品接口、具体产品类、抽象工厂接口、具体工厂类。
在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个新的产品,只要扩展一个新工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点: 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
注意事项: 在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 几次对象就不用了, 这种情况,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
允许子类工厂决定具体制造 哪类产品的实例 最后减低系统的耦合度
工厂方法模式是弥补简单工厂模式中产品单一,扩展难的缺点。比如一个生产汽车的简单工厂如果去生产自行车,就需要去更改生产线,而工厂方法模式就是在创建工厂的时候创建生产自行车的工厂,这样需要时直接用就可以了。
工厂方法模式由四部分组成:抽象产品接口、具体产品类、抽象工厂接口、具体工厂类。
在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个新的产品,只要扩展一个新工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点: 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
注意事项: 在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 几次对象就不用了, 这种情况,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
抽象工厂
子主题
0 条评论
下一页
为你推荐
查看更多