0%

避免使用 GroupByKey

当调用 groupByKey 时,所有的键值对(key-value pair) 都会被移动。在网络上传输这些数据非常没有必要。

以下函数应该优先于 groupByKey :

  • combineByKey
    组合数据,但是组合之后的数据类型与输入时值的类型不一样。

  • foldByKey
    合并每一个 key 的所有值,在级联函数和“零值”中使用。

combineByKey

combineByKey的定义

1
2
3
4
5
6
7
8
9
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C,
partitioner: Partitioner,
mapSideCombine: Boolean = true,
serializer: Serializer = null): RDD[(K, C)] = {
// do something
}

combineByKey函数主要接受了三个函数作为参数,分别为createCombiner、mergeValue、mergeCombiners。这三个函数足以说明它究竟做了什么。理解了这三个函数,就可以很好地理解combineByKey。

createCombiner

createCombiner:在遍历RDD的过程中,对于遍历到的(k,v),如果是第一次遇到,则对这个(k,v)调用createCombiner函数,它的作用是将v转换为c(类型是C,即聚合对象的类型,c作为聚合对象的初始值)

mergeValue

mergeValue:在遍历RDD的过程中,对于遍历到的(k,v),如果不是第一次(而是第i次, 1 < i <= n)遇到,那么将对这个(k,v)调用mergeValue函数,它的作用是将v累加到聚合对象(类型C)中,mergeValue的类型是(C,V)=>C,参数中的C遍历到此处的聚合对象,然后对v进行聚合得到新的聚合对象值

mergeCombiners

mergeCombiners:因为combineByKey是在分布式环境下执行,RDD的每个分区单独进行combineByKey操作,最后需要对各个分区的结果进行最后的聚合。它的函数类型是(C,C)=>C,每个参数是分区聚合得到的聚合对象。

combineByKey的流程

  • 假设一组具有相同 K 的 records 正在一个个流向 combineByKey(),createCombiner 将第一个 record 的value 初始化为 c (比如,c = value),然后从第二个 record 开始,来一个 record 就使用 mergeValue(c,
  • record.value) 来更新 c,比如想要对这些 records 的所有 values 做 sum,那么使用 c = c + record.value。等到records 全部被 mergeValue(),得到结果 c。假设还有一组 records(key 与前面那组的 key 均相同)一个个到来,
  • combineByKey() 使用前面的方法不断计算得到 c’。现在如果要求这两组 records 总的 combineByKey() 后的结果,那么可以使用 final c = mergeCombiners(c, c’) 来计算。

Example

1
2
3
4
5
6
7
var rdd1 = sc.makeRDD(Array(("A",1),("A",2),("A",3),("B",1),("B",2),("C",3)))

// result: ((A,1_$2_@3), (B,1_$2_), (C,3_))
rdd1.combineByKey(
(v : Int) => v + "_",
(c : String, v : Int) => c + "@" + v,
(c1 : String, c2 : String) => c1 + "$" + c2 ).collect

combineByKey应用举例

求均值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
val rdd = sc.textFile("气象数据")
val rdd2 = rdd.map(x=>x.split(" ")).map(x => (x(0).substring("从年月日中提取年月"),x(1).toInt))
val createCombiner = (k: String, v: Int)=> {
(v,1)
}
val mergeValue = (c:(Int, Int), v:Int) => {
(c._1 + v, c._2 + 1)
}

val mergeCombiners = (c1:(Int,Int),c2:(Int,Int))=>{
(c1._1 + c2._1, c1._2 + c2._2)
}

val vdd3 = vdd2.combineByKey(
createCombiner,
mergeValue,
mergeCombiners
)

