Oct 9, 2018
基础
#
分层
视图层 jsp
控制层 servlet
业务层 过滤数据
manager层 封装第三方,service能力下沉,dao中间件
数据访问层对象 封装对象
数据库
java web 13种技术
JDBC Java Database Connectivty
JNDI Java Name and Directory Interface
EJB Enterprise JavaBean
RMI Remote Method Invoke
Java IDL/CORBA
JSP Java Server Pages
Java Servlet
XML Extensible Markup Language
JMS Java Message Service
JTS Java Transaction Service
JTA Java Transaction Architecture
JavaMail
JAF JavaBeans Activation Framework
jsp
#
基于servlet, html页面嵌java代码,第一次访问时解释成servlet。位于视图层
域对象
pageContext # 当前页面有效
request # 一次请求范围
session # 会话
application context # 同一服务器
内置对象
Request
Response
Session
Out # 输出流
PageContext # context
Page # jsp的this
Exception # <%@ page isErrorPage="true"%> 时使用,显示异常信息
Application # 服务器
Config # 服务器配置
jsp-el表达式
#
${ }
11个内置对象
pageContext // pageContext
page // map (相当于pageScope,不过写法上省略了Scope)
requestScope // map
sessionScope // map
applicationScope // map
param // map ,用${param.name}的形式得到传递的参数
paramValues // map<String,String []>
header // map
hearderValues // map<String, String []>
cookie // map
initParam // map
语法
${list[0]}<br>
${map.mapteststring}<br>
${map[mapkey]}<br>
${map['mapteststring']}<br>
${request }
${pageContext.request.contextPath }<br> # el表达式中访问内置对象
${requestScope.aaa } # 访问内置对象requestScope,得到request作用域中的aaa元素
${pageContext.servletContext.contextPath }<br>
${param}<br>
${paramValues['a'] }<br>
${paramValues['a'][0] }
${paramValues['a'][1] }
${paramValues['a'][2] }<br>
${empty novalue}<br>
${1>2?"yes" : "no"}<br>
# . 与 [] 可以替换使用,但有两点需要注意
1 .1不行,但是[1]可以
2 1> map["key"] 是取map中"key"对应的值
2> map[key]是先从作用域中取得key的字符串如"aaa",再取map中"aaa"对应的值
3> .key 相当于1>中的介绍,是取map中"key"对应的值
4> .是不能相当于2>中的介绍那样使用的
比较符${ }中使用
empty
not empty
三元式(?:)
简单的算术运算
jsp-taglib标签库
#
jstl标签库1.1 或1.2
标签库1.1中需要 jstl.jar 与 standard.jar 库
可放入域scope的类型
page
request
session
application
functions
el表达式级使用,其它都标签级使用
functions
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
${fn:contains("gzitcast", "itcast") } <br>
${fn:containsIgnoreCase("gzitcast", "ITCAST") } <br>
${fn:endsWith("gzitcast", "st") } <br>
${fn:indexOf("gzitcsat", "cs") } <br>
${fn:join(arr, "-") } <br>
${fn:length("gzitcast") } <br>
${fn:replace("gzitcast", "gz", "广州") } <br>
${fn:split("gz,it,cast", ",") } <br>
${fn:startsWith("gzitcsat", "gz") } <br>
${fn:substring("gzitcsat", 3, 8) } <br>
${fn:substringAfter("gzitcsat", "cs") } <br>
${fn:substringBefore("gzitcsat", "cs") } <br>
${fn:toLowerCase("gziTCsat") } <br>
${fn:toUpperCase("gziTCsat") } <br>
${fn:trim(" gzitcsat ") } <br>
<%-- 对字符串中进行转义处理,如:会把"<"替换为"<",把">"替换为">" --%>
${fn:escapeXml("<h3>gzitcsat</h3>") } <br>
core
所有标签:
out
set
remove
catch
if
choose
when
otherwise
forEach
url
param
redirect
forTokens
import
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/cores"%>
<c:out var="" default="" escapeXml="true"> <%-- 放过xml过滤,让它显示,默认不显示 --%>
-------------------------------------
scope方式
<c:set var="" value="" scope="">
target方式 <%-- 这个放入page作用域中的map值 --%>
<% Map map = new HashMap(); %>
<c:set property="key1" value="key1value" target="<%=map%>"
<% pageContext.setAttribute("map", map); %>
<c:out value="${map[key2]}"
-------------------------------------
<c:catch var="e"></c:catch>
<c:out value='<%= ((Exception)pageContext.getAttribute("e",PageContext.PAGE_SCOPE)).getMessage() %>'></c:out>
-------------------------------------
<c:remove var="" scope="">
-------------------------------------
<c:if test="${not empty }" scope="" var="">
-------------------------------------
<%-- if...else标签 --%>
<c:choose>
<c:when test="">
<c:otherwise>
-------------------------------------
<c:forEach begin="" end="" step="" items="" var="" varStatus="state"> <%-- varStatus中的函数有first last count begin end --%>
<tr bgcolor='${state.count%2 == 0? "red" : "pink"}' >
</c:forEach>
varStatus可用的函数
current // 当前这次迭代的项
index // 索引
count // 计数
first // 第一个
last // 最后一个
begin // begin属性值
end // end 属性值
step // step属性值
-------------------------------------
uri 代表所有协议路径
<c:url var="itcast" value="http://www.itcast.cn" scope="page" context="" > <%-- context 是整个网站 --%>
<c:param name="name" value="中文"> <%-- 这样传参数有编码 --%>
如果value值为"/" 则加入context属性提供上下文名称,如果context也被省略,就使用当前servlet的上下文名称
-------------------------------------
<c:redirect url="${itcast}" context="" >
-------------------------------------
<c:set var="name" value="xx,xxx,xxx,xx" scope="request" >
<c:forTokens items="${name}" delims="," begin="" end="" step="1" var="name" varStatus="" > <%--切割字符串--%>
-------------------------------------
<c:import url="/publics/head.jsp" > <%--动态包含,引入公共文件--%><%--网站publics文件夹--%>
sql标签库
# 以前没有mvc模式的时候,通过页面访问数据库时用的,现在不用
引入
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
设置数据源
<sql:setDataSource dataSource=”dataSource”[var=”name”]
[scope=”page|request|session|application”]/>
jdbc连接
<sql:setDataSource driver=”driverClass” url=”jdbcURL”
user=”username”
password=”pwd”
[var=”name”]
[scope=”page|request|session|application”]/>
JSTL提供了<sql:query>、<sql:update>、<sql:param>、<sql:dateParam>和<sql:transaction>这5个标签
1.query:
query必需指定数据源
<sql:query sql=”sqlQuery” var=”name” [scope=”page|request|session|application”]
[dataSource=”dateSource”]
[maxRow=”maxRow”]
[startRow=”starRow”]/>
或
<sql:query var=”name” [scope=”page|request|session|application”]
[dataSource=”dateSource”]
[maxRow=”maxRow”] # 设定最多可以暂存数据的长度
[startRow=”starRow”] # 设定从哪一行开始
>
sqlQuery
</sql:query>
结果集的参数
rowCount # 结果集中的记录总数
rowsByIndex # 以数字为作索引的查询结果
columnNames # 字段名称数组
Rows # 以字段为索引查询的结果
limitedByMaxRows # 是否设置了maxRows属性来限制查询记录的数量
limitedByMaxRows用来判断程序是否收到maxRows属性的限制。
并不是说设定了maxRows属性,得到结果集的limitedByMaxRows的属性都为true,
当取出的结果集小于maxRows时,则maxRows没有对结果集起到作用此时也为false。
例如可以使用startRow属性限制结果集的数据量。
2.update:
</sql:update>
<sql:update sql=”SQL语句” [var=”name”] [scope=”page|request|session|application”]
[dateSource=”dateSource”]/>
或
<sql:update [var=”name”] [scope=”page|request|session|application”]
[dateSource=”dateSource”]
>
SQL语句
参数说明
dataSource # 数据源对象
其它与query一样
3.param 参数设置
<sql:param value=”value”/>
或
<sql:param>
Value
</sql:param>
4.dataParam 标签 # 用于为SQL标签填充日期类型的参数值
参数说明
value:java.util.Date类型的参数。
type属性:指定填充日期的类型timestamp(全部日期和时间)、time(填充的参数为时间)、date(填充的参数为日期)。
5.transaction 标签
<sql:transaction [dataSource=”dataSource”]
[isolation=”read_committed|read_uncommitted|repeatable|serializable”]
>
<sql:query>
<sql:uptade>
</sql:transation>
xml标签库
核心操作
out # 主要用来取出XML中的字符串。
属性
select # XPath语句
escapeXml # 是否转换特殊字符
parse # 用来解析xml文件
属性
doc # XML文件
systemId # XML文件的URL
filter # XMLFilter过滤器
varDom # 储存解析后的XML文件
scopeDom # varDom的范围
set # 将从XML文件取得的内容储存至属性范围中
属性
select # XPath语句
流程控制
if
choose when otherwise
属性
select # XPath语句
文件转换
<x:transform doc=”xmldoc” xslt=”XSLTStytlesheet”[docSystemId=”xmlsystemid”]
[result=”result”]
[var=”name”]
[scope=”scopeName”]
[xsltSystemId=”xsltsystemid”]/>
或
<x:transform doc=”xmldoc” xslt=”XSLTStytlesheet”[docSystemId=”xmlsystemid”]
[result=”result”]
[var=”name”]
[scope=”scopeName”]
[xsltSystemId=”xsltsystemid”]
>
<x:param/>
</x:transform>
或
<x:transform doc=”xmldoc” xslt=”XSLTStytlesheet”[docSystemId=”xmlsystemid”]
[result=”result”]
[var=”name”]
[scope=”scopeName”]
[xsltSystemId=”xsltsystemid”]
>
属性
doc # 指定xml文件来源
xslt # 转化xml的样式模板
docSystemId # xml文件的URI
xsltSystemId # xslt文件的URI
result # 用来存储转换后的结果对象
国际化
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
国际化标签
1.setLocale # 设置一个全局的地区代码,设定的是本地的环境
中文-大陆:<fmt:setLocale value="zh_CN"/> <fmt:formatDate value="${todayValue}"/><br>
2.requestEncoding # 设置统一的请求编码
<fmt:requestEncoding value="GB2312"/>
信息显示标签
1.<fmt:bundle> 设置临时要读取的资源文件
2.<fmt:message> 通过key取得value
3.<fmt:setBundle> 设置一个要读取的全局的资源文件
如
<fmt:setBundle basename="applicationMessage" var="MyResourse"/> # 绑定了名为applicationMessage_zh_CN.properties一类 的文件
<fmt:bundle basename="MyResourse" prefix="label.">
<fmt:message key="backcolor" bundle="${applicationBundle}"/>
<fmt:message key="fontcolor" />
</fmt:bundle>
数字及日期格式化标签
1.<fmt:formatDate> 日期的格式化
属性
value:格式化的日期,该属性的内容应该是 java.util.Date 类型的实例
type:格式化的类型
pattern:格式化模式
timeZone:指定格式化日期的时区
2.<fmt:parseDate> 解析日期
属性
value:将被解析的字符串
type:解析格式化的类型
pattern:解析格式化模式
parseLocale:以本地化的形式来解析字符串,该属性的内容为 String 或 java.util.Locale 类型的实例
timeZone:指定解析格式化日期的时区
3.<fmt:formatNumber> 数字格式化
属性
value:格式化的数字,该数值可以是 String 类型或 java.lang.Number 类型的实例
type:格式化的类型,可能值包括:currency(货币)、number(数字)和percent(百分比)
pattern:格式化模式
maxIntegerDigits:指定格式化结果的最大值
minIntegerDigits:指定格式化结果的最小值
maxFractionDigits:指定格式化结果的最大值,带小数
minFractionDigits:指定格式化结果的最小值,带小数
如
<fmt:formatNumber value="1000.888" type="currency" var="money"/>
4.<fmt:parseNumber> 解析数字
属性
value:将被解析的字符串
type:解析格式化的类型
pattern:解析格式化模式
如
<fmt:parseNumber value="15%" type="percent" var="num"/>
5.<fmt:setTimeZone> 标签则允许将时区设置保存为一个变量,在之后的工作可以根据该变量来进行属性描述
属性
value # 时区的设置
var # 用于保存时区为一个变量
6.<fmt:timeZone> 标签将使得在其标签体内的工作可以使用该时区设置
属性
value # 时区的设置
7.<fmt:param> 标签:用于参数传递
如:在MyResourse.properties文件中,有一个索引值如下(其中,{0}代表占位符):
Str2=Hi,{0}
则,使用<fmt:param>标签传入值如下:
<fmt:bundle basename="MyResourse">
<fmt:message key="Str2">
<fmt:param value="张三" />
</fmt:message>
</fmt:bundle>
也可以在资源文件中指定参数的类型:
如:在MyResourse.properties文件中,有一个索引值如下:
Str3={0,date}
则,使用<fmt:param>标签传入值如下:
<% request.setAttribute("now",new Date()); %>
<fmt:bundle basename="MyResourse">
<fmt:message key="Str3">
<fmt:param value="${now}" />
</fmt:message>
</fmt:bundle>
jsp动作标签
#
在JSP中的动作行为包括:Include、 Forward、 UseBean、 GetProperty、 SetProperty、 Plugin。
一、Include行为
<jsp:include>标签表示动态包含一个静态的或者动态的文件。
语法:
<jsp:include page="path" flush="true" />
or
<jsp:include page="path" flush="true">
<jsp:param name="paramName" value="paramValue" />
</jsp:include>
注:
1、page="path" 为相对路径,或者代表相对路径的表达式。
2、flush="true" 必须使用flush为true,它默认值是false。
3、<jsp:param>子句能让你传递一个或多个参数给动态文件,也可在一个页面中使用多个<jsp:param>来传递多个参数给动态文件。
4、<jsp:include page="" flush=""> 与<%@ include file=""%>的区别:
<jsp:include >是动态包含<%@include%>是静态包含。
# jsp页面是把include指令元素(<%@ include file=""%>)所指定的页面的实际内容(也就是代码段)加入到引入它的jsp页面中,合成一个文件后被jsp容器将它转化成servlet。
## 可以看到这时会产生一个临时class文件和一个servlet源文件。
## 而动作元素(<jsp:include page=""/>)是在请求处理阶段引入的,会被JSP容器生成两个临时class文件和两个servlet原文件。
## 而引入的只是servlet的输出结果,即JspWriter对象的输出结果,而不是jsp的源代码。
二、Forward行为
<jsp:forward>标签表示重定向一个静态html/jsp的文件,或者是一个程序段。
语法:
<jsp:forward page="path"} />
or
<jsp:forward page="path"} >
<jsp:param name="paramName" value="paramValue" />……
</jsp:forward>
注:
1、page="path" 为一个表达式,或者一个字符串。
2、<jsp:param> name 指定参数名,value指定参数值。参数被发送到一个动态文件,参数可以是一个或多个值,而这个文件却必须是动态文件。要传递多个参数,则可以在一个JSP文件中使用多个<jsp:param>将多个参数发送到一个动态文件中。
三、UseBean行为
<jsp:useBean>标签表示用来在JSP页面中创建一个BEAN实例并指定它的名字以及作用范围。
语法:
<jsp:useBean id="name" scope="page | request | session | application" typeSpec />
其中typeSpec有以下几种可能的情况:
class="className" | class="className" type="typeName" | beanName="beanName" type="typeName" | type="typeName" |
注:
你必须使用class或type,而不能同时使用class和beanName。beanName表示Bean的名字,其形式为“a.b.c”。
四、GetProperty行为
<jsp:getProperty>标签表示获取BEAN的属性的值并将之转化为一个字符串,然后将其插入到输出的页面中。
语法:
<jsp:getProperty name="name" property="propertyName" />
注:
1、在使用<jsp:getProperty>之前,必须用<jsp:useBean>来创建它。
2、不能使用<jsp:getProperty>来检索一个已经被索引了的属性。
3、能够和JavaBeans组件一起使用<jsp:getProperty>,但是不能与Enterprise Java Bean一起使用。
JavaScript通用库 Jsp语法
JSP的动作标签 2008-04-01 11:47:49| 分类: JSP学习 | 标签: |字号大
中
小 订阅
在JSP中的动作行为包括:Include、 Forward、 UseBean、 GetProperty、 SetProperty、 Plugin。
一、Include行为
<jsp:include>标签表示包含一个静态的或者动态的文件。
语法:
<jsp:include page="path" flush="true" />
or
<jsp:include page="path" flush="true">
<jsp:param name="paramName" value="paramValue" />
</jsp:include>
注:
1、page="path" 为相对路径,或者代表相对路径的表达式。
2、flush="true" 必须使用flush为true,它默认值是false。
3、<jsp:param>子句能让你传递一个或多个参数给动态文件,也可在一个页面中使用多个<jsp:param>来传递多个参数给动态文件。
二、Forward行为
<jsp:forward>标签表示重定向一个静态html/jsp的文件,或者是一个程序段。
语法:
<jsp:forward page="path"} />
or
<jsp:forward page="path"} >
<jsp:param name="paramName" value="paramValue" />……
</jsp:forward>
注:
1、page="path" 为一个表达式,或者一个字符串。
2、<jsp:param> name 指定参数名,value指定参数值。参数被发送到一个动态文件,参数可以是一个或多个值,而这个文件却必须是动态文件。要传递多个参数,则可以在一个JSP文件中使用多个<jsp:param>将多个参数发送到一个动态文件中。
三、UseBean行为
<jsp:useBean>标签表示用来在JSP页面中创建一个BEAN实例并指定它的名字以及作用范围。
语法:
<jsp:useBean id="name" scope="page | request | session | application" typeSpec />
其中typeSpec有以下几种可能的情况:
class="className" | class="className" type="typeName" | beanName="beanName" type="typeName" | type="typeName" |
注:
你必须使用class或type,而不能同时使用class和beanName。beanName表示Bean的名字,其形式为“a.b.c”。
四、GetProperty行为
<jsp:getProperty>标签表示获取BEAN的属性的值并将之转化为一个字符串,然后将其插入到输出的页面中。
语法:
<jsp:getProperty name="name" property="propertyName" />
注:
1、在使用<jsp:getProperty>之前,必须用<jsp:useBean>来创建它。
2、不能使用<jsp:getProperty>来检索一个已经被索引了的属性。
3、能够和JavaBeans组件一起使用<jsp:getProperty>,但是不能与Enterprise Java Bean一起使用。
五、SetProperty行为
<jsp:setProperty>标签表示用来设置Bean中的属性值。
语法:
<jsp:setProperty name="beanName" prop_expr />
其中prop_expr有以下几种可能的情形:
property="*" | property="propertyName" | property="propertyName" param="parameterName" | property="propertyName" value="propertyValue"
注:
使用 jsp:setProperty 来为一个Bean的属性赋值;可以使用两种方式来实现。
1、在jsp:useBean后使用jsp:setProperty:
<jsp:useBean id="myUser" … />
…
<jsp:setProperty name="user" property="user" … />
在这种方式中,jsp:setProperty将被执行。
2、jsp:setProperty出现在jsp:useBean标签内:
<jsp:useBean id="myUser" … >
…
<jsp:setProperty name="user" property="user" … />
</jsp:useBean>
在这种方式中,jsp:setProperty只会在新的对象被实例化时才将被执行。
* 在<jsp:setProperty>中的name值应当和<jsp:useBean>中的id值相同。
六、Plugin行为
<jsp:plugin>标签表示执行一个applet或Bean,有可能的话还要下载一个Java插件用于执行它。
语法:
<jsp:plugin
type="bean | applet"
code="classFileName"
codebase="classFileDirectoryName"
[ name="instanceName" ]
[ archive="URIToArchive, ..." ]
[ align="bottom | top | middle | left | right" ]
[ height="displayPixels" ]
[ width="displayPixels" ]
[ hspace="leftRightPixels" ]
[ vspace="topBottomPixels" ]
[ jreversion="JREVersionNumber | 1.1" ]
[ nspluginurl="URLToPlugin" ]
[ iepluginurl="URLToPlugin" ] >
[ <jsp:params>
[ <jsp:param name="parameterName" value="{parameterValue | <%= expression %>}" /> ]+
</jsp:params> ]
[ <jsp:fallback> text message for user </jsp:fallback> ]
</jsp:plugin>
注:
<jsp:plugin>元素用于在浏览器中播放或显示一个对象(典型的就是applet和Bean),而这种显示需要在浏览器的java插件。
当Jsp文件被编译,送往浏览器时,<jsp:plugin>元素将会根据浏览器的版本替换成<object>或者<embed>元素。注意,<object>用于HTML 4.0 ,<embed>用于HTML 3.2。
一般来说,<jsp:plugin>元素会指定对象是Applet还是Bean,同样也会指定class的名字,还有位置,另外还会指定将从哪里下载这个Java插件。
jsp函数
#
用response.getOutputStream返回数据(而非JspWriter)时,调用:
# 如输出图片对象:ImageIO.write(image, "jpeg", response.getOutputStream());
out.clear(); # 清空 out
out = pageContext.pushBody() # 将图片对象的流从out输出,直到整个输出结束(接收方网页加载全部完成时)后才断开
jsp基本
#
模板元素
脚本
<% %>
脚本表达式
<%= %>
注释
<%-- --%>
指令
<%@ page%>
language="java"
import="java.util.*,java.io.*"
contentType="mineType [; charset=characterSet]"
pageEncoding="characterSet"
session="true"
buffer="none | 8kb | sizekb"
autoFlush="true"
isThreadSafe="true"
info="text"
errorPage="relative_url"
isErrorPage="true"
isELIgnored="true"
<%@ include%> # <%@ include file="in.jspf" %> 是静态包含(原代码中包含),一般包含名字为*.jspf的jsp文件
<%@ taglib%> # 标签库
声明
<%! %> # 全局声明(刷新页面仍然保存数据)
<% %> # 局部的声明(刷新页面不保存数据)
标签
<jsp:forward page=""></jsp:forward>
<jsp:include page=""></jsp:include>
内置对象 9个
pageContext
request
response
config
session
application
page
out
exception
全局变量
static final JspFactory _jspxFactory
static java.util.List _jspx_dependants
javax.el.ExpressionFactory _el_expressionfactory
org.apache.AnnotationProcessor _jsp_annotationprocessor
在_jspService中的变量
HttpServletRequest request
HttpServletResponse response
PageContext pageContext = null;
pageContext = _jspxFactory.getPageContext(this,request,response,null,true,8192,ture);
HttpSession session = null;
session = pageContext.getSession();
ServletContext application = null;
application = pageContext.getServletContext();
ServletConfig config = null;
config = pageContext.getServletConfig();
JspWriter out = null;
out = pageContext.getOut();
Object page = this;
JspWriter _jspx_out = null;
_jspx_out = out;
PageContext _jspx_page_context = null;
_jspx_page_context = pageContext;
jsp验证码
#
实例 # 在<img>标签的src属性中指定该jsp文件即可
## out.clear();out = pageContext.pushBody();两条语句的作用是
## 使该验证码jsp文件的传输不会默认地在返回数据后中断,而是在<img>标签调用该jsp的页面加载结束之后再中断数据的传输
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ page contentType="image/jpeg" import="java.util.*,java.awt.*,java.awt.image.*,javax.imageio.*"%>
<%!
// 声明区,定义产生颜色和验证内容的全局方法
public Color getColor(){
Random random = new Random();
int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
return new Color(r,g,b);
}
public String getNum(){
String str = "";
Random random = new Random();
for(int i = 0; i < 4; i++){
str += random.nextInt(10) + " ";
}
return str;
}
%>
<%
// 设置响应无缓存
response.setHeader("pragma", "mo-cache");
response.setHeader("cache-control", "no-cache");
response.setDateHeader("expires", 0);
// 图片对象,画笔对象
BufferedImage image = new BufferedImage(80,30,BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
// 画背景
g.setColor(new Color(200,200,200));
g.fillRect(0, 0, 80, 30);
// 画干扰线
for(int i = 0; i < 30; i++){
Random random = new Random();
int x = random.nextInt(80);
int y = random.nextInt(30);
int xl = random.nextInt(x+10);
int yl = random.nextInt(y+10);
g.setColor(getColor());
g.drawLine(x, y, x + xl, y + yl);
}
// 画内容
g.setFont(new Font("serif", Font.BOLD,16));
g.setColor(Color.BLACK);
String checkNum = getNum();
g.drawString(checkNum,15,20);
// 放内容到session中,返回图片流
session.setAttribute("validateCode", checkNum.replaceAll(" ", ""));
ImageIO.write(image, "jpeg", response.getOutputStream());
out.clear();
out = pageContext.pushBody(); // 不按照jsp默认的getWriter()方法输出,用我们定义的流的方法进行输出
%>
自定义标签
#
1、JspTag 接口(标记接口,类以Serializable)
2、Tag 接口(空标签,如<img/>)
属性:
static int EVAL_BODY_INCLUDE 通过流执行标签体
static int EVAL_PAGE 继续执行页面
static int SKIP_BODY 忽略执行标签体
static int SKIP_PAGE 忽略后面的JSP页面
方法:
// 生命周期方法
int doEndTag() 当遇到标签结束的时候自动执行
int doStartTag() 当遇到标签开始的时候自动执行
// 实现方法
Tag getParent() 获取当前标签的父标签处理类对象
void release() 当事件改变的时候自动执行
void setPageContext(PageContext pc) 设置当前的JSP上下文环境
void setParent(Tag t) 设置当前标签的父标签对象
3、TagSupport 类(有属性的标签,如<img src=""/>)
实现了Tag接口并且提供处理标签属性的方法(set和get)。而且内部定义了一个PageContext变量并且已经初始化开发者可以直接使用this或者super直接方法该属性。
4、BodyTagSupport类(有属性有文本内容和标签,如<img src="">aaa</img>)
新属性
protected BodyContent bodyContent
新方法
void setBodyContent(BodyContent b)
BodyContent getBodyContent()
BodyContent类
abstract String getString() //获取标签体
写Tag接口的标签库
1、写Tag接口实现类
写属性pageContext(getter 和setter),从setPageContext(PageContext pc)方法中获得该属性
复写方法
2、写tld文件,放到/META-INF文件夹中
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>ouru</short-name> # 简称
<uri>/outrun-tags</uri> # 自定义引入标签时写的路径
<tag>
<name>testDate</name>
<tag-class>outrun.util.jsp.taglib.test.DataImplTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
3、jsp 中引用它
<%@ taglib prefix="ouru" uri="/META-INF/myUtil.tld" %>
写TagSupport接口的实现类
pageContext已内置
定义接收属性
tld文件中加入attribute属性
<attribute> 属性描述的开始
<name>pattern</name> 描述属性名
<required>true</required> 描述属性是否是必须的
<rtexprvalue>true</rtexprvalue> 描述属性值是否可以是输出表达式
</attribute>
写BodyTagSupport接口的实现类
BodyContent body = this.getBodyContent();
String desc = body.getString();
tld 文件中
<body-content>JSP</body-content> # 有标签体,可执行脚本表达式
## scriptless,有标签体,不执委脚本表达式
## empty,没有标签体
Jsp2.0
JspTag — SimpleTag — SimpleTagSupport
SimpleTagSupport类
该类可以直接进行操作标签的属性和标签体。
void doTag() 遇到标签的时候自动指定
protected JspFragment getJspBody() 获取标签体对象
protected JspContext getJspContext() 获取JSP上下文环境对象
JspTag getParent() 获取该标签的父标签处理类对象
JspFragment类
该类代表的标签的标签体。
abstract void invoke(Writer out) 输出数据到指定的流,null输出到JSP页面
获得标签体的方法:
Writer writer = new StringWriter();
JspFragment jspFragment = getJspBody();
jspFragment.invoke(writer);
String text = writer.toString();
项目:实现 if else 判断
Choose.java
private boolean tag = true;
public boolean isTag() {
return tag;
}
public void setTag(boolean tag) {
this.tag = tag;
}
@Override
public void doTag() throws JspException, IOException {
getJspBody().invoke(null);
super.doTag();
}
when.java 文件
private boolean test = false;
public boolean isTest() {
return test;
}
public void setTest(boolean test) {
this.test = test;
}
@Override
public void doTag() throws JspException, IOException {
Choose parent = (Choose) getParent();
if(isTest() && parent.isTag()){
// 条件成立
getJspBody().invoke(null);
// 设置父的tag为false
parent.setTag(false);
}
super.doTag();
}
Otherwise.java 文件
@Override
public void doTag() throws JspException, IOException {
Choose parent = (Choose) getParent();
if(parent.isTag()){
// 条件成立
getJspBody().invoke(null);
parent.setTag(false);
}
super.doTag();
}
tld文件
<tag>
<name>choose</name>
<tag-class>outrun.util.jsp.taglib.ifelse.Choose</tag-class>
<body-content>scriptless</body-content> # 有标签体,可执行脚本表达式
## scriptless,有标签体,不执委脚本表达式
## empty,没有标签体
</tag>
<tag>
<name>when</name>
<tag-class>outrun.util.jsp.taglib.ifelse.When</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>test</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>otherwise</name>
<tag-class>outrun.util.jsp.taglib.ifelse.Otherwise</tag-class>
<body-content>scriptless</body-content>
</tag>
控件标签:
自定义函数库
1 创建函数库类
public class MyFunctions {
public static String formatMyName(String name) {
return "your name is " + name;
}
public static int add(int a, int b) {
return a+b;
}
}
2 在TLD文件中配置 (引用于目标1中的tld文件)
<function>
<name>formatMyName</name>
<function-class>com.taglib.MyFunctions</function-class>
<function-signature>java.lang.String formatMyName(java.lang.String)</function-signature>
</function>
<function>
<name>add</name>
<function-class>com.taglib.MyFunctions</function-class>
<function-signature>java.lang.String add(int, int)</function-signature>
</function>
3 JSP中调用
${cc:formatMyName("wangfei") }
${cc:add(12, 34) }
jdbc
#
流程
Class.forName() # 加载驱动
DriverManager.getConnection()
获得sql会话对象 Statement或PreparedStatement
设置参数setXxx(), 执行sql,处理结果集
关闭结果集、关闭会话、关闭连接
Statement
execute
executeQuery
executeUpdate
不要使用Statement
容易sql注入
代码可读性可维护性差
PreparedStatement性能高,db缓存机制,相同预编译语句调用不再编译
PreparedStatement
# 继承Statement, 预编译sql
事务怎么写
编程式
声明式: 用aop注入
三种连接
Connection
默认自动提交
禁止自动提交开启事务, 后调commit
Pooled Connection
使用完后不用关闭
XA Connection
分布式事务
XAResource获得
i18n
#
页面需要获取用户信息,从数据库中取数据显示
java类中试用:
cn.itcast.resource包中
hello_en_US.properties
hello=hello
hello_zh_CN.properties
hello=编码后的“你好”
Test.java
# 获取信息
Locale locale = locale.CHINA;
# 加载资源
ResourceBundle bundler = ResourceBundle.getBundle("cn.itcast.resource.hello",locale);
# 取出数据
String str = bundler.getString("hello");
jsp中
<%
ResourceBundle bundler = ResourceBundle.getBundle("lang.properties.hello", request.getLocale());
out.write(bundler.getString("title"));
%>
<fmt>标签
<fmt:setLocale value="${pageContext.request.locale }" scope="page"/>
<fmt:setBundle basename="lang.properties.hello" var="bundler" scope="page"/>
<fmt:message bundle="${bundler }" key="title"></fmt:message>
资源转码
native2ascii.exe
myeclipse properties文件编辑器
ie 中得到en_US
Locale locale
getLanguage();
getCountry();
getDefault();
ResourceBundle
读取文件cn.itcast.resource.hello省略_en_US.properties
getString(String key)
实例1:
创建页面
创建资源文件
编辑页面
request.getLocale();
ResourceBundle.getBundle("",locale);
getString("")
实例2:
编辑页面
<%@ taglib%>
<f:setLocale scope="" value="">
<f:setBundle basename="" var="" scope="">
<f:message bundle="" key="" >
动态数据国际化
日期国际化
SimpleDateFormat # 继承DateFormat
getDateTimeInstance
getDateInstance
getTimeInstance
static int FULL
static int LONG
static int MEDIUM
static int SHORT
实例3:
cn.itcast.i18n.MyDateI18n
DateFormat format = DateFormat.getDateTimeInstance(DateFormat.FULL,DateFormat.FULL,Locale.CHINA);
String str = format.format(new Date());
解析页面中的字符串
FULL 和 LONG 和 MEDIUM 和 SHORT 的 区别
DataFomat
String format(Date date)
Date parse(String source)
创建static string2Date(String str)
# 分析区域
Locale locale = Locale.CHINA;
# 分析日期的风格
int dateType = DateFormat.SHORT;
int timeType = DateFormat.FULL;
# 获取对象
DateFormat format = DateFormat.getDateTimeInstance(dateType,timeType,locale);
# 解析
format.parse(str);
动态数字国际化
java.text.*;
Number类
NumberFormat(普通数字,货币,百分比)
getIntegetInstance
getCurrencyInstance
getPercentInstance(Locale inLocale)
format
parse
创建cn.itcast.i18n.MyNumberI18n
# 获取对象
# getPercentInstance
# getCurrencyInstance
NumberFormat format = NumberFormat.getIntegerInstance(Locale.CHINA);
# 格式化 或解析
long num = 10000000000L;
# Number num = format.parse(str);
# double price = num.doubleValue();
format.format(num);
动态文本国际化
At 12:30 pm on jul 3,1998, a hurricance destroyed 99 houses and caused $1000000 of damage.
MessageFormat
MessageFormat(String pattern,Locale locale)
format(String pattern,Object...arguments)
format(Object)
parse()
占位
At{0} on {0}, a hurricance destroyed{1} houses and caused {2} of damage.
实例1:
MyMessageI18n.java
# 定义模式字符串
String pattern
# 定义locale对象
MessageFormat format = new MessageFormat(pattern,Locale.CHINA);
# 定义参数数组
DateFormat datef = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,DateFormat.SHORT,Locale.US);
Date date = datef.parse("Jul 3,1998 12:30 PM");
Integer num = new Integer(99);
long currency = NumberFormat.getCurrencyInstance(Locale.US).parse("$1000000");
String damage = NumberFormat.getCurrencyInstance(locale).format(currency);
Object [] values = {date,num,damage};
# 格式化
String str = format.format(values);
分析:{索引,对象,类型}
MessageFormat messf = new MessageFormat("{0,time,short} on {0,date}, a hurricance destroyed {1} houses and caused {2,number,currency} of damage.",Locale.CHINA);
Object [] values = {new Date(),new Integer(100),1000};
String str = messf.format(values);
中间件
#
顺序
监听器 -> 过滤器
监听器
#
分类
ServletContextListener
ServletContextAttributeListener
HttpSessionListener
HttpSessionAttributeListener
HttpSessionActivationListener
web.xml 注册在过滤器后面,servlet前面
<listener>
<listener-class>cn.listen.MyListener</listener-class>
</listener>
自定义
public class MyListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("die");
}
public void contextInitialized(ServletContextEvent sce) {
System.out.println("init");
}
// 当过滤器被销毁时自动执行
public void destroy(){
System.out.println("Filterdestroyed");
}
// 当拦截的时候
public void doFilter(request,response,chain){
System.out.println("doFilter");
System.out.println("放行目标资源");
chain.doFilter(request,response);
System.out.println("目标已经放行");
}
// 初始化的时候
public void init(FilterConfig config){
System.out.println("FilterInited");
}
}
过滤器
#
web.xml
<filter>
<filter-name>testFilter
<filter-class>cn.itcast.filter.text.TestFilter
<init-param>
<param-name>encoding</param-name>
<param-value>GB2312</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>testFilter
<url-pattern>/*
</filter-mapping>
api
Filter接口
## 多个过滤器, 按web.xml中注册的顺序映射调用。servlet执行完后, 从后向前返回执行chain.doFilter之后的方法
init
destroy
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
FilterChain接口
doFilter(ServletRequest request, ServletResponse response)
FilterConfig接口
private FilterConfig config = null;
init()
this.config = config;
doFilter()
String ip = this.config.getInitParameter("ip");
映射
1.映射的url
2.servlet的名字
# 通配servlet <url-pattern>/servlet/*</url-pattern>
3.为了映射servlet中的转发, 映射一个过滤器到某种传递方式
<filter-mapping>
<dispatcher>FORWARD
# FORWARD 转发方式
# REQUEST 请求方式
# INCLUDE 包含方式
# ERROR 错误页面
</dispatcher>
</filter-mapping>
设置编码
doFilter()
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpRequest.setCharacterEncoding("utf-8")
String method = httpRequest.getMethod();
if("get".equalsIgnoreCase(method)){
chain.doFilter(new MyWapperRequest(httpRequest),response);
}else{
request.setCharacterEncoding("utf-8");
}
class MyWapperRequest extends HttpServletRequestWrapper{
private HttpServletRequest request = null;
public MyWapperRequest (HttpServletRequest request){
super(request);
this.request = request;
}
@Override
public String getParameter(String name){
String value = request.getParameter(name);
String method = request.getMethod();
if(value != null &&"get".equalsIgnoreCase(method)){
value = new String(value.getBytes("iso8859-1"),request.getCharacterEncoding())
}
return value;
}
}
设置缓存
# Expires:-1
# Cache-Control:no-cache
# Pragma:no-cache
NoCacheFilter implements Filter # 设置不缓存
doFilter()
HttpServletResponse httpResponse = response;
httpResponse.setHeader("Expires",-1 + "");
// setDataHeader("expires",-1);
httpResponse.setHeader("cache-control","no-cache");
httpResponse.setHeader("pragma","no-cache");
chain.doFilter(request,httpResponse);
CacheFilter
# 缓存静态资源
# web.xml中url-pattern 可以映射多个 param-name=jpg param-value=2
private FilterConfig config = null;
doFilter()
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletResponse httpRequest = (HttpServletRequest) request;
String resource = request.getRequestURI();
String date = null;
if(resource.endsWith("jpg")){
date = config.getInitParameter("jpg");
httpResponse.setDateHeader("expires",System.currentTimeMillis() + longDate * 60 * 60 * 1000); // 换算成秒
} else if(resource.endsWith("js")){
String date = config.getInitParameter("js");
config.getInitParameter("js");
httpResponse.setDateHeader("expires",System.currentTimeMillis() + longDate * 60 * 60 * 1000);
}
chain.doFilter(httpRequest,httpResponse);
修改编码
EncodingFilter implements Filter{
doFilter(request,response){
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletResponse httpRequest = (HttpServletRequest) request;
httpRequest.setCharacterEncoding("utf-8");
chain.doFilter(httpRequest,httpResponse);
}
}
class EncodingRequest extends HttpServletRequestWrapper{
private HttpServletRequest request;
public EncodingRequest (HttpServletRequest request){
super(request);
this.request = request;
}
@Override
public String getParameter(String name){
String value = request.getParameter(name)
if(value != null && "get".equalsIgnoreCase(request.getMethod)){
value = new String(value.getBytes("iso8859"),"utf-8");
}
return value;
}
}
登录
LoginFilter
init(){
this.config = config;
}
private FilterConfig config = null;
doFilter(){
String path = this.config.getInitParameter("loginPage");
HttpSession session = httpRequest.getSession(false);
// 判断用户请求的是否是UserServlet
String servletName = httpRequest.getServletPath();
servletName = substring(servletName.lastIndexOf("/")+1);
if("UsersServlet".equals(servletName)){
}else{ // 一般的servlet
if(session != null){
// 获取登录标记
User user = null;
user = (User)session.getAttribute("user");
// 判断
if(user != null){
// 放行资源
chain.doFilter(httpRequest,httpResponse);
}else{
// 页面重定向到登录页面
httpResponse.sendRedirect(httpRequest.getContextPath() + "/" + path);
}
}
}
}
web.xml
<filter>
<filter-name>loginFilter
<filter-class>
<init-param>
<param-name>loginPage
<param-value>
<filter-mapping>
<filter-name>
<url-pattern>/jsps/*
<url-pattern>/servlet/*
<url-pattern>/publics/*
<filter-name>
<url-pattern>/publics/head.jsp
<dispatcher>INCLUDE
<dispatcher>FORWARD
<dispatcher>ERROR
<dispatcher>REQUEST
# 默认是request,当加上其它参数时(如include),request会没有,所以要加两个forward,request
## 是指向里面以该方法请求的时候进行过滤
struts2
#
Oct 9, 2018
基础
#
并发编程三大特性
可见性(visibility)
synchronized会刷新线程栈
有序性(ordering)
原子性(atomicity)
CPU
#
PC寄存器(Program Counter Register, 存下一指令)
优化
乱序读
合并写
乱序执行(流水线)
ALU
访问速度
Registers: < 1ns
L1 cache(核内): 1ns
WC(Writer Comblining) Buffer # 合并写, 一般4个字节
L2 cache(核内): 3ns
L3 cache(CPU内): 15ns
memory: 80ns
局部性原理
空间
按块读取(cache line)
一次读64Bytes # disruptor RingBuffer实现前后7个long,两个元素不同行,避免缓存一致性协议的通知
Java1.8注解@Contended # 保证不在同一行,需要JVM参数-XX:-RestrictContended
时间
批量读指令
内存屏障 # 不同CPU不一样
Intel
sfence # save, 之前写操作必须完成
lfence # load, 之前读操作必须完成
mfence # mix(save + load)
lock指令 # 指令执行完之前,锁内存
lock_add
CPU缓存一致性协议
#
# 是缓存锁。无法被缓存、跨越多个缓存的数据,依然总线锁
状态
MESI Cache一致性协议 # Intel CPU,实现方式为主动监听
Modified # 一行数据在CPU Modified, 其它CPU为Invalid
Exclusive # 独享,其它CPU没有
Shared # 别人也在读
Invalid # 别人改过了
MSI
MOSI
缓存行 # 多数64字节
伪共享 # 缓存行内无关数据也要同步
OS
#
进程
# 资源分配
线程
# 调度执行
切换(Context Switch)
CPU导出原线程指令和data到cache, 再导入新线程数据
线程数
N(threads) = N(CPU) * U(CPU) * (1 + W/C)
# N(CPU): CPU数
# U(CPU): 期望CPU利用率, 0到1
# W/C: wait时间/compute时间, (1+W/C)即运行时间/计算时间。用profiler、arthas分析
压测决定
CPU性能压榨
单进程
批处理
多进程切换
多线程进程内切换
I/O复用
协程(纤程、绿色线程)用户空间切换
JVM
#
内存屏障
#
LoadLoad
StoreStore
LoadStore
StoreLoad
乱序执行(指令重排序)
#
为什么
CPU快, 指令1阻塞时,指令2先执行
判断
代码不影响单线程最终一致性
例子,多线程
static int x = 0, y = 0;
static int a = 0, b = 0;
void main(){
for (long i = 0; i < Long.MAX_VALUE; i++>) {
x=0;
y=0;
a=0;
b=0;
CountDownLatch latch = new CountDownLatch(2);
Thread one = new Thread(() -> {
public void run() {
a = 1;
x = b;
latch.countDown();
}
});
Thread other = new Thread(() -> {
public void run() {
b = 1;
y = a;
latch.countDown();
}
});
one.start();
other.start();
latch.await();
if (x == 0 && y == 0) {
break;
}
}
}
例子,类初始化指令换顺序
class T {
int m = 8;
}
T t = new T();
汇编码
0 new #2 <T>
# 变量半初始化状态为0
3 dup
4 invokespecial #3 <T.<init>>
7 astore_1
# 4,7乱序执行, 先建立了关联再初始化, 变量中间状态值为0。线程访问时中间状态逸出
8 return
class C {
private int num = 0;
public C() {
new Thread(() -> System.out.println(this.num)).start();
}
void main() {
new C();
System.in.read();
}
}
Unsafe类
#
# 相当于指针。1.8只能根部类用, 11可以直接用
操作内存
allocateMemory
putXX
freeMemory
pageSize
生成类实例
allocateInstance
操作类或实例
objectFieldOffset
getInt
getObject
CAS
compareAndSwapObject() # JDK1.8
compareAndSetObject()
weakCompareAndSetObject() # JDK11
修饰符与锁
#
概念
#
锁细化 # 少代码加轻量锁
锁粗化 # 锁太多时,如行锁变表锁
synchronized
#
实现方式
JVM没要求实现方式
早期都是OS调度
HotSpot
在对象头(64位)上用2位实现,组合成锁的类型
锁升级 # 不比原子类慢,升完不降
偏向锁, 第一个线程第一次访问只记线程id
自旋锁,线程争抢时,JDK6旋10次,现在为CPU内核数一半。非公平
重量级锁,OS调度,线程WAIT。符合执行时间长,线程多的场景
原子性、可见性
可重入 # 同一个对象同线程可重入
加锁对象
方法锁和对象锁锁this
static方法锁和类锁锁class类
继承时锁的子对象(因为是this), 调super synchronized方法也锁子对象
使用注意
抛异常立即释放锁,但被锁数据已更新
不能用的对象
String常量,可能未知地方锁同一个
Integer、Long等基本类型, 值变化会生成新对象
synchronized的属性加final防止赋值
volatile
#
# 用synchronized性能差不多,volatile一般不用
作用
# 没有原子性,可能写同一值
变量在线程见可见性
依靠CPU缓存一致性协议
禁止指令重排序 # 用JVM的读写屏障
修饰引用类型,内部属性不监控
DCL(Double Check Lock)单例volatile问题
private static volatile C c; // 禁止了创建c指令重排序
private C(){}
public static C getInstance() {
if (c == null) {
synchronized (C.class) {
if (c == null) {
// 申请内存(半初始化状态默认0),成员变量初始化,赋值
// 先赋值未初始化时,线程2判断非空,返回了半初始化状态的对象
c = new C();
}
}
}
return c;
}
CAS
#
# Compare And Set/Swap, 无锁优化, 乐观锁, 自旋
# Unsafe类支持
CPU原语
cas(V, Expected, NewValue)
if V == E # 无并发值判断问题,原语上加了屏障
V = New
else try again or fail
Java
AtomicInteger
incrementAndGet()
ABA问题
# 线程1读取标记, 线程2改过又改回来,线程1判断标记锁住了提交了业务数据
版本号 # Java版本号类AtomicStampedReference
LongAdder
LongAdder每次加数字, LongAccumulator用lambda
分段锁(CAS)。值分开放数组里, 多线程对应一个item
性能测试
LongAdder(713) > Atomic(2166) > Synchronized(3129)
AQS
#
# AbstractQueueSynchronizer, CLH(Craig, Landin, and Hagersten)队列锁的变种
# 实现方式: CAS,volatile, 模板方法
类图
AbstractQueueSynchronizer
Sync
NonfairSync
方法
AbstractQueueSynchronizer
# 一个state和一个双向链表,双向链表看前一结点状态(如持有时等待)
Node
volatile Node prev
volatile Node next
volatile Thread thread
VarHandle
# JDK1.9,保存引用,普通属性原子操作。
# 相比反射,直接操作二进制码
get()
set()
compareAndSet() # 原子性
getAndAdd() # 原子性
class C {
int x = 0;
private static VarHandle handle;
static {
handle = MethodHandles.lookup().findVarHandle(C.class, "x", int.class)
handle.compareAndSet(c, 0, 1);
}
}
volatile state # 多态实现
acquire()
tryAcquire() # 模板方法
acquireQueued() # 获得
addWaiter(Node.EXCLUSIVE) # 放入队列,排他锁或共享锁, CAS设置tail(从前锁整表)
cancelAcquire() # status CANCELLED, tail时设置null, 非tail时unpark下一节点
NonfairSync
nonfairTryAcquire()
ReentrantLock
#
# 可重入锁,CAS实现, state记重入多少次
new ReentrantLock(true) # 公平锁
tryLock(long, TimeUnit)
lockInterruptibly() # 响应interrupt()标记
newCondition() # 多一个等待队列
源码
调NonfairSync
CountDownLatch
#
# 比join()灵活
new CountDownLatch(4)
countDown()
await()
CyclicBarrier
#
# 满了一起放行, 场景如I/O批量消费
new CyclicBarrier(4, ()->{})
await()
Phaser
#
# 阶段批量执行过滤
class MyPhaser extends Phaser {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch(phase) {
case 0:
print("arrived" + registeredParties);
return false;
case 1:
print("eated" + registeredParties);
return false;
case 2:
print("hugged" + registeredParties);
return true;
default:
return true;
}
}
}
Person implements Runnable {
private int i;
public Person(int i) {
this.i = i;
}
public void arrive() {
phaser.arriveAndAwaitAdvance();
}
public void eat() {
phaser.arriveAndAwaitAdvance();
}
public void hug() {
if (i == 0 || i == 1) {
phaser.arriveAndAwaitAdvance();
} else {
phaser.arriveAndDeregister();
}
}
@Override
public void run() {
arrive();
eat();
hug();
}
}
phaser = new MyPhaser();
phaser.bulkRegister(5);
for (int i = 0; i < 5; i++) {
new Thread(new Person(i)).start()
}
ReadWriteLock
#
# 读锁是共享锁,不能写,悲观锁
# 写锁是排他锁,不能读写
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();
void read(Lock lock) {
lock.lock()
lock.unlock()
}
void write(Lock lock) {
lock.lock()
lock.unlock()
}
for (int i =0; i<10;i++) {
new Thread(()->read(readLock)).start();
}
for (int i =0; i<2; i++) {
new Thread(()->write(writeLock)).start();
}
StampedLock
#
# 读时允许写,读完判断不一致再读一遍,乐观锁
Semaphore
#
# 信号量, 限流同时运行, 用于线程间同步。可设置公平
Semaphore s = new Semaphore(1, true)
new Thread(() -> {
s.acquire();
s.release()
})
Exchanger
#
# 线程间通信, 阻塞交换
exchange()
LockSupport
#
# 线程间通信,非锁阻塞,指定线程唤醒
# 线程启动后,unpark()可以在park()前调用生效, make(chan struct{}, 1)
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
if (i == 5) {
LockSupport.park();
}
}
})
t.start();
TimeUnit.SECONDS.sleep(1);
LockSupport.unpark(t);
线程及线程池
#
使用注意
#
为什么不用Executors线程池
用LinkedBlockingQueue超数量OOM
拒绝策略
线程命名
Object
wait() # 释放synchronized锁并加入等待队列,唤醒后执行需要得到synchronized锁
notify() # 只唤醒,不释放当前synchronized锁
Thread
static currentThread()
static sleep()
static yield() # 让出CPU, 进Ready队列
start()
getState()
join() # 等待结束
setDaemon() # 是否后台
setPriority(Thread.NORM_PRIORITY) # 优先级,没有用
interface Runnable
void run()
interface Callable
V call() throws Exception
interface Future
get()
get(long, TimeUnit)
cancel(boolean)
isCanceled()
isDone()
interface RunnableFuture
class FutureTask
interface CompletableFuture # parallel
static CompletableFuture<U> supplyAsync()
static CompletableFuture<Void> allOf(CompletableFuture<U>...)
static CompletableFuture<Void> anyOf(CompletableFuture<U>...)
T join()
CompletableFuture<U> thenApply()
CompletableFuture<Void> thenAccept(Consumer<T>)
interface ThreadFactory
Thread newThread(Runnable)
class DefaultThreadFactory
interface Executor
void execute()
interface ExecutorService
shutdown()
shutdownNow()
isShutdown()
isTerminated()
awaitTermination(long, TimeUnit)
Future submit(Callable<T>)
Future submit(Runnable, T) # 手动设个result
submit(Runnable)
invokeAll(Collection<Callable<T>>)
invokeAll(Collection<Callable<T>>, long, TimeUnit)
invokeAny(Collection<Callable<T>>)
invokeAny(Collection<Callable<T>>, long, TimeUnit)
abstract AbstractExecutorService
RunnableFuture<T> newTaskFor(Runnable, T)
RunnableFuture<T> newTaskFor(Callable<T>)
T doInvokeAny(Collection<Callable<T>>, boolean timed, long)
submit()
invokeAll(Collection<Callable<T>>)
# 忽略CancellationException, ExecutionException,其它异常抛出并取消未完成任务
invokeAll(Collection<Callable<T>>, long, TimeUnit)
# 忽略CancellationException, ExecutionException, TimeoutException,其它异常抛出并取消未完成任务
invokeAny(Collection<Callable<T>>)
invokeAny(Collection<Callable<T>>, long, TimeUnit)
class ThreadPoolExecutor # 线程池+任务队列
# 任务顺序: 核心线程, 任务队列,起新线程,拒绝策略
class ScheduledThreadPoolExecutor # 用DelayedWorkQueue
scheduleAtFixedRate(()->{}, int initial, int period, TimeUnit)
class ForkJoinPool
execute(ForkJoinTask)
interface ScheduledExecutorService
[class ScheduledThreadPoolExecutor]
interface CompletionService # 不阻塞全部任务,已有结果入队列
poll()
class ExecutorCompletionService
static class Executors
newSingleThreadExecutor() # 为了用任务队列和生命周期管理
newCachedThreadPool() # 超时60s, max为MAX_VALUE, 任务不堆积场景
newFixedThreadPool()
newScheduledThreadPool() # AbstractQueuedSynchronizer
newWorkStealingPool() # ForkJoinPool, go的M,G,P
# 每个线程单独队列, 尾部偷加尾部
创建线程
#
# 继承
class MyThread extendws Thread {
@Override
public void run(){}
}
new MyThread().start();
# 组合
class MyRun implements Runnable {
@Override
public void run(){}
}
new Thread(new MyRun()).start();
# 返回值
class myCall implements Callable<String> {
@Override
public String call(){}
}
FutureTask = ft = new FutureTask<String>(new MyCall())
new Thread(ft).start();
ft.get();
# 线程池
// execute无返回值
ExecutorService service = Executors.newCachedThreadPool()
service.execute(()->{});
// submit有返回值
Future<String> f = service.submit(new MyCall());
service.shutdown();
线程状态
#
NEW
RUNNABLE # 可调度
READY
RUNNING
WAITING # 等待唤醒,忙等待(一直占CPU)
o.wait()
t.join()
LockSupport.park()
Lock.lock()
o.notify()
o.notifyAll()
LockSupport.unpark()
Lock.unlock()
TIMED WAITING
Thread.sleep(time)
o.wait(time)
t.join(time)
LockSupport.parkNanos()
LockSupport.parkUntil()
BLOCKING # 阻塞等待(不占CPU但经过OS调度)
synchronized
TERMINATED
线程打断
#
方法
interrupt() # 设置打断标记位
isInterrupted() # 检查标记位
static interrupted() # 检查当前线程标记位,并重置
检测当前线程打断标记的方法 # 抛异常并重置
Thread.sleep()
o.wait();
o.join();
ReentrantLock
lockInterruptibly()
不检测当前线程打断标记的方法
synchronized # 不是代码实现检测不了
ReentrantLock
lock()
强制打断
Thread
stop() # 已废弃, 立即释放所有锁
suspend() # 已废弃,强制暂停,所有锁不释放容易死锁
resume() # 已废弃,强制恢复
volatile
判断数字不准,有同步的时间延迟, interrupt()也有延迟
也需要代码中判断, 但interrupt()有wait()等系统方法支持
线程间通信
#
通知
#
# synchronized wait() notify(), CountDownLatch, LockSupport
volatile List c = new ArrayList();
final Object lock = new Object();
new Thread(() -> {
synchronized(lock) {
if (c.size() != 5) {
lock.wait();
}
lock.notify(); // 唤醒t1
}
}, "t2").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
synchronized(lock) {
for (int i = 0; i < 10; i++) {
c.add(new Object());
if (c.size() == 5) {
lock.notify();
lock.wait(); // 让出sychronized锁
}
}
}
}, "t1").start();
生产消费
#
# 优化count可以用CAS加(有ABA问题)
class MyContainer<T> {
final private List<T> list = new LinkedList<>();
final private int MAX = 10;
private int count = 0;
public synchronized void put(T t) {
while(list.size() == MAX) {
this.wait(); // 期间可能有add()
}
list.add(t);
count++;
this.notifyAll(); // 应该只唤醒消费者
}
public synchronized T get() {
T t = null;
while(list.size() == 0) {
this.wait();
}
t = list.removeFirst();
count--;
this.notifyAll(); // 应该只唤醒生产者
return t;
}
}
# 同步容器, ReentrantLock Condition
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
public void put(T t) {
try {
lock.lock();
while(list.size() == MAX) {
producer.await();
}
list.add(t);
count++;
consumer.signalAll();
} finally {
lock.unlock();
}
}
public T get() {
T t = null;
try {
lock.lock();
while(list.size() == 0) {
consumer.await();
}
t = list.removeFirst();
count--;
producer.signalAll();
} finally {
lock.unlock();
}
return t;
}
协程
#
quasar库 # 需要设javaagent, 每个fiber生成栈
fiber = new Fiber<Void>()
fiber.start()
并发API
#
Stream
#
parallelStream() # ForkJoinPool
ThreadLocal
#
内部类
ThreadLocalMap<ThreadLocal, Object>
# 存在每个线程里。场景如声明式事务拿conn
# key是弱引用指向ThreadLocal, value是强引用。
Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
方法
set(T)
内存泄露问题
ThreadLocal<M> tl = new ThreadLocal();
tl.set(new M());
tl = null;
# threadLocalMap中key弱引用回收, value不回收
tl.remove();
# 必需remove()否则内存泄露, threadLocalMap中value强引用,tl回收了也一直存在
PipedStream
#
# 效率不高
PipedInputStream
connect(PipedOutputStream)
read(byte[])
PipedOutputStream
write(byte[])
JMH
#
# Java Microbenchmark Harness
概念
Warmup # 预热
Mesurement # 总执行次数
Timeout # 每次执行超时时间
Threads # fork线程数
Benchmark mode # 模式
Benchmark # 方法名
环境变量
TEMP或TMP # JHM临时文件存放
使用
@Benchmark
@Warmup(iterations = 2, time = 3) # 执行2次, 每次隔3秒
@Fork(5) # 多少线程
@BenchmarkMode(Mode.Throughput) # 显示每秒多少次
@Measurement(iterations = 10, time = 3) # 共测10次, 每次隔3秒
public void testA() {
}
Disruptor
#
介绍
CAS, 环形数组Buffer
数组用sequence定位修改快,也避免了头尾加锁
直接覆盖降低GC
覆盖前有等待策略
单机MQ
发布订阅模式
观察者模式
EventFactory
会调工厂提前分配内存, 使用时不new而是修改值,提高效率, 降低GC
使用
class MyEvent {}
class MyEventFactory implements EventFactory<MyEvent> {
@Override
public MyEvent newInstance() {}
}
class MyEventHandler implements EventHandler<MyEvent> {
@Override
void onEvent(MyEvent, long sequence, boolean endOfBatch) {}
}
class MyExceptionHandler implements ExceptionHandler<MyEvent> {
@Override
void handleEventException()
@Override
void handleOnStartException()
@Override
void handleOnShutdownException()
}
disruptor = new Disruptor<>(factory, 1024, Executors,defaultThreadFactory())
disruptor = new Disruptor<>(MyEvent::new, 1024, Executors,defaultThreadFactory())
disruptor = new Disruptor<>(factory, 1024, Executors,defaultThreadFactory(),
ProducerType.SINGLE, new BlockingWaitStrategy())
# 默认ProducerType.MULTI, SINGLE可提高性能不用加锁
// 消费
disruptor.handleEventsWith(handler1, handler2)
disruptor.handleEventsWith((event,seq,end)->{})
disruptor.handleExceptionsFor(handler1).with(excptionHandler1)
disruptor.start()
// 生产
ringBuffer = disruptor.getRingBuffer()
sequence = ringBuffer.next()
event = ringBuffer.get(sequence)
event.set("")
ringBuffer.publish(sequence)
translator = new EventTranslator<>() {
@Override
void translateTo(event, sequence) {
event.set("")
}
}
ringBuffer.publishEvent(translator)
ringBuffer.publishEvent((event,seq, "") -> event.set(l), "")
等待策略
BlockingWaitStrategy # 阻塞直到再次唤醒
BusySpinWaitStrategy # 自旋等待
SleepingWaitStrategy # sleep等待
LiteBlockingWaitStrategy # 同BlockingWaitStrategy减少加锁次数
LiteTimeoutBlockingWaitStrategy # 同LiteBlockingWaitStrategy加超时
PhasedBackoffWaitStrategy
TimeoutBlockingWaitStrategy # 同BlockingWaitStrategy加超时
YieldingWaitStrategy # 尝试100然后Thread.yield()
源码分析
#
ThreadPoolExecutor
#
new ThreadPoolExecutor()
int corePoolSize # 核心线程数, 永远存活。可设置参与回收
int maximumPoolSize # 最大线程数
long keepAliveTime # 生存时间
TimeUnit
BlockingQueue<Runnable> # 任务队列
ThreadFactory # 线程工厂, 设线程名
RejectedExecutionHandler # 拒绝策略
Abort # 抛异常
Discard # 忽略掉
DiscardOldest # 忽略掉排除最久的
CallerRuns # 调用者线程执行, 再多就阻塞
AtomicInteger ctl
# 高3位线程池状态,低29位线程数量
void execute()
判断添加核心线程
放入队列成功
拒绝或添加非核心线程
添加非核心线程失败
拒绝
boolean addWorker(Runable, boolean)
线程数量加1
添加Worker
加锁
加线程
启动
class Worker extends AbstractQueuedSynchronizer implements Runnable
# 本身是AQS锁, 被多任务(线程)访问
Tread thread
ForkJoinPool
#
abstract class ForkJoinTask
ForkJoinTask<V> fork()
V join()
abstract class RecursiveAction # 无返回值
void compute()
abstract class RecursiveTask # 有返回值
例子
class MyTask extends RecursiveTask<Long> {
int start;
int end;
@Override
Long compute() {
if (end - start <= MAX_NUM) {
return sum
}
subTask1 = new MyTask(start, mid)
subTask2 = new MyTask(mid, end)
subTask1.fork()
subTask2.fork()
return subTask1.join() + subTask2.join();
}
}
fjp = new ForkJoinPool()
task = new MyTask(0, nums.length)
fjp .execute(task)
result = task.join()
Oct 9, 2018
基础
#
定义
JVM规范说明书, JVMS(Java Virtual Machine Specifications)
Java语言规范, JLS(Java Language Specification)
虚拟机
指令集
内存管理
过程
x.java -> javac -> x.class -> ClassLoader -> (字节码解释器、JIT) -> 执行引擎
JVM语言
Scala, Kotlin, Groovy, Clojure, jython, jruby等100多个
JVM实现
HotSpot # Oracle官方, 8之后收费, OpenJDK为开源版本
Jrockit # Oracle收购, 合并入HotSpot
J9 # IBM
Microsoft VM
TaobaoVM # HotSpot深度定制
LiquidVM # 直接匹配专门硬件
azul zing # 收费,快, GC1mm, HotSpot参考写的G1
JRE, JDK
JRE = jvm + core lib
JDK = JRE + development kit
JVM构造
#
指标
#
吞吐量: 代码时间 / (代码时间 + GC时间)
响应时间: STW越短, 响应时间越好
指令(Instructions)
#
分类
基于栈的指令类 # 简单, HotSpot
基于寄存器的指令集 # 复杂但快, HotSpot局部变量表
8大原子操作(JSR-133已放弃这个描述,但指令没变化)
lock # 主内存,标识变量线程独占
unlock # 主内存,解锁独占
read # 主内存,读到工作内存
load # 工作内存,read后的值放入线程本地变量副本
use # 工作内存,传值给执行引擎
assign # 工作内存,执行引擎结果赋值给线程本地变量
store # 工作内存,存值到主内存给write备用
write # 主内存,写变量值
方法指令 # 在methods的Code中罗列
aload_0 # 变量表第0项入栈
invokespecial #1 # 调private(无多态)的方法
invokevirtual # 调有多态可能性的方法
invokestatic # 调静态方法
invokeinterface # 调interface方法
invokedynamic # 1.7加入,定义类似函数指针时生成(但每个函数都创建了类)
调用动态产生的类
lambda
反射
scala等JVM语言
CGLib ASM
组成
bootstrapMethod
方法签名
<1.8的bug # 类产生于Perm Space,内存不回收
for(;;) {I j = C::n;}
return # 方法返回
bipush 8 # byte扩展成int类型,放到方法栈中
sipush 200 # short
istore_1 # 出栈,放到下标为1的局部变量表
iload_1 # 局部变量表下标1位置值压栈
iinc 1 by 1 # 局部变量表1位置值+1
iadd # 出栈两个,相加压栈
new # new对象, 地址压栈
dup # 复制栈顶并压栈
pop # 弹出栈顶
if_icmpne 7 # int值比较,不等时跳到第7条指令
mul # 乘法
sub # 减法
class结构
#
工具
#
javap -v a.class
jetbrain jclasslib
jetbrain BinEd
JBE # 可编辑
二进制
#
Magic Number(4字节)
cafe babe
Minor Version(2字节) # 小版本
Major Version(2字节) # 大版本
JDK1.7是51.0
JDK1.8是52.0
constant_pool_count(2字节)
# 长度constant_pool_count-1的表
constant_pool # 索引、tag、类型
1 CONSTANT_Utf8_info # 存一些描述字符串
2 标记
3 CONSTANT_Integer_info
4 CONSTANT_Float_info
5 CONSTANT_Long_info
6 CONSTANT_Double_info
7 CONSTANT_Class_info
8 CONSTANT_String_info
9 CONSTANT_Fieldref_info
10 CONSTANT_Methodref_info # 方法引用
指向CONSTANT_Class_info
指向CONSTANT_NameAndType_info
11 CONSTANT_InterfaceMethodref_info
12 CONSTANT_NameAndType_info # 方法名与类型
15 CONSTANT_MethodHandle_info
16 CONSTANT_MethodType_info
18 CONSTANT_InvokeDynamic_info
access_flags(2字节) # bitmap按位与组合
# class的修饰符
ACC_PUBLIC 0x0001 public
ACC_FINAL 0x0010 final
ACC_SUPER 0x0020 JDK1.0.2之后必须为真, 表示invokespectial用新语义
ACC_INTERFACE 0x0200 是否接口
ACC_ABSTRACT 0x0400 抽象类
ACC_SYNTHETIC 0x1000 编译器自动生成
ACC_ANNOTATION 0x2000
ACC_ENUM 0x2000
this_class(2字节)
存名字对应指向常量池序号
super_class(2字节)
存名字对应指向常量池序号
interfaces_count(2字节)
interfaces
fields_count(2字节)
fields
access_flags(2字节)
name_index # 存常量池索引
descriptor_index
byte B
char C
double D
float F
int I
long L
short S
boolean Z
void V
Object Ljava/lang/Object
数组
一维数组 [B
多维数组 [[C
attributes_count # 赋加属性
attributes
methods_count(2字节)
methods
access_flags(2字节)
name_index
descriptor_index # 先参数列表,后返回值
void m() -> ()V
String toString() -> Ljava/lang/String;
attributes_count
attributes # 赋加属性
Code # 指令列表, 一般先压栈this(aload_0)
LineNumberTable
LocalVariableTable
attributes_count(2字节)
attributes
Agent
#
例子
打包 a.jar
MANIFEST.MF
Premain_Class: MyAgent
public class MyAgent {
public static Instrumentation inst;
public static void premain(String agentArgs, Instrumentation _inst) {
inst = _inst;
}
}
JVM参数 -javaagent: a.jar
使用 MyAgent.inst
JMM(Java Memory Model)
#
内存
#
运行时区域
#
# Runtime data areas
分类
Program Counter # 程序计数器,下一条指令位置
Method Area # 方法区,线程间共享
存储
Class元信息
代码编译信息, JIT编译信息
常量池(Runtime Constant Pool) # 常量池在运行时存放区
版本区别
Perm Space(<1.8) # 要设定大小, 会溢出报错
存字符串常量
lambda生成临时类永远存在
Full GC不清理
Meta Space(>=1.8) # 自动大小无上限
字符串常量位于堆
会触发Full GC
JVM stacks # 线程栈
Frame(栈帧) # 一个方法一个栈帧
Local Variable Table # 局部变量表, 方法内的局部变量,值在常量池
默认第0个为this
Operand Stack # 操作数栈
Dynamic Linking # 指向调用方法的 运行时常量池的符号连接
return address # 当前方法执行完的返回地址
Native Method Stacks # C/C++方法栈
Direct Memory # 直接内存
Heap # 堆, 线程间共享
屏障
#
CPU屏障
JVM规范
LoadLoad # 上load和下load不能重排
StoreStore
LoadStore
StoreLoad # 最强
对象
#
对象内存存储
#
普通对象
对象头: markword 8字节
ClassPointer # 指向Class对象, 启用压缩4字节,不启用8字节
实例数据
引用类型 # 启用压缩4字节,不启用8字节
Padding: 对齐8的倍数
数组对象
对象头
ClassPointer
数组长度4字节
数组数据
Padding
对象头
# 32位64位(25位没用到),内容不同
锁标志位2位 # 根据锁标志位判断存储内容
01 无锁/偏向锁
00 轻量级锁
10 重量级锁
11 GC标记
是否偏向锁1位
剩余位
无锁状态
对象hashCode(25位或31位)
没重写过时默认计算(System.identityHashCode())
重写过的hashCode()结果不存在这里
分代年龄
轻量级锁
指向栈中锁记录的指针
重量级锁
指向互斥量(重量级锁)的指针
偏向锁
线程ID 23位
Epoch 2位
分代年龄4位(所以分代年龄只有15)
其它问题
对象计算过hashCode,对象不能进入偏向锁状态(位已经被占了)
实验工具 javaagent
对象定位
#
句柄池 # 指向句柄,句柄有对象指针和class指针, 三色标记GC提高效率
直接指针 # 指向对象,对象指class, HotSpot使用
并发
#
硬件层数据一致性
#
硬件结构
L0寄存器 # 1 cycles
L1高速缓存 # 3-4 cycles, 1ns
L2高速缓存 # 10 cycles, 3ns
L3高速缓存 # 40-45 cycles, 15ns, 在主板
(QPI总线传输) # 20ns
L4主存 # 60-80ns
L5磁盘
L6远程文件存储
数据不一致 # 从L2多CPU开始
锁总线(bus lock)
CPU缓存一致性协议(如intel MESI)
volatile
#
工具
hsdis # HotSpot Dis Assembler, 虚拟机字节码对应汇编
bytecode
ACC_VOLATILE
JVM
StoreStoreBarrier
volatile写操作 # 上边写完再写,写完下边再读,写一致
StoreLoadBarrier
LoadLoadBarrier
volatile读操作 # 上边读完再读,读完下边再写,读一致
LoadStoreBarrier
OS
windows
lock
linux
上下屏障,最后lock
synchronized
#
bytecode
方法修饰
synchronized
代码
monitorenter
monitorexit
JVM
C/C++实现,会调用OS的同步机制
OS
lock
happens-before原则
#
# Java要求指令不能重排的几种情况
as if serial
#
# 不管如何重排序,单线程执行结果不变
过程
#
编译
#
过程
代码 -> bytecode -> JVM指令 -> OS指令
解释器(bytecode intepreter)
JIT(Just In-Time compiler)
混合模式
解释器 + 热点代码编译
热点代码检测
方法计数器
循环计数器
加载
#
HotSpot C++代码加载
class对象加载到MethodArea
metaspace(JDK1.8)
permGeneration(JDK1.8之前)
class加载过程
loading # 读到内存
linking
verification # 校验
preparation # 静态变量赋默认值
resolution # 解析, loadClass()可指定是否解析。常量池的符号引用转换成内存地址引用
initializing # 静态变量赋初始值,执行静态代码
对象加载
new过程
class加载
申请对象内存
成员变量赋默认值
调用构造方法<init>
成员变量顺序赋初始值
执行构造方法语句(先super)
双亲委派
过程
类名一层层向上找
找不到时,一层层找再向下委派找
都不能加载时, 抛ClassNotFound
为什么
安全, 自定义类不能覆盖
已加载不用重复加载
父加载器
不是类加载器的加载器
不是父类
是组合的parent对象
打破
为什么
JDK1.2之前都重写loadClass()
thread.setContextClassLoader()指定线程上下文classLoader
热启动/热部署(OSGi tomcat)加载同一类不同版本
做法
重写loadClass(), new多个ClassLoader
类加载器
Bootstrap # 加载核心类 lib/rt.jar charset.jar等, C++实现所以get时为null
如加载String
Extension # 加载扩展jar jre/lib/ext/*.jar, 由-Djava.ext.dirs指定
App # 加载classpatch指定内容
Custom ClassLoader # 自定义ClassLoader
加载路径环境变量 # 来自Launcher源码
Bootstrap.ClassLoader sun.boot.class.path
ExtensionClassLoader java.ext.dirs
AppClassLoader java.class.path
API
Class
getClassLoader()
ClassLoader # findInCache() -> parent.loadClass() -> findClass()
private final ClassLoader parent
loadClass # 热加载
Launcher
$AppClassLoader
$ExtClassLoader
自定义类加载器
class MyClassLoader extends ClassLoader {
@Override
Class findClass(String) {
return defineClass()
}
}
懒加载 # JVM未规定什么时候加载,但规定了什么时候初始化
初始化
new getstatic putstatic invokestatic指令,访问final变量除外
java.lang.reflect对类进行反射调用
初始化子类时,父类首先初始化
虚拟机启动时,被执行的主类
动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic, REF_putstatic, REF_invokestatic的方法句柄时, 该类初始化
GC
#
引用方式(强软弱虚)
#
软引用
# 内存不够用时回收,用做缓存
# -Xms20M -Xmx20M
SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
System.gc();
m.get();
new byte[1024*1024*15]
m.get();
弱引用
# 只要gc就回收,用做容器如WeakHashMap(key是弱引用), ThreadLocalMap的key
WeakReference<M> m = new WeakReference<>(new M());
System.gc();
m.get();
虚引用
# 值被回收时放入队列来通知, 用来触发回收堆外内存(用Unsafe的freeMemory())
# 如NIO的直接内存DirectByteBuffer
PhantomReference<M> r = new PhantomReference<>(new M(), QUEUE);
r.get() // 自己写永远返回null
对象分配过程
#
向栈分配 # 不要调参数
好处
不必GC
条件
线程私有小对象
无逃逸
可标量替换(基本类型替换整个对象)
过大,分配到老年代
线程本地分配 # TLAB(Thread Local Allocation Buffer), 不要调参数
好处
为了减少线程争用
条件
小对象
占用eden, 默认每个线程占1%
伊甸区
s1,s2
次数 # 最大15(对象头上空间决定)
Parallel Scavenge 15
CMS 6
G1 15
动态年龄
eden + s1 复制到s2, 超过s2总内存一半时,年龄最大的对象进入老年代
分配担保
YGC时survivor区空间不够, 直接进入老年代
GC清除或到老年代
GC分代过程
#
YGC -> s0
YGC, eden + s0 -> s1
年龄足够 -> old
s区装不下 -> old
YGC, eden + s1 -> s0
old满了 -> FGC
常见的回收器
#
概念
Safe Point # STW时机
没有无STW的回收器
分代
Young
Serial # 第一个GC
STW, 单线程串行回收
Parallel Scavenge
STW, 并行回收
ParNew # Parallel New
增强PS, 以配合CMS并行回收, CMS到某阶段时PN运行
Old
Serial Old
特点
适用几十M
mark-sweep-compact,单线程
Parallel Old
特点
适用几个G
mark-sweep-compact,多线程
CMS # concurrent mark sweep, 1.4后期引入, JDK11取消
特点
适用20G
多线程并行回收, 并发回收(GC与程序同时运行),降低STW时间(200ms)
不足 # 问题多,没有版本默认CMS
浮动垃圾
碎片多,新对象分配不下时,使用SerialOld
设低GC阈值回收浮动垃圾
清理过程
初始标记(initial mark)
STW, 单线程, 标记根对象
[预标记] # Card Marking, 把Card标为Dirty
并发标记(concurrent mark)
不STW, 多线程, 执行多次
重新标记(remark) # 处理并发标记过程中的变化
STW, 多线程,
并发清理(concurrent sweep) # 过程中产生的浮动垃圾, 下次回收
不STW, 多线程,
[整理阶段]
日志
[GC(Allocation Failure)[ParNew:6144K->640K(6144K)], 0.02 secs] 6585K->2770K(19840K),0.02 secs][Times:user=0.02 sys=0.00, real=0.02 secs]
6144K->640K(6144K): 回收前 回收后 总容量
6585K->2770K(19840K): 堆回收前 回收后 总大小
[GC (CMS Initial Mark)]
[1 CMS-initail-mark]
[CMS-concurrent-mark-start]
[CMS-concurrent-preclean-start]
[GC (CMS Final Remark)]
[YG occupancy] # 清理后年轻代占用及容量
[Rescan(parallel)] # STW下标记存活对象
[weak refs processing] # 弱引用处理
[class unloading] # 卸载用不到的class
[scrub symbol table] # 清理常量池
[scrub string table] # 清理常量池
[1 CMS-remark] # 清理后老年代占用及容量
[CMS-concurrent-sweep-start]
[CMS-concurrent-reset-start]
不分代
G1 # Garbage First, 1.7引入, 1.8完善, 1.9默认
特点
适用上百G
STW 10ms回收
容易预测STW时间
低latency, 但throughput也低
并发回收, 三色标记
只逻辑分代, 不物理分代
内存分Region, Region组成不同大小块,块在逻辑分代中
Eden和Old区的内存映射会变化
动态新老代空间 # 如果YGC频繁,就Young调大
不要手工指定, 是G1预测YGC停顿时间的基准, 停顿时间通过参数设置
概念
CSet # Collection Set
可回收Region集合, 回收时存活的对象被移动
占堆空间不到1%
RSet # Remembered Set
用于找到谁引用当前对象(对象级别), 记录其他Region的引用
赋值时有GC写屏障 # 非内存屏障
CardTable # YGC定位垃圾,要从Root查所有Old区对象,效率低
Old区对象引用Young区时, bitmap标DirtyCard。YGC时只扫描DirtyCard
MixedGC # 默认45%, 同CMS
初始标记
重新标记
筛选回收 # 筛选Region回收,有用对象复制到其它Region
日志
[GC pause (G1 Evacuation Pause) (young) (initial-mark)] # 复制存活对象, initial-mark在MixedGC时有
[GC concurrent-root-region-scan-start] # 混合回收
[GC concurrent-mark-start] # 混合回收
[Full GC (Allocation Failure)] # 无法evacuation时, G1中很严重
ZGC # JDK11, 不分代(SingleGeneration)
特点
STW设计10ms, 实际1ms
适用4T(JDK13 16T)
内存分块(有大有小)
概念
没有RSet, 改进了SATB指针
Shenandoah # JDK11
Epsilon # JDK11, debug用
Zulu
组合
S + SO
PS + PO # 1.8默认, 10G内存10+秒
PN + CMS
算法
#
定位
引用计数(ReferenceCount)
循环引用问题 # 内部互相引用,没有外部引用
根可达算法(RootSearching)
线程栈变量
静态变量
常量池
JNI指针 # 本地方法用到的对象
并发标记
三色标记
白 # 未被标记
灰 # 自身被遍历到,成员变量未被遍历到
黑 # 自身、成员变量都被遍历到
漏标问题
两个必要条件
黑引用白后,灰对白的引用断开
算法
incremental update # 增量更新,关注引用的增加, CMS用的
增加引用后,标记为灰, 重新标记阶段再扫描
缺点是灰色还要重新扫描
SATB snapshot at the beginning # 关注引用的删除, G1用的
开始时快照, 引用消失时,引用推到堆栈, 下次扫描还扫白色对象
优点是只扫描修改过的对象, 看RSet中有没有引用
颜色指针 # 信息记在指针里
租户隔离, Session Base GC # Web专用, 基于session, session断开后删除
各GC应用
CMS
三色标记 + Incremental Update
G1
三色标记 + SATB
ZGC
颜色指针 + 写屏障
Shenandoah
颜色指针 + 读屏障
清除
标记清除(Mark-Sweep) # 一遍标记,一遍清理, 适合老年代
算法简单,戚对象多时效率高
两遍扫描效率低,容易产生碎片
拷贝(Copying) # 存活对象copy到新内存, 旧内存直接清理,适合伊甸区(新生代)
适用存活对象少的情况
内存减半
标记压缩(Mark-Compact) # 有用的填到前边去空隙去, 适合老年代
不会碎片,不会内存减半
扫描两次,还要移动
分代模型
分代模型 # -Xms -Xmx设置大小
new/young(新生代) # MinorGC/YGC, -Xmn设置大小, 默认占比1
eden(伊甸) # 默认占比8
survivor x 2 # 默认每个占比1
old(老年代) # MajorGC/FullGC, 1.8默认占比2, 之前是3
tenured(终身)
methodArea # 1.7永久代, 1.8元数据区
各JVM的分代模型
Epsilon ZGC Shenandoah不是分代模型
G1是逻辑分代,物理不分代
其他都是逻辑分代 + 物理分代
调优(Tuning)
#
前提
从业务场景开始
无监控(能压测), 不调优
目标
减少FGC
确定倾向 # 吞吐量, 或响应时间
吞吐量好: PS + PO
响应时间好: G1 或 PN + CMS # G1吞吐量少10%
组成部分
JVM预规划
优化JVM运行环境(慢、卡顿)
解决JVM运行时出现的问题(OOM)
步骤
熟悉业务场景
响应时间
吞吐量
选择回收器组合
计算内存需求(小的快,大的少gc)
选CPU
设定年代大小、升级年龄
设定日志参数
观察日志情况
问题分析
#
工具
#
CPU经常100%
top查进程CPU(top)
进程中线程CPU(top -Hp)
导出该线程堆栈(jstack)
查哪个方法(栈帧)消耗时间(jstack)
内存高
导出堆内存(jmap)
分析(jhat jvisualvm mat jprofiler ...)
监控JVM
jstat jvisualvm jprofiler arthas top ...
网管: Ansible
流程
网管报警
top -Hp 进程号
jstack 进程号 # 列出所有线程号, 线程状态
WAITING, 一直等不到, BLOCKED, 拿不到锁
waiting on <0x0000> (a java.lang.Object) # 找到目标等待的线程
jstack -l 16进制线程号
jps
jinfo 进程号 # 列JVM信息
jstat -gc 进程号 500 # 每500ms打印一次gc信息
jmap -histo 进程号 | head -20 # 列所有对象
有性能消耗,但不很高,可以在线执行
jmap -dump:format=b, file=x pid # 导出转储文件
内存特别大时,jmap会卡顿
多个服务器可用,停一个不影响
设定HeapDumpOnOutOfMemoryError产生堆转储文件
在线定位(中小型公司用不到)
jhat -J-mx512M x.hprof # 分析堆dump文件, 有OQL
arthas # 在线定位
启动
java -jar arthas-boot.jar
常用命令 # 没有集成jmap功能
jvm # jinfo
thread # jstack
thread 1
dashboard # top
heapdump # jmap -dump
dump
redefine # 热替换
目前只能改method实现,不能改方法名与属性
jad # 反编译类
看动态代理生成的类
看第三方类
看版本
sc # search class, 显示class信息
watch # watch method
MAT # 分析dump文件
jprofiler
jconsole # 需要JMX
JMX会消耗性能生产服务器不开
JMX图形界面只用于压测
jvisualVM # 需要JMX, 可分析dump文件
内存
#
现象
OOM崩溃
CPU飙高, 不断FGC
线程池不当运用
加内存反而卡顿
GC, 应该用CMS或G1替换 PS+PO
JIRA不停FGC, 没定位出来
扩内存到50G, GC换G1, 重启
tomcat server.max-http-header-size过大
默认4096, 每个请求都分配
lambda表达式导致方法区溢出
java.lang.OutofMemoryError: Compressed class space
disruptor不释放缓存
使用Unsafe分配内存, 直接内存溢出
-Xss设定小, 栈溢出
重写finalize()引发GC
finalize()耗时长, GC时回收不过来,不停GC
内存不到10%,频繁FGC
有人显式调用System.gc() # 不定时调,但会频繁调
大量线程, native thread OOM
减少堆空间,留更多系统内存产生native thread
G1产生FGC
降低MixedGC触发的阈值 # 默认45%
扩内存
提高CPU # 回收快
HotSpot参数
#
辅助
-XX:+PrintCommandLineFlags -version # 打印启动参数, -version是随便一个命令
-XX:+PrintFlagsFinal -version # 打印所有XX参数
-XX:+PrintFlagsInitial # 打印默认参数
-XX:+PrintVMOptions # 显示VM启动参数
- # 标准参数
-X # 显示非标参数
-XX # 显示不稳定参数
内存
-XX:+HeapDumpOnOutOfMemoryError # OOM时产生堆转储文件
-Xms40M # 堆起始大小
-Xmx60M # 堆最大大小, 最好和Xms一样以免堆弹大弹小
-Xmn # 年经代
-Xss # 栈空间
-XX:InitialHeapSize # 起始堆大小,自动算
-XX:MaxHeapSize # 堆最大大小,自动算
内存模型
-XX:-DoEscapeAnalysis # 去逃逸分析
-XX:-EliminateAllocations # 去标量替换
-XX:-UseTLAB # 去tlab
-XX:TLABSize # 设置TLAB大小
-XX:+PrintTLAB
-XX:MaxTenuringThreshold # 进老年代(升代)回收次数, 最大值15, CMS默认6,其它默认15
对象和类
-XX:+UseCompressedClassPointers # class指针压缩
开启时4字节,不开启时8字节
-XX:+UseCompressedOops # 引用类型指针压缩, Ordinary Object Pointers
开启为4字节,不开启时8字节
-verbose:class # 类加载详细过程
-XX:PreBlockSpin # 锁自旋次数
编译
-Xmixed # 混合模式
-Xint # 解释模式
-Xcomp # 编译模式
-XX:CompileThreshold = 10000 # 检测热点代码次数
GC
-XX:+PrintGC # 打印GC信息
PrintGCDetails # 打印GC更详细
PrintGCTimeStamps # 打印GC时间
PrintGCCauses # GC原因
PrintHeapAtGC
PrintGCApplicationConcurrentTime # GC应用程序时间
PrintCApplicationStoppedTime # 打印STW时长
-XX:+PrintReferenceGC # 打印回收多少种引用类型
-XX:+UseConcMarkSweepGC # 用CMS
-XX:+DisableExplictGC # System.gc()不管用
Parallel常用
-XX:SurvivorRatio # 新生代Eden区和Surivor区的比例
-XX:PreTenureSizeThreshold # 大对象到底多大
-XX:+ParallelGCThreads # 并发线程数, 默认是CPU数
-XX:+UseAdaptiveSizePolicy # 自动调所有区比例
CMS常用
-XX:ParallelCMSThreads # 并发线程数,默认是CPU数一半
-XX:CMSInitiatingOccupancyFraction 92% # 老年代占多少时触发GC, 1.8 92%, 之前68%
设小一点,清除浮动垃圾
过大时,栈分配不下,Promotion Failure,触发FGC
-XX:+UseCMSCompactAtFullCollection # GC时压缩,避免碎片片
-XX:CMSFullGCsBeforeCompaction # 多少次GC后压缩
-XX:+CMSClassUnloadingEnabled # 回收方法区
-XX:CMSInitiatingPermOccupancyFraction # 到什么比例时进行Perm回收, 1.8之前
GCTimeRatio # GC占程序运行时间的百分比
-XX:MaxGCPauseMillis # GC停顿时间, CMS会减少年轻代大小
G1
-XX:MaxGCPauseMillis # STW时间, 区别CMS, G1会调整Young区的块数
GCTimeRatio
-XX:GCPauseIntervalMillis # STW之间间隔时间
-XX:+G1HeapRegionSize # Region大小, 1 2 4 8 16 32, 逐渐增大, GC间隔更长, 每次GC时间更长
ZGC是动态调整的
G1NewSizePercent # 新生代最小比例, 默认5%
G1MaxNewSizePercent # 新生代最大比例,默认60%
ConcGCThreads # GC线程数
InitiatingHeapOccupancyPercent # 启动GC的堆空间占用比例
JMX监控
-Djava.rmi.server.hostname=192.168.1.1
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=11111
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
调优 # 参数越来越少
JVM参数800个
CMS参数300个
G1参数100个
ZGC更少
Zing1个
GC组合参数
-XX:+UseSerialGC
S + SO
-XX:+UseParNewGC # 已废弃
PN + SO
-XX:+UseConc(urrent)MarkSweepGC
PN + CMS + SO
-XX:+UseParallelGC # 1.8默认
PS + PO
-XX:+UseParallelOldGC
PS + PO
-XX:+UseG1GC
G1
日志参数
-Xloggc:/logs/xx-xx-%t.log
-XX:+UseGCLogFileRotation # 5个满了,覆盖第一个
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=1024M
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCCause
HotSpot日志
#
GC # PrintGCDetails
[GC
GC表示YGC, Full GC是FGC
(Allocation Failure)
原因
[DefNew:4544k->259k(6144k), 0.0873 secs]
DefNew表示年轻代, 回收前后的大小, 6144是年轻代总大小,回收时间
4544k->4356k(19840k), 0.0812 secs]
堆的前后大小, 19840是堆总空间, 回收时间
[Times: user=0.01 sys=0.00, real=0.01 secs]
linux time命令,用户态时间,内核态时间,总时间
异常退出dump堆
def new generation total 6144k, used 5504k [0x00, 0x00, 0x00]
新生代总共多少,用了多少。内存起始地址,使用空间结束地址,整体空间结束地址
total = eden + 1个survivor
eden space 5504k, 100% used []
eden
from space 640k, 0% used []
s0
to space 640, 0% used []
s1
tenured generation total 13696k, used 13312k []
old
the space 13696k, 97% used []
old
Metaspace used 2538k, capacity 4486k, committed 4864k, reserved 1056768k
used真正使用的大小
capacity目前指定的容量
committed 表示预先占用的大小
reserved表示共保留的大小
class space used 275k, capacity 386k, committed 512k, reserved 1048576k
metaspace中存class的部分
xml
#
介绍
可扩展性标记语言,使用DTD和XML Schema标准化XML结构
优点: 格式统一,符合标准,用于互不兼容系统
缺点: 格式复杂,占流量大,解析占资源
解析器
DOM # 树形结构
SAX # 事件模型
标签头
<?xml version="1.0" encoding="utf-8"?>
命名空间
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
# xmlns:beans="http://www.springframework.org/schema/beans" # 引用其它uri空间
## jar 包中的dtd/xsd文件直接用相对路径引用即可(相当于src/目录下的文件)
<h:table xmlns:h="http://www.w3.org/TR/html4/">
<h:tr>
<h:td>Apples</h:td>
<h:td>Bananas</h:td>
</h:tr>
</h:table>
# 命名空间约束文件的查找
1.联网
2.myeclipse中的xml
3.同目录下
4.jar包中
# xmlns:只能有一个没有别名
注意
命名空间不可以分层使用,如 <r:g:element> 是不允许的
例子
<?xml version="1.0" encoding="GB2312" ?>
<c:customer xmlns:c="http://www.customer.com/">
<c:name>ZhangSan</c:name>
<c:phone>09098768</c:phone>
<c:host xmlns:e="http://www.employee.com/">
<e:name>LiSi</e:name>
<e:phone>89675412</e:phone>
</c:host>
</c:customer>
<?xml version="1.0" encoding="GB2312"?>
<book xmlns="http://www.library.com/">
<title>The C++ Standard Library</title>
<author>Nicolai M.Josutis</author>
</book>
<?xml version="1.0" encoding="GB2312"?>
<customer xmlns="http://www.customer.com/"
xmlns:e="http://www.employee.com/">
<name>ZhangSan</name>
<phone>09098768</phone>
<host>
<e:name>LiSi</e:name>
<e:phone>89675412</e:phone>
</host>
</customer>
语法
xmlns:[prefix]=”[URI of namespace]”
dtd文件路径解析
system声明方式:根据给出URL寻找DTD?DTD通过URL显式地直接定位。
public声明方式:查找众所周知词汇表,应用程序自行确定从数据库中定位dtd,或是从声明的网站中下载(存入数据库缓存中)。
# myeclipse中通过xml Catalog选项来定位dtd约束
schema文件路径解析
xsd文件中:targetNamespace="http://www.w3school.com.cn" # 定义自己的uri标识空间名
xml文件中:xsi:schemaLocation="http://www.w3school.com.cn note.xsd" # 指定约束文件的uri地址,一般是空间名加/文件名(目前只有cxf的jaxws约束文件例外【把路径换成了包名】)
dtd
#
注意
同名元素只能用命名空间来区分定义
例子
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE poem[
<!ELEMENT poem (author, title, content)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT content (#PCDATA)>
]>
<poem>
<author>王维</author>
<title>鹿柴</title>
<content>空山不见人, 但闻人语声, 返景入深林,复照青苔上。</content>
</poem>
外部引用
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE poem SYSTEM "outer.dtd">
// outer.dtd
<?xml version="1.0" encoding="utf-8"?>
<!ELEMENT poem (author, title, content)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT content (#PCDATA)>
语法
<!ELEMENT author (#PCDATA)> 之中的两个空格必须要有
元素类型
EMPTY # 可以有属性
ANY # 根元素设为ANY类型后,元素出现的次数和顺序不受限制
#PCDATA
纯元素类型
混合类型 # 可以是元素与内容的混合
例子
<!ELEMENT 家庭 (人+, 家电*)>
修饰符说明
() # 用来给元素分组
如
(古龙|金庸|梁羽生), (王朔|余杰), 毛毛
| # 选择一个
如
(男人|女人)
+ # 出现一或多次
如
(成员+)
* # 出现零或多次
? # 出现零或一次
, # 对象必须按指定的顺序出现
如
(西瓜, 苹果, 香蕉)
属性类型
schema
#
Schema约束
tld文件是Schema约束的
引入
根元素添加
文件books.xsd
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" // ns 是 namespace
targetNamespace="http://www.jnb.com" // 给当前约束文件起一个名字
elementFormDefault="qualified"> // 添加属性,qualified指所有都来自xs空间
可选attributeFormDefault="unqualified" // unqualified 默认来自的空间
文件books.xml
从根元素开始约束
<jnb:书架 xmlns:jnb="http://www.jnb.com" // 在被约束文件根元素添加属性,约束命名空间
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" // 找到xsi
xsi:schemaLocation="http://www.jnb.com books.xsd"> // 指定对应约束文件的物理地址
<jnb:书>
</jnb:书>
</jnb:书架> // 在被约束所有元素添加指定空间前缀
// 保存并检验
语法
元素约束
<xs:element name="xxx" type="yy" defaule="" /> // name 元素名 type 类型
元素类型
xs:string // 字符串
xs:decimal // big decimal 数字类型
xs:integer //
xs:boolean
xs:date // 日期
xs:time // 时间
属性约束,在元素标签体里
<xs:attribute name="" type="" /> // name 属性名 type 类型
属性类型和元素类型一样
属性说明
<xs:attribute use="required" />
限定约束
对值的限定
枚举限定
正则限定
选择限定
长度限定
混合内容
<xs:complexType mixed="true"> // 只有文本和子元素,mixed可以不使用
</xs:complexType>
指示器
All
Choice // 选择出现
Sequence // 按声明顺序出现
maxOccurs
minOccurs
Group name
attributeGroup name
<xs:all> // 指定以下元素随意出现
</xs:all>
uml
#
Unified Modeling Languag
静态模型
用例图(需求分析):客户看 # 项级图(突出重点),一级图,二级图,三级图
参与者(泛化)
(关联)
用例(包含,扩展【继承】) # 是动词,表示功能模块
注释
类图:类结构、类关系(可以自动生成java类)
继承
实现
关联(全局变量)【导航性:一对多关系等】【聚合(所有引用),组合(生命周期相同,如int类型属性)】
# spring 中的依赖注入是关联
依赖(局部变量)
实体关系图ER
动态模型
时序图:可以根据时序图写代码
# rose工具中时序图中加入参与者:用例图中创建参与者,拖入时序图中
plantUML
#
分类
salt
wireframe
uml
activity
class
component
sequence
state
use case
mermaid.js
#
- 流程图(flowchart)
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
- 时序图(sequence diagram)
sequenceDiagram
participant Alice
participant Bob
Alice->>John: Hello John, how are you?
loop Healthcheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts
prevail!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
- 甘特图(gantt diagram)
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
Active task :active, des2, 2014-01-09, 3d
Future task : des3, after des2, 5d
Future task2 : des4, after des3, 5d
section Critical tasks
Completed task in the critical line :crit, done, 2014-01-06,24h
Implement parser and jison :crit, done, after des1, 2d
Create tests for parser :crit, active, 3d
Future task in critical line :crit, 5d
Create tests for renderer :2d
Add to mermaid :1d
- 类图(class diagram)
classDiagram
Class01 <|-- AveryLongClass : Cool
Class03 *-- Class04
Class05 o-- Class06
Class07 .. Class08
Class09 --> C2 : Where am i?
Class09 --* C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class08 <--> C2: Cool label
- git graph
gitGraph
options
{
"nodeSpacing": 150,
"nodeRadius": 10
}
end
commit
branch newbranch
checkout newbranch
commit
commit
checkout master
commit
commit
merge newbranch
markdown
#
标题
# 到 ######开头 # 分别表示一号 到 六号 标题
字体
**a**或__a__ # 加粗
*a*或_a_ # 斜体
***a***或___a___ # 加粗斜体
~~a~~ # 删除线
格式
换行 结尾两空格 或 <br/>
引用
>a
>>b # 不断增加>来多层引用
分割线
--- # 三个及以上
*** # 三个及以上,与---显示无分别
图片
 # alt显示在图片下,title在鼠标悬停时显示
超链接
[文本](url)
<a href="url" target="_blank">文本</a> # 可以用a标签指定target,在新页面显示
列表
-或+或*开头 # 无序列表-
数字加点开头 # 有序列表,行数自动
多空格(至少两个)加列表开头 # 嵌套列表
表格
标题1|标题2|标题3 # 默认居中对齐
-|-|- # -可以多个,:-控制标题和内容左对齐,:-:居中,-:右对齐
1|2|3 # 默认左对齐
代码
`a + b;` # 单选代码
```
function a(){
}
``` # 多行代码
# tab开头
流程图
略
org-mode
#
restructedText
#
LaTeX
#
KaTeX
#