在用二q编文章中提刎ͼ可以以汇dŞ式查看日志,也可以通过图表来查看?/span>Perf4j?/span>log4j集成后,其实也可以以图表的Ş式来查看性能状况?/span>
我们q篇文章q是使用前一文章中提到log4j.xml的配|,其他都一P只是在配|中加入了图表的配置Q?/span>
<!-- 生成firstBlock,secondBlock的^均值的图表 -->
<appender name="graphExecutionTimes"
class="org.perf4j.log4j.GraphingStatisticsAppender">
<!-- GraphType:MeanQ^均|, MinQ最|, MaxQ最大|, TPSQ每U事务数Q?/span> -->
<param name="GraphType" value="Mean"/>
<param name="TagNamesToGraph" value="firstBlock,secondBlock"/>
<appender-ref ref="graphsFileAppender"/>
</appender>
<!-- 生成firstBlock,secondBlock?/span>tps的图?/span> -->
<appender name="graphExecutionTPS"
class="org.perf4j.log4j.GraphingStatisticsAppender">
<param name="GraphType" value="TPS"/>
<param name="TagNamesToGraph" value="firstBlock,secondBlock"/>
<appender-ref ref="graphsFileAppender"/>
</appender>
<!-- 记录图表生成url?/span>log文g -->
<appender name="graphsFileAppender" class="org.apache.log4j.FileAppender">
<param name="File" value="/home/perfGraphs.log"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%m%n"/>
</layout>
</appender>
另外q需要改一个地方,是CoalescingStatistics的配|:(x)
<appender name="CoalescingStatistics"
class="org.perf4j.log4j.AsyncCoalescingStatisticsAppender">
<!--
TimeSlice配置多少旉间隔d一ơ汇d入文件中
默认值是 30000 ms
-->
<param name="TimeSlice" value="10000"/>
<appender-ref ref="fileAppender"/>
<appender-ref ref="graphExecutionTimes"/>
<appender-ref ref="graphExecutionTPS"/>
</appender>
黄色那段配置的意思就是把日志写入到图表日志去?/span>
q行代码Perf4JAppenderExampleQ我们在perfGraphs.log文g中生成了图表?/span>urlQ?/span>
大家可以?/span>url攑ֈ览器访问下?/span>
上面q种方式呢,需要自q录到服务器上Q找?/span>log文gQ在攑ֈ览器中查看Qȝq程q是比较ȝ。如果大安要监控的工程是一?/span>web工程的话Q那更方便了,直接配置一?/span>servlet来查看?/span>Web.xml的配|如下:(x)
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>perf4j</servlet-name>
<servlet-class>org.perf4j.log4j.servlet.GraphingServlet</servlet-class>
<!-- graphExecutionTimes?/span>graphExecutionTPS是我们?/span>log4j中配|的名称 -->
<init-param>
<param-name>graphNames</param-name>
<param-value>graphExecutionTimes,graphExecutionTPS</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>perf4j</servlet-name>
<url-pattern>/perf4j</url-pattern>
</servlet-mapping>
</web-app>
大家可以打包工程Qƈ攑ֈweb服务器下启动Q然后访问下/perf4jq个uri?/span>
Maven有一?/span>jetty插gQ可以方便启?/span>web工程Q只要大家在pom.xml文g中加入如下配|:(x)
<plugins>
<!-- jetty插g, 讑֮端口?/span>context path-->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
</plugin>
</plugins>
在控制台中输入:(x)mvn jetty:runQ即可。省M打包发布Q很省心喔?/span>
W一ơ用http://localhost:8080/perf4j讉K查看图表的时候没有生成Q何东西,那是因ؓ(f)内存中没有收集到最新的性能数据。所以我?/span>index.jsp里调用下以便产生性能数据。然后重新访问,q个时候就有图表生成了?/span>
Perf4j最主要的一个好处就是可以跟log4j或者logback来性能分析和监控线上运行的E序。集成的方式主要是:(x)自定义log4j的appenders通过标准的配|加入到l(f)og4j中去Q后面会(x)有配|的例子Q。有一个要注意的地方就是需要用log4j?.2.14版本或者更高版本。由于我一般都是用log4jQ所以对于logback的集成我׃描述了,我觉得应该差不多的?/span>
Perf4j最重要的appender是AsyncCoalescingStatisticsAppenderQ它?x)把一D|间内StopWatch的信息汇d一个独立的GroupedTimingStatistics日志信息Q然后把q个独立的信息传l下游的appendersQ比如fileappendersQ这样就可以写到文g中去了。也可以传给per4j的其他自定义appenders?/span>
接下来我们看一个log4j.xml的例子,有一个限Ӟ如果要用AsyncCoalescingStatisticsAppender只能用xml文g而不能用properties文g?/span>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false" xmlns:log4j="http://jakarta.apache.org/log4j/">
<!--
配置控制台输?/span>
-->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %c{1} - %m%n"/>
</layout>
</appender>
<!-- Perf4J appenders -->
<!--
AsyncCoalescingStatisticsAppender攉StopWatch的日志信息ƈ传送到下游的文?/span>appenders?/span>
-->
<appender name="CoalescingStatistics"
class="org.perf4j.log4j.AsyncCoalescingStatisticsAppender">
<!--
TimeSlice配置多少旉间隔d一ơ汇d入文件中
默认值是 30000 ms
-->
<param name="TimeSlice" value="10000"/>
<appender-ref ref="fileAppender"/>
</appender>
<!-- 把汇ȝperf4j的日志信息写?/span>perfStats.log文g中去 -->
<appender name="fileAppender" class="org.apache.log4j.FileAppender">
<param name="File" value="/home/perfStats.log"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%m%n"/>
</layout>
</appender>
<!-- Loggers -->
<!--
配置perf4j logger
Additivity讄?/span>false主要因ؓ(f)是不惌代码q行旉的日志输出给上游appendersQ即不要在控制台输出?/span>
-->
<logger name="org.perf4j.TimingLogger" additivity="false">
<level value="INFO"/>
<appender-ref ref="CoalescingStatistics"/>
</logger>
<!--
Root logger打印所有日志,但不包含perf4j的信息。原因是?/span>TimingLogger配置中设|了additivity?/span>false
-->
<root>
<level value="INFO"/>
<appender-ref ref="console"/>
</root>
</log4j:configuration>
黄色背景是perf4j的配|信息。其他都是log4j的基本配|。下面是试perf4j与log4j集成的代码?/span>
package com.baowu.perf4j;
import org.apache.log4j.Logger;
import org.perf4j.StopWatch;
import org.perf4j.log4j.Log4JStopWatch;
public class Perf4JAppenderExample {
public static void main (String[] args) throws Exception {
Logger rootLogger = Logger.getRootLogger();
for (int i = 0; i < 10; i++) {
// Log4JStopWatch默认使用org.perf4j.TimingLoggerq个c?/span>
StopWatch stopWatch = new Log4JStopWatch();
//模拟代码q行旉
Thread.sleep((long) (Math.random() * 1000L));
//打印到控制台
rootLogger.info("Normal logging messages only go to the console");
stopWatch.lap("firstBlock");
Thread.sleep((long) (Math.random() * 2000L));
stopWatch.stop("secondBlock");
}
}
}
q行代码?/span>
控制台输出:(x)
INFO root - Normal logging messages only go to the console
INFO root - Normal logging messages only go to the console
INFO root - Normal logging messages only go to the console
INFO root - Normal logging messages only go to the console
INFO root - Normal logging messages only go to the console
INFO root - Normal logging messages only go to the console
INFO root - Normal logging messages only go to the console
INFO root - Normal logging messages only go to the console
INFO root - Normal logging messages only go to the console
INFO root - Normal logging messages only go to the console
文g输出Q?/span>
输出格式也可以改?/span>CSV格式。配|:(x)
<appender name="fileAppender" class="org.apache.log4j.FileAppender">
<param name="File" value="/home/perfStats.log"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%m%n"/>
</layout>
</appender>
org.apache.log4j.PatternLayoutҎ(gu)org.perf4j.log4j.StatisticsCsvLayout卛_?/span>
具体的参数请查看api?br />下蝲工程
Perf4j使用一主要演示了性能监控的日志直接打印在标准输出。那么用二呢则主要来演C怎么来分析打印出来的日志文g?/span>
׃我们q没有跟log4j集成Q日志文件打印在标准输出,我们需要把标准输出重定向?/span>times.log文g中。重定向有两U方式:(x)直接copy到文件中Q或者在eclipse里指定下输出文g。我主要是用eclipse指定输出文g?/span>
然后q行代码Q?/span>Perf4j使用一?/span>Example.javaQ,控制C(x)在第一句话中打?/span>[Console output redirected to file:E:\yangpingyu\work\times.log]Q这栯行的l果?x)同时打印在文g中和标准输出中?/span>
有了times.logQ我们就可以Ҏ(gu)志文件进行分析,以找出有问题的代码?/span>
分析日志命o(h)Q?/span>
E:\yangpingyu\work>java -jar perf4j-0.9.16.jar times.log
?/span>csv的格式来查看l果Q命令如下:(x)
java -jar perf4j-0.9.16.jar -f csv times.log
以上都是以文本的格式q行输出Q但文本没有图表更具有表辑֊。所以把l果以图表Ş式输出是必不可少Q幸?/span>perf4j也支持,命o(h)如下Q?/span>
java -jar perf4j-0.9.16.jar --graph perfGraphs.html times.log
执行命o(h)后,在控制台输出相应的统计信息,相应的在盘上也生成了一?/span>htmlQ?/span>html里包含^均值图表和tps图表?/span>
<html>
<head><title>Perf4J Performance Graphs</title></head>
<body>
<br/><br/><img src="http://chart.apis.google.com/chart?cht=lxy&chtt=Mean&chs=750x400&chxt=x,x,y&chd=t:0.0,50.0,100.0|56.3,60.1,6.0|0.0,50.0|88.5,94.5|50.0,100.0|43.2,7.2|0.0,50.0,100.0|71.8,57.4,8.0|0.0,50.0,100.0|100.0,61.2,59.6|0.0,50.0,100.0|63.9,62.0,18.7|0.0,50.0,100.0|34.4,72.1,30.1&chco=ff0000,00ff00,0000ff,00ffff,ff00ff,ffff00,000000&chm=d,ff0000,0,-1,5.0|d,00ff00,1,-1,5.0|d,0000ff,2,-1,5.0|d,00ffff,3,-1,5.0|d,ff00ff,4,-1,5.0|d,ffff00,5,-1,5.0|d,000000,6,-1,5.0&chdl=codeBlock1|codeBlock2.failure|codeBlock2.success|codeBlock3|codeBlock4|codeBlock5|codeBlock6&chxr=2,0,748.5&chxl=0:|18:12:00|18:12:30|18:13:00|1:|Time&chxp=0,0.0,50.0,100.0|1,50&chg=50.0,10"/>
<br/><br/><img src="http://chart.apis.google.com/chart?cht=lxy&chtt=TPS&chs=750x400&chxt=x,x,y&chd=t:0.0,50.0,100.0|36.4,90.9,9.1|0.0,50.0|27.3,63.6|50.0,100.0|36.4,9.1|0.0,50.0,100.0|36.4,90.9,9.1|0.0,50.0,100.0|36.4,90.9,9.1|0.0,50.0,100.0|36.4,90.9,9.1|0.0,50.0,100.0|27.3,100.0,9.1&chco=ff0000,00ff00,0000ff,00ffff,ff00ff,ffff00,000000&chm=d,ff0000,0,-1,5.0|d,00ff00,1,-1,5.0|d,0000ff,2,-1,5.0|d,00ffff,3,-1,5.0|d,ff00ff,4,-1,5.0|d,ffff00,5,-1,5.0|d,000000,6,-1,5.0&chdl=codeBlock1|codeBlock2.failure|codeBlock2.success|codeBlock3|codeBlock4|codeBlock5|codeBlock6&chxr=2,0,0.4&chxl=0:|18:12:00|18:12:30|18:13:00|1:|Time&chxp=0,0.0,50.0,100.0|1,50&chg=50.0,10"/>
</body></html>
以上?/span>html的内容,里面最重要的信息就是两?/span>img标签Q里面具体的囄?/span>google chart api生成。可以打开html直接查看图表?/span>
如果惌看更详细的参敎ͼ可以使用—help来查看?/span>java -jar perf4j-0.9.16.jar –help?/span>
如果大家使用的是maven工程Q那么现?/span>pom文g中加?/span>perf4j的依赖?/span>
<dependency>
<groupId>org.perf4j</groupId>
<artifactId>perf4j</artifactId>
<version>0.9.16</version>
<scope>compile</scope>
</dependency>
如果用的是普通工E,那么直接下蝲jar包放?/span>lib目录下即可?/span>
例子Q?/span>
package com.baowu.per4j;
import org.perf4j.LoggingStopWatch;
import org.perf4j.StopWatch;
public class Example1 {
public static void main(String[] args) throws InterruptedException{
method1();
method2();
method3();
}
/**
* 监控一处代码示?/span>
* @throws InterruptedException
*/
private static void method1() throws InterruptedException{
//创徏一个监控对象,q里使用LoggingStopWatchQ它是把l果直接输出到控制台。我们也可以
//使用StopWatch的其他子c,比如Q?/span>Log4JStopWatchQ?/span>CommonsLogStopWatch。不q这些子c需
//要工E用日志框?/span>
StopWatch stopWatch = new LoggingStopWatch("codeBlock1");
//q里是一些需要监控的代码Q我们命名ؓ(f)codeBlock1
//使用U程休眠是ؓ(f)了模拟代码执行时?/span>
Thread.sleep((long)(Math.random() * 1000L));
//停止计算代码性能
stopWatch.stop();
}
/**
* 一个方法多Z码监?/span>
* @throws InterruptedException
*/
private static void method2() throws InterruptedException{
StopWatch stopWatch = new LoggingStopWatch();
Thread.sleep((long)(Math.random() * 1000L));
stopWatch.lap("codeBlock3");
Thread.sleep((long)(Math.random() * 1000L));
stopWatch.lap("codeBlock4");
Thread.sleep((long)(Math.random() * 1000L));
stopWatch.lap("codeBlock5");
Thread.sleep((long)(Math.random() * 1000L));
stopWatch.stop("codeBlock6");
}
/**
* stopҎ(gu)可以加入一些说明信?/span>
*/
private static void method3(){
StopWatch stopWatch = new LoggingStopWatch();
try {
// the code block being timed - this is just a dummy example
long sleepTime = (long)(Math.random() * 1000L);
Thread.sleep(sleepTime);
if (sleepTime > 500L) {
throw new Exception("Throwing exception");
}
stopWatch.stop("codeBlock2.success", "Sleep time was < 500 ms");
} catch (Exception e) {
stopWatch.stop("codeBlock2.failure", "Exception was: " + e);
}
}
}
q行l果Q?/span>
start[1334457619937] time[355] tag[codeBlock1]
start[1334457620296] time[152] tag[codeBlock3]
start[1334457620453] time[138] tag[codeBlock4]
start[1334457620593] time[598] tag[codeBlock5]
start[1334457621187] time[700] tag[codeBlock6]
start[1334457621890] time[619] tag[codeBlock2.failure] message[Exception was: java.lang.Exception: Throwing exception]
Perf4j主要的用途是计量代码性能和分析性能数据?/span>
Z么要使用q个工具呢?我们可以联想下最早期java开发者调试代码用的方式Q以前没有日志框Ӟ?/span>java开发就使用System.out.println()来输己想查看的变量。但是这样项目上U的话,pLq些打印语句以减性能影响。那万一在线上出问题了,调试哪里出问题就很麻烦,因ؓ(f)没有输出的日志可查。所以后来有人开发了日志框架Q通过日志U别控制日志的输出?/span>
cM的,如果没有perf4jQ我们在查看代码q行旉的话可以用以下代码来实现Q?/span>
long start = System.currentTimeMillis();
// execute the block of code to be timed
System.out.println("ms for block n was: " + (System.currentTimeMillis() - start));
q种方式有几个缺点:(x)
1?nbsp;q种方式输出内容比较单一Q就是代码ȝq行旉。但是我们代码需要查看的性能指标有更多,比如ȝq_|最|最大|tps{等?/span>
2?nbsp;也许我们的代码在U上q行Q我们想把这些值通过图表的Ş式展C出来。或者把q些内容通过jmx输出?/span>
3?nbsp;另外Q我们可能把perf4j?/span>log4jQ?/span>slf4j{日志框架和日志门面pȝ整合h?/span>
Z以上q些问题Q所以开源社区就出现?/span>perf4jQh多力量大Q社区的力量是强大Q?/span>
Perf4j一些特性:(x)
l 单的停止查看机制来计语句时间消耗输出?/span>
l 命o(h)行解?/span>log文g产生汇L据和图表?/span>
l 单的集成日志框架和门面框架?/span>
l 自定?/span>log4j?/span>logback?/span>appenders来生数据和图表?/span>
l 通过jmx查看性能指标QƈҎ(gu)阈值发送消息?/span>
l Web工程可以通过servlet来输出性能指标?/span>
l Perf4j可以?/span>aop{切面框架整合v来输出性能指标?/span>
l Perf4j是一个可扩展的架构?/span>
公司最q严抓Y件质量问题,我抽IZ解了下提高代码质量的一些开源工兗其中一个就?/span>findbugs。?/span>findbugs有很多方式,比如Q安?/span>eclipse findbugs插gQ通过maven调用生成报告。今天主要演CZmaven?/span>findbugs集成?/span>
W一步:(x)下蝲mavenQ我使用的是maven3。把maven的命令加?/span>PATH环境变量?/span>
W二步:(x)创徏一个普通的maven java工程。命令如下:(x)mvn archetype:maven-archetyp-quickstart –DgroupId=com.tianya –DartifactId=baowu。如果正常执行的话会(x)生成如下l构的一个工E?/span>
W三步:(x)我们看下pom文g
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tianya</groupId>
<artifactId>baowu</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<!--配置插g来源 -->
<pluginRepositories>
<pluginRepository>
<id>Codehaus repository</id>
<url>http://repository.codehaus.org/</url>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<!-- <configLocation>${basedir}/springside-findbugs.xml</configLocation> -->
<threshold>High</threshold>
<effort>Default</effort>
<findbugsXmlOutput>true</findbugsXmlOutput>
<!-- findbugs xml输出路径--> <findbugsXmlOutputDirectory>target/site</findbugsXmlOutputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</project>
我来解释?/span>xml配置Q?/span>
l 配置插g下蝲地址
<!--配置插g来源 -->
<pluginRepositories>
<pluginRepository>
<id>Codehaus repository</id>
<url>http://repository.codehaus.org/</url>
</pluginRepository>
</pluginRepositories>
l ׃maven核心做的事情都是抽象的构E,很多实际的工作都是具体的插g来实现。所以很昄Q?/span>maven以插件的方式集成findbugs?/span>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<!-- <configLocation>${basedir}/springside-findbugs.xml</configLocation> -->
<!-- findbugs xml输出--> <findbugsXmlOutput>true</findbugsXmlOutput>
<!-- findbugs xml输出路径--> <findbugsXmlOutputDirectory>target/site</findbugsXmlOutputDirectory>
</configuration>
</plugin>
l 大家注意Cfindbugs插g里,我注释掉了一句话Q其实这句话是可以使用自己?/span>fingbugs配置来做(g)查。我用的?/span>springside的一?/span>xml文g?/span>
W四步:(x)配置好相x件之后,接下来就是执行相兛_令了?/span>
mvn compile findbugs:findbugs生成报告。报告生成的地址是${目根目?/span>}/target/site。也可以通过mvn findbugs:gui gui界面查看findbugs?/span>report?/span>
从学校毕业到工作已经2q半旉了,在豆瓣、当当、京东和亚玛逊上x了很多书c,也比较喜Ƣ逛书店。当然买了很多和看了部分Q自己家里很多书q没看(人类的惰性,借口Q。在q两q半旉内,C一些弯路,所以想ȝ下自q学习(fn)生?/span>
毕业的时候去了一家创业型的互联网公司Q在q家公司没有M培训机制QQ何东襉K需要靠自己来捉摸。根据工作需要看?/span>struts2Q?/span>springQ?/span>hibernateQ?/span>jstlQ?/span>jspQ?/span>servlet{一?/span>J2EE相关lg的书和资料。仅靠这些技术也能搭建出一个符合品的|站。接触到infoq|站之后Q了解了一些大型网站的架构变迁{技术,在一q的旉内不断追寻这些不W合自己目前需要的技术,充其量就是开阔了自己技术的眼界。由于互联网公司的一些劣势,D我有了蟩槽的xQ但是出去面试之后,才知道自己是井底之蛙。自׃热爱技术,也算努力学习(fn)和研I的Q自己反思和ȝ了一下:(x)不注重基?/span>
那么学习(fn)什么才基呢?我主要列举下我的学习(fn)书单Q?/span>
ü 计算机基QQ何Y件运行的基础?/span>
ü 操作pȝQ硬件管理的软gQ我们的应用软g主要依赖于操作系l?/span>
n 鸟哥?/span>LinuxU房?/span> 基础学习(fn)?/span>
n 操作pȝ原理
ü 数据l构QY?/span>=数据l构+法。其实操作系lY件和应用软g都在大量应用数据l构?/span>
n 大话数据l构
ü |络Q系l一定需要与外部交互Q那需要网l?/span>
ü 数据库:(x)存储数据?/span>
n MySQL 5 权威指南-(W?/span>3?/span>)
ü 软g工程Q开发Y件是一个大工程Q需要有一套理论来理软g开发?/span>
n 软g工程
ü 法Q尚未接触?/span>
ü Java基础Q具体的高语言Q最好的学习(fn)地方是java的官方网站和开源代码?/span>
n Java|络~程(中文?/span> W三?/span>)
ü Javaq阶Q面向对象思想、设计模式和J2EE深入?/span>
n 企业应用架构模式
n J2EE设计开发编E指?/span>Q?/span>Expert One-on-One J2EE Design and DevelopmentQ?/span>
n Expert One-on-One J2EE Development without EJB
学习(fn)了这些基之后Q我们在来说分布式,nosqlQ云计算Q企业集成等{。只有掌握了基础我们才能更好的创新?/span>
Java语言?/span>c语言有一个非帔R要的区别是Q内存管理方式的不同Q?/span>java语言内存理不需要程序开发h员关注,?/span>c语言的内存的h和释N是开发h员来处理。辩证的思维来看Q不同内存管理实现方式有优点和缺点,所以语a应用的场景,效率?x)有很大不同?/span>
Jvmq行时的数据区域主要有:(x)E序计数器、虚拟机栈、本地方法栈、方法区和堆。其中程序计数器、虚拟机栈和本地Ҏ(gu)栈是U程独nQ而方法区和堆是所有线E共享?/span>
ü E序计数器:(x)jvm每个U程都有一个程序计数器。在M时刻都有一个线E的Ҏ(gu)在运行,如果q个Ҏ(gu)不是本地Ҏ(gu)Q那么程序计数器存放的就是正在执行的指o(h)地址Q如果是本地Ҏ(gu)Q那么程序计数器中存攄指定地址?/span>undefined?/span>
ü 虚拟机栈Q当jvm创徏一个线E的时候就?x)?f)U程分配一个虚拟机栈。主要用于存放方法的一些本地变量和部分l果Q一般这里的大小都是固定Q但不是l对。一个方法的执行到完成就是栈的入栈和出栈。假讑֜某方法中定义了一个对?/span>Object obj=new Object();其中obj是存攑֜栈上Q?/span>new Object()是在堆上分配?/span>-Xss可以控制jvm虚拟机栈的大?/span>
ü 本地Ҏ(gu)栈:(x)大体跟虚拟机栈类|不过是给本地Ҏ(gu)使用的。虚拟机栈和本地Ҏ(gu)栈在hotspot是没有分开实现的,而是l称为栈?/span>
ü Ҏ(gu)区:(x)主要存放静态变量,帔RQ类加蝲器加载的cȝ一些信息?/span>
ü 堆:(x)jvml大部分的对象分配都在堆上分配?/span>-Xmn –Xmx是控制堆最值和最大|一般堆的大在使用了超q?/span>mx讑֮?/span>70%的时候,׃(x)自动扩大到最大|所以防止这U扩大和~小我们讄成一L(fng)倹{?/span>
ClassCastExceptioncd转换异常Q是一个运行时异常?/span>
非常常见是不同cd之间的强制类型{换就?x)抛?/span>ClassCastException异常。还有一U就是不?/span>ClassLoader加蝲的相同的cd转换也会(x)抛出ClassCastException。接下来我用代码来详l解释下?/span>
1?nbsp;强制cd转换
public class ClassCastExceptionTest {
/**
* @param args
*/
public static void main(String[] args) {
Animal a1 = new Dog(); //1
Animal a2 = new Cat(); //2
Dog d1 = (Dog)a1; //3
Dog d2 = (Dog)a2; //4
}
}
把猫转换成狗Q是不对的。后面注释ؓ(f)4的代码是无法正常赋值的?/span>
Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Dog
at ClassCastExceptionTest.main(ClassCastExceptionTest.java:13)
2?nbsp;不同classloader加蝲相同cdcM间的转换
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
public class ClassCastExceptionTest {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
File file = new File(Thread.currentThread().getContextClassLoader().getResource("").getPath());
URL[] urls = {file.toURL()};
URLClassLoader classloader1 = new URLClassLoader(urls, ClassLoader.getSystemClassLoader().getParent());
Class classloader1Animal1 = classloader1.loadClass("Dog");
Dog dog1 = (Dog)classloader1Animal1.newInstance();
URLClassLoader classloader2 = new URLClassLoader(urls, ClassLoader.getSystemClassLoader().getParent());
Class classloader1Animal2 = classloader1.loadClass("Dog");
Dog dog2 = (Dog)classloader1Animal1.newInstance();
dog1 = dog2;
}
}
代码中我们看?/span>dog1=dog2Q这栯值是?x)抛异常的?/span>
Exception in thread "main" java.lang.ClassCastException: Dog cannot be cast to Dog
at ClassCastExceptionTest.main(ClassCastExceptionTest.java:17)
以后大家遇到classCastException的时候要注意了,不一定是强制cd转换D的,也有可能不同?/span>classloader加蝲了相同的c,然后q个cM同的实例q行赋倹{?/span>
Q一Q问?/span>
目中需要对文g?/span>md5sumQ分两步赎ͼ(x)1、对文g的每个字节?/span>md5实例q行updateQ然后进?/span>digest?/span>2?/span>digestq回长度?/span>16?/span>byte数组Q一般我们需要把byte数组转成16q制字符Ԍ很多开源的md5加密法如此实现Q真正的原因q不是很理解Q可能是便于查看和传输)。具体的实现代码如下Q?/span>
/**
* Ҏ(gu)件进?/span>md5 sum操作
* @param checkFile 要进行做md5 sum的文?/span>
* @return
*/
public static String md5sum(File checkFile){
String md5sumResult = "";
if(checkFile == null || (!checkFile.exists())){
return md5sumResult;
}
MessageDigest digest = MessageDigest.getInstance("MD5");
InputStream is = new FileInputStream(checkFile);
byte[] buffer = new byte[8192];
int read = 0;
try {
while( (read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
md5sumResult = bigInt.toString(16);
}
catch(IOException e) {
throw new RuntimeException("Unable to process file for MD5", e);
}
finally {
try {
is.close();
}
catch(IOException e) {
throw new RuntimeException("Unable to close input stream for MD5 calculation", e);
}
}
return md5sumResult;
}
其中黄色背景色的转换方式是有问题的。ؓ(f)什么用bigint?/span>16q制?x)有问题呢?/span>原因?/span>bigintq行16q制转换的时候第一?/span>0被自动去掉了.
Q二Q正解x?/span>
那正的方式是怎么L(fng)呢?下面有两U不同的转换方式Q但是原理其实是一致的?/span>
W一U正的方式Q由王徏提供Q:(x)
/**
* 字节数l{换ؓ(f)16q制字符?/span>
*
* @param buffer
* @return
*/
public static String toHex(byte[] buffer) {
StringBuffer sb = new StringBuffer(buffer.length * 2);
for (int i = 0; i < buffer.length; i++) {
sb.append(Character.forDigit((buffer[i] & 240) >> 4, 16));
sb.append(Character.forDigit(buffer[i] & 15, 16));
}
return sb.toString();
}
W二U正的方式Q?/span>
public static String bytes2HexString(byte[] b) {
String ret = "";
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
ret += hex;
}
return ret;
}
Q三Q问题分?/span>
Md5法对Q何长度的字符串进行编码最后输出是128位长整数Q也是长度?/span>16?/span>byte数组。我们项目调用的?/span>jdk实现?/span>md5法Q所以一般是没问题的?/span>
接下来我们要处理的事情,分别循环数组Q?span style="background:yellow;">把每个字节{换成2?/span>16q制字符Q也是说每4位{成一?/span>16q制字符?/span>
上面正确的两U方式也是做了q样的事情?/span>
W一U方式:(x)
Character.forDigit((buffer[i] & 240) >> 4, 16)把字节的?/span>4位取出右U?/span>4位换成intQ然后通过forDigit转换?/span>16q制字符
Character.forDigit(buffer[i] & 15, 16)把字节的?/span>4位取出换成intQ然后通过forDigit转换?/span>16q制字符
W二U方式:(x)
Integer.toHexString(b[i] & 0xFF)把整个字节{?/span>intQ然?/span>toHexString也就是做?/span>4位和?/span>4位的q算。但是这个方法如果高四位?/span>0的话׃输出M东西Q?/span>
所以在输出的字W前?/span>0卛_?/span>
b[i] & 0xFF是?/span>byte转成intQؓ(f)什么用?/span>oxff做与q算Q是因ؓ(f)如果b[i]是负数的话,?/span>8位变?/span>32位会(x)?/span>1Q所以需要与0xff做与q算Q可以把前面?/span>24位全部清Ӟ又可以表C成原来的字节了?/span>
附:(x)
量使用开源提供的工具包,比如Q?/span>
org.apache.commons.codec.digest.DigestUtils.md5Hex(InputStream data)来对文g进?/span>md5卛_Q更加方便,可靠?/span>
早期的javaE序员可能只要懂基本语法Q还有少数的目l验可以找C份比较好的工作。Java和javaC的发展,更多的h了解它,深入它。现在javaE序员了解一些语法我看还q远不够了,对于jvm的了解和深入是非帔R要的。网民的增多Q网站的刚性需求,很多|站面(f)高性能Q高q发{等一pd的问题。没有深入jvm的javaE序员是很难写出高质量高q发的代码(也许一子打死所有h了,但我想绝大部分是肯定的)?/span>
Osgi也许你ƈ不陌生,但是他底层的实现机制你可能没M解过。如果你是个打破砂锅问到底的人,你肯定会(x)想知道osgi是如何做到的。但是你没有了解jvm的类加蝲体系Q你肯定很难理解osgi是如果做到类隔离{一pd的问题。不q想完整理解osgiq需要其他很多方面的知识Q但是它基本的机制还是的了解jvm的类加蝲机制?/span>
Javacd有些包只是定义了一个标准,具体的实现都是由具体的供应商来提供。Java与数据库q接是一个很好的例子。Java.sqlcd只是定义了java与数据库q接的标准,那么与mysql需要msyql的驱动,oracle需要oracle的驱动,而java.sqlcd是由bootstrap classloader加蝲Q驱动包中的cL?span>system classloader来加载,不同cd载器加蝲的类是无法相互认识,所以自然也无法正常提供功能了。jvm又是提供了什么机制让他们交互呢?如果你确实对q些问题毫无头A的话Q那么我觉得你真的要好好理解下jvmcd载体pR?/span>
q篇文章主要是介l下jvmcd载的机制基础知识。关于其他相x?qing),有时间的话,我?x)单独写文章来介绍?/span>
1?/span> javacd载器
1.1 Bootstrap classloaderQsun jdk是用c++实现Q所以在代码中你是无法获取此加蝲器的实例。此加蝲器主要负责加?JAVA_HOME/jre/lib/rt.jar。javacM获取l果为nullQ这里可以用一个例子跑下证明:(x)
public class Test {
public static void main(String[] arg) throws Exception{
ClassLoader classloader = Test.class.getClassLoader();
System.out.println(classloader);
System.out.println(classloader.getParent());
System.out.println(classloader.getParent().getParent());
}
}
输出l果Q?/span>
sun.misc.Launcher$AppClassLoader@19821f
sun.misc.Launcher$ExtClassLoader@addbf1
null
最后输出的null是代表bootstrap classloader?/span>
1.2 Extension classloaderQ主要加载扩展功能的jarQ?JAVA_HOME/jre/lib/ext/*.jar?/span>
1.3 System classloaderQ加载claspath中的jar包?/span>
1.4自定?classloaderQ主要加载你指定的目录中的包和类?/span>
2?/span> 双亲委派模型
pȝq行Ӟ我们h加蝲Stringc,此时System Classloader自己不找classpath中的包,把请求{发给Extension ClassloaderQ但它也不做查找Q又转发lBootstrap ClassloaderQ但是它发现自己没有parent了。于是他在rt.jar包中扑ֈStringcdƈ加蝲到jvm中提供用。JvmZ么要q么实现呢?其实和java安全体系有关。假设jvm不是q么实现Q我们自定义一个Stringc,做一些破坏,那么q行jvm的机器肯定要受到损坏。具体例子:(x)
public class String {
public static void main(String[] args) {
System.out.println("hello world");
}
}
我们q行自定义Stringcȝ时候报错了Q说没有mainҎ(gu)Q可我们定义的明明有的,嘿嘿Q委z机制的~故最后加载到的是由bootstrap classloader在rt.jar包中的StringQ那个类是没有mainҎ(gu)Q因此报错了?/span>
3?/span> c隔?/span>
jvm中的cLQ类加蝲?包名+cd。比如:(x)URLClassLoader1QURLClassLoader2分别加蝲com.test.Test的时候会(x)加蝲两次Q因为每个classloader中的cd于其他classloader来说是隔ȝQ不认识的。例子:(x)
import java.net.URL;
import java.net.URLClassLoader;
public class CustomClassloaderTest {
public static void main(String[] args) throws Exception {
URL url = new URL("file:/g:/");
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
Class c = ucl.loadClass("Yang");
c.newInstance();
System.out.println(c.getClassLoader());
URLClassLoader ucl2 = new URLClassLoader(new URL[]{url});
Class c2 = ucl2.loadClass("Yang");
c2.newInstance();
System.out.println(c2.getClassLoader());
}
}
大家把Yangcd在g盘下?/span>
public class Yang {
static {
System.out.println("Yang");
}
}
q行l果Q?/span>
Yang
java.net.URLClassLoader@c17164
Yang
java.net.URLClassLoader@61de33
看到每次加蝲Yangcȝ时候都输出YangQ说明Yangc被加蝲了两ơ?/span>
如果你不信Q可以修改下代码Q让同一classloader加蝲YangcM?/span>
import java.net.URL;
import java.net.URLClassLoader;
public class CustomClassloaderTest {
public static void main(String[] args) throws Exception {
URL url = new URL("file:/g:/");
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
Class c = ucl.loadClass("Yang");
c.newInstance();
System.out.println(c.getClassLoader());
Class c2 = ucl.loadClass("Yang");
c2.newInstance();
System.out.println(c2.getClassLoader());
}
}
看看输出l果Q?/span>
Yang
java.net.URLClassLoader@c17164
java.net.URLClassLoader@c17164
l果中只输出了一ơYang。因此可以证明我们最开始说的类隔离?/span>
4?/span> U程上下文类加蝲?/span>
我们理解了双亲委z模型,那么目前只有׃向上单向Lc(system->extension->bootstrapQ?/span>
。我们在最开始的时候说q,java.sql包中的类由bootstrap或extension classloader加蝲Q而mysql驱动包是在classpath中由system来加载,但bootstrap中的cL无法扑ֈsystem classloader中的c,此时靠线E上下文cd载器来解冟뀂线E上下文cd载器主要是能让jvmcd载模型具有了向下L的可能,bootstrap->extension->systemQ如果不做Q何设|,U程上下文类加蝲器默认是system classloader。本来这里想写一个例子的Q可是有炚w烦,所以下ơ单独写一关于这斚w的知识?/span>