rdd3.foreach(x=>println(x._1 + ": average tempreture is " + x._2._1/x._2._2)

Step1 Find the resource

search in Google, steam 上已经没有了。

Step2 Fix the Max OS 10.11 bug

An error occured while starting the X11 server:
Failed to activate core devices”
Click Quit to quit X11. Click Report to see more details or send a report to Apple.

https://www.reddit.com/r/OSXElCapitan/comments/3d64sd/wineskin/ 找到解决方法。即:
open terminal:

mkdir/lib
cp -r /Applications/aoeHD.app/Contents/Frameworks/* /lib

此时再打开游戏,成功运行,听到了熟悉的声音。

Reference

https://www.reddit.com/r/OSXElCapitan/comments/3d64sd/wineskin/

将数据转成json格式:python -m json.tool

1
2
3
4
$ echo '{"json":"obj"}' | python -m json.tool
{
"json": "obj"
}

查看gzip数据

使用python的zlib库来解压

1
2
3
4
5
s='\x1F\x8B\x08\x00\x00\x00\x00\x00\x00\x005N\xCD\x0A\xC3 \x18{\x97\xEF,\xE5s\xF3gz\xAB\xA0/1z\x18\xC31\x87m\xA5\xEA\xA1\x94\xBE\xFB\xBE\xC3vJHB\x92\x03z\x8D\xDBXJ\x05{?\xA0,`\xE1\xB9\xCEC\xEB\x9F\xF4\x18\xDEk\x8B\x19\x18\xD4\x99d\xC9/\xCE\xF9\x10Ft\xE1\xAA\x9DFm\x8C\x92B)w\xF3\x02\xBD\xA1\x5C\xA3\x16.\x84\xE4\x5CHD\x94\x82Ao`\x97\x9E3\x83Df\xDBzdP\xD2_{\x11C\x06\xB9\x13\x9C\x13\x0D\xED\xF5\xF7e:\xBF\xAB \xDB\x10\x9B\x00\x00\x00' 

import zlib
d=zlib.decompressobj(16+zlib.MAX_WBITS)
data=d.decompress(s)

统计服务器上的历史登录记录

1
last -ad | awk '{print $1}' | sort | uniq -c | sort -t$'\t' -k1,1 -nr

linux文件格式转换

在linux下,不可避免的会用VIM打开一些windows下编辑过的文本文件。我们会发现文件的每行结尾都会有一个^M符号,这是因为DOS下的编辑器和Linux编辑器对文件行末的回车符处理不一致,
对于回车符的定义:

  • windows:0D0A
  • unix\linux: 0A
  • MAC: 0D
    去除这些符号的方法有:
  • vim下 :set fileformat=unix
  • 终端 dos2unix filename

git图形化提交历史

1
git log --pretty=format:"%h %an %s" --graph

echo 总结

  1. echo默认是带换行符做结尾的
  2. echo -n 可以去掉换行符
  3. printf是没有换行符结尾的
  4. tr可以删掉一个字符,如 tr -d ‘\n’

删除空行

  1. grep: grep -v ‘^$’ file
  2. sed: sed ‘/^$/d’ file 或 sed -n ‘/./p’ file
  3. awk: awk ‘/./ {print}’ file

shell读文件

读取 md5s 文件,对每行做处理。

1
2
3
4
5
6
7
8
cat md5s | while read line 
do
     md5=$line
     level2Path=`expr substr "$md5" 30 2` #50
     level1Path=`expr substr "$md5" 32 1` #a
     storagePath=hdfs:///ljp/apk/path/$level1Path/$level2Path/$md5
     echo $storagePath
done

什么是序列化

java 序列化是将对象转化为二进制流。不同的序列化框架会将对象转成不同的二进制流。通过 透过byte数组简单分析Java序列化、Kryo、ProtoBuf序列化 这篇文章里可以看到,不同的序列化框架最终转成的二进制流是不一样的。

Java 默认序列化

默认序列化机制

如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。

整个过程都是Java虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。

serialVersionUID

serialVersionUID的作用
不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L

Java 序列化实现

ObjectInputStream && ObjectOutputStream

类ObjectInputStream 和ObjectOutputStream是高层次的数据流,它们包含序列化和反序列化对象的方法。
ObjectOutputStream 类包含很多写方法来写各种数据类型,但是一个特别的方法例外:

1
public final void writeObject(Object x) throws IOException

上面的方法序列化一个对象,并将它发送到输出流。相似的ObjectInputStream 类包含如下反序列化一个对象的方法:

1
public final Object readObject() throws IOException, ClassNotFoundException

该方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。

Serializable 接口

情境:一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。
解决:要想将父类对象也序列化,就需要让父类也实现Serializable 接口。如果父类不实现的话的,就 需要有默认的无参的构造函数。在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。

Externalizable 接口

无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口—Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。
writeExternal:把一个Java对象写入到流中
readExternal:从流中读取一个Java对象

java序列化一览

Java 序列化框架比较

性能比较

测试方法

jvm-serializers 提供了一个很好的比较各种Java序列化的的测试套件。 它罗列了各种序列化框架, 可以自动生成测试报告。

适用性比较

  • json
    json的序列化框架有fastjson,jackson,gson等。
    适用于数据量小,实时性较低(例如秒级别)的服务。JSON格式具有非常强的前后兼容性,并且调式方便,所以对客户端与服务端的通讯尤其适用。
  • xml
    xml的序列化框架有XStream。XML的序列化和反序列化的空间和时间开销都比较大,对于对性能要求在ms级别的服务,不推荐使用。
  • hessian
    hessian主要用于java序列化。它的实现机制是着重于数据,附带简单的类型信息的方法:
  • 对于简单的数据类型。就像Integer a = 1,hessian会序列化成I 1这样的流,I表示int or Integer,1就是数据内容。
    • 对于复杂对象,通过Java的反射机制,hessian把对象所有的属性当成一个Map来序列化,产生类似M className propertyName1 I 1 propertyName S stringValue
    • 对于引用对象,在序列化过程中,如果一个对象之前出现过,hessian会直接插入一个R index这样的块来表示一个引用位置,从而省去再次序列化和反序列化的时间。
  • thift
    Thrift是Facebook开源提供的一个高性能,轻量级RPC服务框架,其产生正是为了满足当前大数据量、分布式、跨语言、跨平台数据通讯的需求。 但是,Thrift并不仅仅是序列化协议,而是一个RPC框架。相对于JSON和XML而言,Thrift在空间开销和解析性能上有了比较大的提升,对于对性能要求比较高的分布式系统,它是一个优秀的RPC解决方案;但是由于Thrift的序列化被嵌入到Thrift框架里面,Thrift框架本身并没有透出序列化和反序列化接口,这导致其很难和其他传输层协议共同使用(例如HTTP)。
  • protobuf
    序列化数据非常简洁,紧凑,析速度非常快,提供了非常友好的动态库。使用简介,反序列化只需要一行代码。但是在JavaBean和proto之间的转换较麻烦。
  • avro
    Avro的产生解决了JSON的冗长和没有IDL的问题。 Avro提供两种序列化格式:JSON格式或者Binary格式。Binary格式在空间开销和解析性能方面可以和Protobuf媲美,JSON格式方便测试阶段的调试。
  • 动态类型:Avro并不需要生成代码,模式和数据存放在一起,而模式使得整个数据的处理过程并不生成代码、静态数据类型等等。这方便了数据处理系统和语言的构造。
    • 未标记的数据:由于读取数据的时候模式是已知的,那么需要和数据一起编码的类型信息就很少了,这样序列化的规模也就小了。
    • 不需要用户指定字段号:即使模式改变,处理数据时新旧模式都是已知的,所以通过使用字段名称可以解决差异问题。

Reference

https://www.ibm.com/developerworks/cn/java/j-lo-serial/
http://www.infoq.com/cn/articles/serialization-and-deserialization
http://sqtds.github.io/2015/05/13/2015/java-serizable/
http://www.solinx.co/archives/377

调试过程

本地运行代码,输出如下:

1
2
3
4
5
15/11/12 12:09:51 INFO SparkContext: Running Spark version 1.5.1
Exception in thread "main" java.lang.NoClassDefFoundError: scala/collection/GenTraversableOnce$class
at org.apache.spark.util.TimeStampedWeakValueHashMap.<init>(TimeStampedWeakValueHashMap.scala:42)
at org.apache.spark.SparkContext.<init>(SparkContext.scala:287)
Caused by: java.lang.ClassNotFoundException: scala.collection.GenTraversableOnce$class

查了半天没有任何结果,大家分析的原因各式各样。后来看到了一位仁兄总结的帖子:solve spark issue of all masters are unresponsive,跑去spark机器看了一下log,果然有收获。

spark日志:

1
ReliableDeliverySupervisor: Association with remote system [akka.tcp://sparkDriver@100.64.80.93:57108] has failed, address is now gated for [5000] ms. Reason is: [scala.Option; local class incompatible: stream classdesc serialVersionUID = -114498752079829388, local class serialVersionUID = -2062608324514658839].

根据 scala.Option; local class incompatible 可以发现是 scala 的版本不对,spark 默认的是 scala-2.10,需要改变依赖的scala版本。

改完以后又发现,还是连接不上。本地的输出:

1
2
3
4
5
6
15/11/12 21:46:22 ERROR SparkUncaughtExceptionHandler: Uncaught exception in thread Thread[appclient-registration-retry-thread,5,main]
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@5430d0ff rejected from java.util.concurrent.ThreadPoolExecutor@7819693b[Running, pool size = 1, active threads = 0, queued tasks = 0, completed tasks = 1]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)

spark的日志如下:

A

1
15/11/12 21:46:03 ERROR ErrorMonitor: dropping message [class akka.actor.ActorSelectionMessage] for non-local recipient [Actor[akka.tcp://sparkMaster@10.19.27.215:4041/]] arriving at [akka.tcp://sparkMaster@10.19.27.215:4041] inbound addresses are [akka.tcp://sparkMaster@master1:4041]

B

1
15/11/12 22:00:41 WARN ReliableDeliverySupervisor: Association with remote system [akka.tcp://sparkDriver@100.64.80.93:61812] has failed, address is now gated for [5000] ms. Reason is: [Disassociated].

关于B这部分的log,怀疑是测试环境的spark的网络访问权限没有打开!最后打开网络访问权限后解决。spark master和worker之间的通信使用的是akka,tcp协议。

spark的A部分log和本地的log是一致的。

第二天接着查,查了很多地方。怀疑是Spark的配置不正确。
对于Spark的配置,官网说的是:

Options for the daemons used in the standalone deploy mode

SPARK_MASTER_IP, to bind the master to a different IP address or hostname

而我spark机器上的设置是:

  1. conf/spark-env.sh: export SPARK_MASTER_IP=master1
  2. /etc/hosts: 10.x.xxx.215 master1

一切配置正确但依然不行。Google上到处寻觅,找到 spark 的 group 里面的一个帖子,https://groups.google.com/forum/#!topic/spark-users/SKE4UOUQ_U8,提到

Yes, this message means that one of the workers tried to contact you using your IP address (10.129.7.154), but Akka is (somewhat stupidly) configured to rely on a DNS name (namely ip-10-129-7-154). If you’ve set up the Spark standalone mode, there was a bug in the scripts where they would use an IP address for the master instead of a hostname.

所以当我将 SMART_IP 改成 ip 而不是 hostname 后,本地终于能连上spark了,设置如下:

conf/spark-env.sh: export SPARK_MASTER_IP=10.x.xxx.215

几点备忘

  1. 通过 %% 方法获取正确的 Scala 版本
    如果你用是 groupID %% artifactID % revision 而不是 groupID % artifactID % revision(区别在于 groupID 后面是 %%),sbt 会在 工件名称中加上项目的 Scala 版本号。 这只是一种快捷方法。你可以这样写不用 %%:

  2. enable build.sbt auto import
    修改了 build.sbt,但是包没有引入生效

  3. ./spark-shell 加载配置文件
    在Spark 集群上运行一个应用,只需通过master的 spark://IP:PORT 链接传递到SparkContext构造器
    在集群上运行交互式的Spark 命令, 运行如下命令:
    MASTER=spark://IP:PORT ./spark-shell
    注意,如果你在一个 spark集群上运行了spark-shell脚本,spark-shell 将通过在conf/spark-env.sh下的SPARK_MASTER_IP和SPARK_MASTER_PORT自动设置MASTER

1. contact

MySQL CONCAT function is used to concatenate two strings to form a single string.

MySQL GROUP_CONCAT() function returns a string with concatenated non-NULL value from a group.

数据库Person表的内容如下:

id name source age
1 A GP 6
2 B GP 2
3 A FB 1
4 C FB 4
5 D FB 5
6 A FB 3
7 C TW 7


1.SQL:

1
2
3
4
select name, count(distinct source) as sourceCount,
group_concat(distinct source separator "/") as sources
from Person
group by name;

Query Result:

name sourceCount sources
A 2 GP/FB
B 1 GP
C 2 GP/TW
D 1 FB


2.SQL:

1
2
3
4
5
select name, count(distinct source) as sourceCount,
group_concat(distinct source separator "/") as sources
from Person
group by name
having sourceCount = 1 and sources = 'FB';

Query Result:

name sourceCount sources
D 1 FB


3.SQL:

1
2
3
select name, count(distinct age) as ageCount,
group_concat(age order by age separator "#") as ages
from Person;

Query Result:

name ageCount ages
A 3 1#3#6
B 1 2
C 2 4#7
D 1 5

2. mysql -N 不显示字段名

普通的查询语句,查询结果中带字段名

1
2
 mysql -h xxxx -P 8000 -u'xxx' -p'xxx' -D xxdb
-e "select name from Person where name = 'A'";

+—————-+

| name |

+—————-+

| not found |

+—————-+

带-N的查询语句,查询结果中不带字段名

1
2
 mysql -N -h xxxx -P 8000 -u'xxx' -p'xxx' -D xxdb
-e "select name from Person where name = 'A'";

+—————-+

| not found |

+—————-+

3. IFNULL

使用IFNULL能判断是否有查到结果。
在shell中跑mysql的指令容易出现空行,此时用IFNULL是最合适的了。

1
2
3
4
 mysql -N -h xxxx -P 8000 -u'xxx' -p'xxx' -D xxdb
-e "select IFNULL(
(select name from Person where name = 'A' and age = 100),
'not found')"

+—————-+

| not found |

+—————-+

什么是内存泄露

内存泄露 memory leak,是指已申请的无用内存无法被回收。

内存泄漏有两种情况:

  • 一种情况如在C/C++语言中的,在堆中的分配的内存,在没有将其释放掉的时候,就将所有能访问这块内存的方式都删掉(如指针重新赋值)

  • 一种情况则是在内存对象明明已经不需要的时候,还仍然保留着这块内存和它的访问方式(引用)

第一种情况,在Java中已经由于垃圾回收机制的引入,得到了很好的解决。所以,Java中的内存泄漏,主要指的是第二种情况。

内存泄露的一个例子:

1
2
3
4
5
for (int i = 0; i < 1000; i++) {
Object obj = new Object(
list.add(obj);
obj = null;
}

这段代码是:for循环中,new一个Object对象obj,然后将其添加到list中,最后将obj置空。

这个时候就发生了内存泄露,因为obj是可达的无用对象。发生GC时,尽管obj已经被置空成为了无用对象,但是obj能够从list可达,从而GC无法将其释放掉。次数obj占用的内存就是泄露了。

内存泄露与内存溢出

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。当发生内存溢出时,程序将无法进行,强制终止。在java中常见的java.lang.OutOfMemoryError就是内存溢出的log。

内存长期泄露终将导致内存溢出。

内存泄露的危害

一次内存泄露危害可以忽略,但内存长期泄露,可用内存会逐渐减少,导致降低性能,最终内存溢出。

在移动设备对于内存和CPU都有较严格的限制的情况下,Java的内存泄露还会导致程序性能降低甚至崩溃。

怎么产生内存泄露

容易引起内存泄漏的几大原因

  1. 静态集合类

    像HashMap、Vector 等静态集合类的使用最容易引起内存泄漏,因为这些静态变量的生命周期与应用程序一致,如示例1,如果该Vector 是静态的,那么它将一直存在,而其中所有的Object对象也不能被释放,因为它们也将一直被该Vector 引用着。

  2. 监听器

    在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。

  3. 物理连接

    一些物理连接,比如数据库连接和网络连接,除非其显式的关闭了连接,否则是不会自动被GC 回收的。Java 数据库连接一般用DataSource.getConnection()来创建,当不再使用时必须用Close()方法来释放,因为这些连接是独立于JVM的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。

  4. 内部类和外部模块等的引用

    内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。

垃圾回收

可以手动执行垃圾回收吗?

只能建议jvm进行GC,但什么时候做GC由JVM决定

System.gc()

可以通过调用System.gc()建议JVM执行垃圾回收,但JVM不保证一定会执行GC操作。通常不推荐使用System.gc()。

finalize()

finalize()方法存在于java.lang.Object类中,可以被所有对象所使用。默认情况下其不执行任何动作。当垃圾回收器确定了一个对象没有任何引用时,其会调用finalize()方法。但是,finalize方法并不一定会被执行,因此也不建议覆写finalize()该方法。

内存泄露,会被垃圾回收吗

内存泄露 memory leak,是指已申请的无用内存无法被回收。GC只能回收第一种情况的内存泄露,见前面的释义。

设置null能防止内存泄露吗

最基本的建议就是尽早释放无用对象的引用,大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域后,自动设置为null。

不过这个真的有用吗?

查阅了网上的一些讨论以后有以下结论:

首先,jdk远比我们想象中的聪明,完全能判断出对象是否已经可以回收。但是在极少数情况下,这么做依然是有效的。

这些情况是:方法前面中有定义大的对象,然后又跟着非常耗时的操作,且没有触发JIT编译。

JVM即时编译器:即时编译器(Just In Time Compiler) 简称JIT
JAVA程序最初是通过解释器(Interpreter)进行解释执行的,当JVM发现某个方法或代码块运行特别频繁的时候,就会认为这是“热点代码”(Hot Spot Code)。
为了提高热点代码的执行效率,就会将这些“热点代码”编译成与本地机器相关的机器码,进行各个层次的优化。 完成这个任务的编译器就是即时编译器(JIT)。

例如:

1
2
3
4
5
6
private void processObj() {
BigObject obj = … // 声明大对象obj
doSomethingWith(obj); // 使用obj
obj = null; // explicitly set to null
doSomethingElse(); //非常耗时的操作
}

此时显示的设置无用的对象obj为null才有效。

How to avoid Memory Leak in Java

贴出 How to avoid Memory leak issue in Java 一文中提到的防止java内存泄露的一些建议。

How to avoid Memory Leak in Java?

While coding if we take care of a few points we can avoid memory leak issue.

  1. Use time out time for the session as low as possible.
  2. Release the session when the same is no longer required. We can release the session by using HttpSession.invalidate().
  3. Try to store as less data as possible in the HttpSession.
  4. Avoid creating HttpSession in jsp page by default by using the page directive

    <%@page session=”false”%>

  5. Try to use StringBuffer’s append() method instead of string concatenation. String is an immutable object and if we use string concatenation, it will unnecessarily create many temporary objects on heap which results in poor performance.

    For ex. if we write String query = “SELECT id, name FROM t_customer whereMsoNormal” style=”margin-bottom: 0.0001pt;”> it will create 4 String Objects. But if we write the same query using StringBuffer’s append() it will create only one object as StringBuffer is mutable i.e. can be modified over and over again.

  6. In JDBC code, While writting the query try to avoid “*”. It is a good practice to use column name in select statement.
  7. Try to use PreparedStatement object instead of Statement object if the query need to be executed frequently as PreparedStatement is a precompiled SQL statement where as Statement is compiled each time the Sql statement is sent to the database.
  8. Try to close the ResultSet and Statement before reusing those.
  9. If we use stmt = con.prepareStatement(sql query) inside loop, we should close it in the loop.
  10. Try to close ResultSet, Statement, PreparedStatement and Connection in finally block.

在测试内存泄露时,对GC有一些收获

  1. cannot disable java gc
  2. 我们不能决定什么时候发生GC。
  3. System.gc() vs GC button in JVisualVM/JConsole
    As far as I know, Jconsole or any other tool, uses System.gc() only. There is no other option. As everyone know, java tells everyone not to rely on System.gc(), but that doesn’t mean it doesn’t work at all.

Reference

>

碰到的问题

今天做了一个feature的admin的接口api/xxv/abc/feature,便于ops在admin上直接管理配置信息。但是我们的域名是 ttt.company.com,所以admin应该访问
http://ttt.company.com/api/xxv/abc/feature
但是admin的域名是admin(-test).company.com,而且admin后台是需要登录的。这样就导致了前端跨域传cookie会有问题。

解决的方案

nginx 做请求转发

目前的做法是在服务端之前做一个反向代理,admin请求同域名的
http://admin-test.company.com/api/abc/feature
然后在nginx层将请求转发到
http://ttt.company.com/api/xxv/abc/feature

对于staging和online不同的环境,将请求转发到不同的server即可。

请求转发
http://admin-test.company.com/api/abc/feature
->
http://ttt.company.com/api/xxv/abc/feature

ngin配置

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name admin-test.company.com;

location /api/abc {
rewrite /api/abc/(.*) /api/xxv/$1 break;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://staging.server.hostname:8080;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name admin.company.com;

location /api/abc {
rewrite /api/abc/(.*) /api/xxv/$1 break;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://myserver-write-nodes;
}
}

服务端处理跨域请求

在返回的response header中加入允许跨域访问的属性。例如:

Access-Control-Allow-Origin: {允许的域名}

更多信息参考:跨域 HTTP 请求(Cross-site HTTP request)

Btrace的简介

Btrace是由Kenai 开发的一个开源项目,是一种动态跟踪分析JAVA源代码的工具。它可以用来帮我们做运行时的JAVA程序分析,监控等等操作。

官方参考手册

https://kenai.com/projects/btrace/pages/UserGuide

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import com.sun.btrace.annotations.*;
import com.sun.btrace.AnyType;
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class TestServiceImplTrace {
@TLS
private static long service_get_data_startTime = 0;

@OnMethod(
clazz = "com.xxx.mms.test.impl.TestServiceImpl",
method = "getTestData"
)
public static void startTestServiceImplExecute() {
section_facade_impl_startTime = timeMillis();
}

@OnMethod(
clazz = "com.xxx.mms.test.impl.TestServiceImpl",
method = "getTestData"
location = @Location(Kind.RETURN)
)
public static void endTestServiceImplExecute(AnyType[] args) { // 传入所有参数
long time = timeMillis() - section_facade_impl_startTime;

Object obj = args[4];
Integer end = (Integer)obj; // 将第5个参数转成Integer

printFields(args[0]); // 打印第1个参数的所有成员变量的值

if(end == 6){
print(strcat(“service getTestData execute time(millis): ", str(time)));
print(strcat(“\t string param: ", str(args[3]))); // 将第4个参数转成string并打印
println(strcat(“\t end: ", str(end)));
}
}
}

心得

  1. btrace脚本的函数都没有走进去时,btrace pid tracing.java 是得不到结果的。
  2. Kind.LINE指向的行必须是代码能运行到的行。比如,以括号结束的行和空行都是无效的。
  3. 在刚启动btrace脚本监控时,会存在较大的耗时
  4. print有很多功能:
    printNumberMap
    printFields: print 每个域
    printArray:print 数组
  5. 如果服务的qps较低(0.2),直接去机器上app222通过ip请求,btrace的event不好用也达不到触发某个请求的目的,这个时候可以直接在本机请求此server的api,虽然与实际情况不符,但是能知道耗时的比例关系。

Btrace的原理

Btrace是由:Attach API + BTrace脚本解析引擎 + ASM + JDK6 Instumentation组成。简单来说就是:用Attach API附加*.jar然后使用BTrace脚本解析引擎 + ASM来实现对类的修改,在使用Instumentation实现类的内存替换。可详细的说明可以看refers的几篇文章。

使用Btrace对java进程的影响

  • 装载时的影响:
    btrace每次使用,都会重新load所有的class。当然如果OnMethod不匹配,是不会被重新装载。所以跟你的OnMethod的匹配规则很有关系,如果使用+java.lang.Object。那就死定了。
  • 退出后的影响:
    btrace监控每次退出后,原先所有的class都不会被恢复,你的所有的监控代码依然一直在运行

抓取了下btrace改写过后的类:

1
2
3
4
5
6
public InstrumentServer(String ip, String port)
{
$btrace$com$agapple$btrace$Instrumentor$InstrumentTracer$bufferMonitor(this);
this.ip = ip;
this.port = port;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static void $btrace$com$agapple$btrace$Instrumentor$InstrumentTracer$bufferMonitor(@Self Object arg0)
{
if (!BTraceRuntime.enter(InstrumentTracer.runtime)) return;
try {
Field ipField = BTraceUtils.field("com.agapple.btrace.Instrumentor.InstrumentServer", "ip");
Field portField = BTraceUtils.field("com.agapple.btrace.Instrumentor.InstrumentServer", "port");

String ip = (String)BTraceUtils.get(ipField, self);
String port = (String)BTraceUtils.get(portField, self);

BTraceUtils.println(BTraceUtils.strcat(BTraceUtils.strcat(BTraceUtils.strcat("ip : ", BTraceUtils.str(ip)), " port : "), BTraceUtils.str(port)));
BTraceRuntime.leave(); return; } catch (Throwable localThrowable) { BTraceRuntime.handleException(localThrowable);
}
}

注意其中的

1
if (!BTraceRuntime.enter(InstrumentTracer.runtime)) return;

再看一下BTraceRuntime中对应方法的实现:

1
2
3
4
5
private volatile boolean disabled;
public static boolean enter(BTraceRuntime current) {
if (current.disabled) return false;
return map.enter(current);
}

每次执行你的监控代码之前会先进行一个判断,判断当前是否处于监控中。你的客户端发起了exit指令后,该方法判断false,直接return。

所以btrace使用退出后会让你的代码多走了一个方法调用+一个对象属性判断,所以说影响还是非常少的。

推荐阅读

Btrace系列之一:Btrace的基本原理 http://victorzhzh.iteye.com/blog/965789
btrace一些你不知道的事(源码入手) http://agapple.iteye.com/blog/1005918

Reference

>
Java SE 6 新特性: Instrumentation 新功能 http://www.ibm.com/developerworks/cn/java/j-lo-jse61/
Btrace系列之一:Btrace的基本原理 http://victorzhzh.iteye.com/blog/965789
btrace一些你不知道的事(源码入手) http://agapple.iteye.com/blog/1005918

设值注入(推荐)

1
2
3
4
<bean id="myService" class="com.zane.test.MyServiceImpl">
<property name="serializer" ref="Serializer"/>
<property name="httpService" ref="httpService"/>
</bean>

构造器注入(死的应用)

1
2
3
4
<bean id="myModel" class="com.zane.test.MyModel">
<constructor-arg index="0" value="${name}"/>
<constructor-arg index="1" value=“20"/>
</bean>

注入List

1
2
3
4
5
6
7
8
9
<bean id="myTypes" class="java.util.ArrayList">
<constructor-arg>
<list>
<value type="com.zane.test.MyType">A</value>
<value type="com.zane.test.MyType">B</value>
<value type="com.zane.test.MyType">C</value>
</list>
</constructor-arg>
</bean>

注入Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<bean id="myTypeValueMap" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="#{T(com.zane.test.MyType).A}">
<value type="java.lang.Integer">3</value>
</entry>
<entry key="#{T(com.zane.test.MyType).B}">
<value type="java.lang.Integer">4</value>
</entry>
<entry key="#{T(com.zane.test.MyType).C}">
<value type="java.lang.Integer">5</value>
</entry>
</map>
</constructor-arg>
</bean>

当注入的是第三方的jar包的key类型时,需要使用@Resource注入

1
2
3
@Resource
@Qualifier("myTypeValueMap")
private Map<MyType, String> myTypeValueMap;

否则使用Autowired即可

1
2
3
@Autowired
@Qualifier("myTypeValueMap")
private Map<MyType, String> myTypeValueMap;