Winse Blog

走走停停都是风景, 熙熙攘攘都向最好, 忙忙碌碌都为明朝, 何畏之.

用ADT调试Xamarin程序中的Java库

在编写SDK的时刻,有用户需要使用Xamarin来开发应用。我们这边暂时没有这个方面的经验,有点瞎扯扯意味,路是崎岖的前进是痛苦的。

封装Android-SDK

Xamarin是使用C#语言来编写代码的,所以需要先把Android的jar库包装成为C#的代码。可选方式有3种),这里选用Wrapper的形式,不过多讲解,看文章Binding a Java Library - Consuming .JARs from C#/)。

建立Binding项目,把依赖的包加入到Jars目录下。由于Bmob-Android官方的包是进行混淆的,有些代码不会用到的/没有必要Wrapper生成jni代码调用的,可以通过removenote去掉不生成C#的wrapper类。第二点就是java的泛型是会被抹掉的,而C#的是会编入程序中的,遇到Comparable这种类型的方法时,需要进行参数强制转换下。第三点就是接口回调,有多个方法时会导致名称冲突,需要为每个接口的方法都配置一个Args的节点属性。这些都是官网的例子中有说明,有需要可以具体参考上面链接的文章内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<metadata>
  <remove-node path="/api/package[@name='cn.bmob.im.db']" />

  <attr path="/api/package[@name='cn.bmob.im.inteface']/interface[@name='DownloadListener']/method[@name='onError']" name="argsType">DownloadListenerErrorEventArgs</attr>
</metadata>

<enum-method-mappings>
  <mapping jni-class="cn/bmob/im/bean/BmobRecent">
    <method jni-name="compareTo" parameter="p0" clr-enum-type="Java.Lang.Object" />
  </mapping>
  <mapping jni-class="cn/bmob/im/BmobDownloadManager">
    <method jni-name="doInBackground" parameter="p0" clr-enum-type="Java.Lang.Object[]" />
  </mapping>
</enum-method-mappings>

还有另一个坑是,混淆后内部类会被扁平化,导致jar2xml执行时获取类的getSimpleName名称会抛异常,我这里直接反编译源码改成getName就好了,仅仅是代码中全路径和仅类名的却别,暂时来看没啥印象。

然后编译,加入到主项目的依赖中就可以使用该库的Java功能了。名称可能并不能全部对应上,与Java中的方法名和常量名大小写、下划线的不同罢了。

调试

下面是重点,但是很简短。

作为写SDK的,肯定不仅仅要用特定的工具,还的把中间的过程也扭顺,即既要做一个好点(example),又得实现连接的线(SDK)。

如果Android SDK的代码没有执行,该怎么办?Xamarin中都是C#的代码并不能用于调试java啊!问题自然归结到怎么用两个工具(Xamarin和Eclipse)来同时调试一个Xamarin Android应用的问题?!

先讲讲我遇到的坑,由于是开发者发给我的应用,不知道结构是怎么样的。我直接用Xamarin打开,是没有带可执行属性的,在Run-With菜单中是能看到我的实体机器的,但是就是不能把程序发布上去!提示【执行失败。未将对象引用设置到对象的实例。】然后就没了。最终在stackoverflow中找到了类型问题的解决方法,需要设置运行属性。

配置如下,在解决方案属性中【构建-配置-ConfigurationMappings】把项目添加为构建项。

可能还会遇到的问题是版本的问题,报错【java.lang.RuntimeException: Unable to get provider mono.MonoRuntimeProvider: java.lang.RuntimeException: Unable to find application Mono.Android.DebugRuntime or Mono.Android.Platform.ApiLevel_19!】需要在csproj的配置中修改AndroidUseLatestPlatformSdk属性为false。

下面的步骤才是本文的关键:

首先,在MainActivity的onCreate方法开始出打个断点,便于初始化功能调试,点击左上角的开始运行按钮。这样就能把代码发布到机器,且运行后会停留在onCreate处。

Xamarin调试效果图

再,打开ecilpse导入obj\Debug\android目录下的项目【Import-Android-Existing Android Code Into Workspace】,错误什么的无所谓。这个项目只是用了ADT能识别而已。然后再java包的代码里面打上断点。

最后,起到定乾坤作用的就是DDMS的Devices试图的小爬虫,选择你要调试的程序,然后点击它就可以了。切换到Xamarin继续运行程序,接下来就会运行停留到eclipse中的java包中的断点程序出。

OK,接下来就按照eclipse的调试技巧弄就好了。

步骤很简单,查询的路子却是艰辛的。第一次尝试成本总是昂贵的,第二步自然会慢慢顺起来。fighting…

–END

查找逐步定位Java程序OOM的异常实践

类C语言,继C++之后的最辉煌耀眼的明星都属Java,其中最突出的又数内存管理。JVM对运行在其上的程序进行内存自动化分配和管理,减少开发人员的工作量之外便于统一的维护和管理。JDK提供了各种各样的工具来让开发实施人员了解运行的运行状态。

  • jps -l -v -m
  • jstat -gcutil 2000 100
  • jmap
  • jinfo 查看参数例子
  • jstack
  • jvisualvm/jconsole
  • mat(MemoryAnalyzer)
  • btrace
  • jclasslib(查看局部变量表)

前段时间,接手(前面已经有成型的东西)使用Hadoop存储转换的项目,但是生产环境的程序总是隔三差五的OOM,同时使用的hive0.12.0也偶尔出现内存异常。这对于运维来说就是灭顶之灾!搞不好什么时刻程序就挂了!!必须咬咬牙把这个问题处理解决,开始把老古董们请出来,翻来基本不看的后半部分–Java内存管理。

  • 《Java程序性能优化-让你的Java程序更快、更稳定》第5章JVM调优/第6章Java性能调优工具
  • 《深入理解Java虚拟机-JVM高级特性与最佳实践》第二部分自动内存管理机制

这里用到的理论知识比较少。主要用Java自带的工具,加上内存堆分析工具(mat/jvisualvm)找出大对象,同时结合源代码定位问题。

下面主要讲讲实践,查找问题的思路。在本地进行测试的话,我们可以打断点,可以通过jvisualvm来查看整个运行过程内存的变化趋势图。但是到了linux服务器,并且还是生产环境的话,想要有本地一样的图形化工具来监控是比较困难的!一般服务器的内存都设置的比较大,而windows设置的内存又有限!所以内存达到1G左右,立马dump一个堆的内存快照然后下载到本地进行来分析(可以通过-J-Xmx调整jvisualvm的内存)。

  • 首先,由于报错是在Configuration加载配置文件时抛出OOM,第一反应肯定Configuraiton对象太多导致!同时查看dump的堆内存也佐证了这一点。直接把程序中的Configuration改成单例。

程序对象内存占比排行(jmap -histo PID):

使用mat或者jvisualvm查看堆,确实Configuration对象过多(jmap -dump:format=b,file=/tmp/bug.hprof PID):

  • 修改后再次运行,但是没多大用!还是OOM!!

  • 进一步分析,发现在Configuration中的属性/缓冲的都是弱引用是weakhashmap。

OOM最终问题不在Configuration对象中的属性,哪谁hold住了Configuration对象呢??

  • 再次从根源开始查找问题。程序中FileSystem对象使用FileSystem.get(URI, Configuration, String)获取,然后调用get(URI,Configuration)方法,其中的CACHE很是刺眼啊!

缓冲FileSystem的Cache对象的Key是URI和UserGroupInformation两个属性来判断是否相等的。对于一个程序来说一般就读取一个HDFS的数据即URI前部分是确定的,重点在UserGroupInformation是通过UserGroupInformation.getCurrentUser()来获取的。

即获取在get时UserGroupInformation.getBestUGI得到的对象。而这个对象在UnSecure情况下每次都是调用createRemoteUser创建新的对象!也就是每调用一次FileSystem.get(URI, Configuration, String)就会缓存一个FileSystem对象,以及其hold住的Configuration都会被保留在内存中。

只消耗不释放终究会坐吃山空啊!到最后也就必然OOM了。从mat的UserGroupInformation的个数查询,以及Cache对象的总量可以印证。

问题处理

把程序涉及到FileSystem.get调用去掉user参数,使两个参数的方法。由于都使用getCurrentUser获取对象,也就是说程序整个运行过程中就一个FileSystem对象,但是与此同时就不能关闭获取到的FileSystem,如果当前运行的用户与集群所属用户不同,需要设置环境变量指定当前操作的用户!

1
System.setProperty("HADOOP_USER_NAME", "hadoop");

查找代码中调用了FileSystem#close是一个痛苦的过程,由于FileSystem实现的是Closeable的close方法,用Open Call Hierarchy基本是大海捞中啊,根本不知道那个代码是自己的!!这里用btrace神器让咋也高大上一把。。。

当时操作的步骤找不到了,下图是调用Cache#getInternal方法监控代码GIST

hive0.12内存溢出问题

hive0.12.0查询程序MR内容溢出

在hive-0.13前官网文档中有提到内存溢出这一点,可以对应到FileSystem中代码的判断。

1
2
3
4
String disableCacheName = String.format("fs.%s.impl.disable.cache", scheme);
if (conf.getBoolean(disableCacheName, false)) {
  return createFileSystem(uri, conf);
}

hive0.13.1处理

新版本在每次查询(session)结束后都会把本次涉及到的FileSystem关闭掉。

理论知识

从GC类型开始讲,对自动化内存的垃圾收集有个整体的感知: 新生代/s0(survivor space0、from space)/s1(survivor space1、to space)/永久代。虚拟机参数-Xmx,-Xms,-Xmn-Xss)来调节各个代的大小和比例。

  • -Xss 参数来设置栈的大小。栈的大小直接决定了函数的调用可达深度
  • -XX:PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -Xms40m -Xmx40m -Xmn20m
  • -XX:NewSize-XX:MaxNewSize
  • -XX:NewRatio-XX:SurvivorRatio
  • -XX:PermSize=2m -XX:MaxPermSize=4m -XX:+PrintGCDetails
  • -verbose:gc
  • -XX:+PrintGC
  • -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/bug.hprof -XX:OnOutOfMemoryError=/reset.sh
  • jmap -dump:format=b,file=/tmp/bug.hprof PID
  • jmap -histo PID > /tmp/s.txt
  • jstack -l PID

–END

巧用Equals和Hashcode

java中让人疑惑的一点就是等于的判断,有使用==equals, 有一些专门字符串初始化的资料来考你在是否已经真正的掌握判断两个对象是否一致。

同时,在重写equals时很多资料都强调要重写hashcode。

java中HashMap就是基于equals和hashcode来实现拉链的键值对。Map中存储了entry<K,V>的数组,数组的下标是基于对象的hashcode再对entry长度[并]&的结果。

使用set/map来实现集合,并且对象重写了equals但没有重写hashcode的情况下,得到的结果与你臆想的不同。同时,在特定场景结合hashcode和equals可以实现很酷的效果。

  • 第一个例子A 重写了equals但是没有重写hashcode(ERROR),此时判断元素是否在集合中结果可能并不是你想要的。
  • 第二个B的例子 重写hashcode和equals对应后就正确了。
  • 第三个AA是个很酷的例子 equals的条件更强,可以实现类似map<string, list<string>>的效果。
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
static class A {

  String name;
  int age;

  public A(String name, int age) {
      this.name = name;
      this.age = age;
  }

  @Override
  public boolean equals(Object obj) {
      return new EqualsBuilder().append(getClass(), obj.getClass()).append(name, ((A) obj).name).isEquals();
  }

}

@Test
public void testA() {
  Set<A> set = new HashSet<>();

  set.add(new A("abc", 12));
  set.add(new A("abc", 14));

  System.out.println(set.size());

  System.out.println(set.contains(new A("abc", 0)));
}

static class B extends A {

  public B(String name, int age) {
      super(name, age);
  }

  @Override
  public int hashCode() {
      return this.name.hashCode();
  }
}

@Test
public void testB() {
  /* Set<A> */Set<B> set = new HashSet<>();

  set.add(new B("abc", 12));
  set.add(new B("abc", 14));

  System.out.println(set.size());

  System.out.println(set.contains(new B("abc", 0)));
}

static class AA extends A {
  public AA(String name, int age) {
      super(name, age);
  }

  @Override
  public boolean equals(Object obj) {
      return new EqualsBuilder().append(getClass(), obj.getClass()).append(name, ((A) obj).name)
              .append(age, ((A) obj).age).isEquals();
  }

  @Override
  public int hashCode() {
      return this.name.hashCode();
  }
}

@Test
public void testAA() {
  // 实现Map<String, Set<String>>的效果
  Set<AA> set = new HashSet<>();

  set.add(new AA("abc", 12));
  set.add(new AA("abc", 14));

  System.out.println(set.size());

  System.out.println(set.contains(new AA("abc", 0)));
}

参考

–END

[读码] Hadoop2 Balancer磁盘空间平衡(上)

javadoc

在一些节点满了或者加入了新的节点情况下,使用balancer工具可以平衡HDFS集群磁盘空间使用率。该功能作为一个单独的程序,能同时与集群的其他文件操作一起进行。

threshold(阀值)参数是个介于1%~100%的值,默认情况下是10%。指定了集群达到平衡的指标值。当节点的利用率(所在节点的磁盘使用率,只HDFS可利用的部分)与集群利用率(集群HDFS的使用率)之间的差不大于阀值threshold就表示集群已经处于平衡状态。所以,阀值threshold越小,集群各节点数据分布越平衡(集群越平衡),当然这也会耗费更多的时间来达到小的平衡阀值。同时,当数据同时又在进行读写操作时,可能平衡并不能达到非常小的阀值。

这个工具依次地把磁盘使用率高的机器的块移动到使用率低的数据节点上。每次迭代移动/接受的数据小于10G或者节点容量的阀值百分比(In each iteration a datanode moves or receives no more than the lesser of 10G bytes or the threshold fraction of its capacity. )。每次迭代不会大于20分钟。每次迭代完成后,balancer把数据节点信息更新到namenode,重新计算利用率后,再进行下一次迭代直到集群利用率阀值。

配置dfs.balance.bandwidthPerSec控制balancer操作传输的带宽,默认配置是1048576(1M/s)这个属性决定了一个块从一个数据节点移动到另一个节点的最大速率。默认是1M/s。bandwidth越高集群达到平衡越快,但是程序之间的竞争会更激烈。如果通过配置文件来修改这个属性,需要在下次启动HDFS才能生效。可以通过hdfs dfsadmin -setBalancerBandwidth 10485760来动态的设置。

每次迭代会输出开始时间,迭代的次数,上一次迭代移动的数据量,集群达到平衡还需要移动的数据量,该次迭代将移动的数据量。一般情况下,“Bytes Already Moved”将会增加同时“Bytes Left To Move”将会减少(但是如果此时有大数据量写入,那么Bytes Left To Move可能不减反增)。

1
Time Stamp               Iteration#  Bytes Already Moved  Bytes Left To Move  Bytes Being Moved

多个balancer程序不能同时运行。

balancer程序会自动退出当存在以下情况:

  • 集群已经平衡
  • 没有块将会被移动
  • 连续5次的处理没有块移动
  • 连接namenode时出现IOException
  • 另一个balancer程序在跑

根据上面的5中情况,balancer程序退出,同时会打印如下的信息:

  • The cluster is balanced. Exiting
  • No block can be moved. Exiting…
  • No block has been moved for 5 iterations. Exiting…
  • Received an IO exception: failure reason. Exiting…
  • Another balancer is running. Exiting…

当balancer运行时,管理员可以随时运行stop-balancer.sh来中断balancer程序。

–END

Hadoop的datanode数据节点软/硬件配置应该一致

最好的就是集群的所有的datanode的节点的硬件配置一样!当然系统时间也的一致,hosts等等这些。机器配置一样时可以使用脚本进行批量处理,给维护带来很大的便利性。

今天收到运维的信息,说集群的一台机器硬盘爆了!上到环境查看df -h发现硬盘配置和其他datanode的不同!但是hadoop hdfs-site.xml的dfs.datanode.data.dir却是一样的!

经验: dir的配置应该是一个系统设备对应一个路径,而不是一个系统目录对应dir的一个路径!

问题现象以及根源

问题机器A的磁盘情况:

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
[hadoop@hadoop-slaver8 ~]$ df -h
文件系统              容量  已用  可用 已用%% 挂载点
/dev/sda3             2.7T  2.5T   53G  98% /
tmpfs                  32G  260K   32G   1% /dev/shm
/dev/sda1              97M   32M   61M  35% /boot

[hadoop@hadoop-slaver8 /]$ ll
总用量 170
dr-xr-xr-x.   2 root   root    4096 2月  12 19:39 bin
dr-xr-xr-x.   5 root   root    1024 2月  13 02:40 boot
drwxr-xr-x.   2 root   root    4096 2月  23 2012 cgroup
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data1
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data10
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data11
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data12
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data13
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data14
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data15
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data2
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data3
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data4
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data5
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data6
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data7
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data8
drwxr-xr-x.   3 hadoop hadoop  4096 6月  30 10:36 data9

再看集群其他机器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[hadoop@hadoop-slaver1 ~]$ df -h
文件系统              容量  已用  可用 已用%% 挂载点
/dev/sda3             2.7T   32G  2.5T   2% /
tmpfs                  32G   88K   32G   1% /dev/shm
/dev/sda1              97M   32M   61M  35% /boot
/dev/sdb1             1.8T  495G  1.3T  29% /data1
/dev/sdb2             1.8T  485G  1.3T  28% /data2
/dev/sdb3             1.8T  492G  1.3T  29% /data3
/dev/sdb4             1.8T  488G  1.3T  28% /data4
/dev/sdb5             1.8T  486G  1.3T  28% /data5
/dev/sdb6             1.8T  480G  1.3T  28% /data6
/dev/sdb7             1.8T  479G  1.3T  28% /data7
/dev/sdb8             1.8T  474G  1.3T  28% /data8
/dev/sdb9             1.8T  480G  1.3T  28% /data9
/dev/sdb10            1.8T  478G  1.3T  28% /data10
/dev/sdb11            1.8T  475G  1.3T  28% /data11
/dev/sdb12            1.8T  489G  1.3T  29% /data12
/dev/sdb13            1.8T  475G  1.3T  28% /data13
/dev/sdb14            1.8T  476G  1.3T  28% /data14
/dev/sdb15            1.8T  469G  1.3T  27% /data15

出问题机器没有挂存储,仅仅是建立了对应的目录结构,并不是把目录挂载到单独的存储设备上。

同时查看50070的前面的信息,hadoop把每个逗号分隔后的路径默认都做一个磁盘设备来计算!

1
2
3
Node               Address             ..Admin State CC    Used  NU    RU(%) R(%)      Blocks Block  Pool Used Block Pool Used (%)
hadoop-slaver1    192.168.32.21:50010 2   In Service  26.86   7.05    1.37    18.44   26.25       68.66   264844  7.05    26.25   
hadoop-slaver8    192.168.32.28:50010 1   In Service  37.94   2.46    34.71   0.77    6.48        2.03    29637   2.46    6.48    

配置容量是所有配置的路径所在盘容量的累加。总的剩余空间(余量)也是各个dir配置路径的剩余空间累加的!这样很容易出现问题! 最好的就是集群的所有的datanode的节点的硬件配置一样!当然系统时间也的一致,hosts等等这些。

问题处理

首先得把问题解决啊:

  • dfs.datanode.data.dir路径个数调整为磁盘个数!
  • 修改该datanode的hdfs-site的配置,添加dfs.datanode.du.reserved,留给系统的空间设置为400多G。
  • 冗余份数也没有必要3份,浪费空间。如果两台机器同时出现问题,还是同一份数据,那只能说是天意!你可以去趟澳门赌一圈了!
1
2
3
4
5
6
7
8
9
10
11
12
<property>
<name>dfs.datanode.data.dir</name>
<value>/data1/hadoop/data</value>
</property>
<property>
<name>dfs.datanode.du.reserved</name>
<value>437438953472</value>
</property>
<property>
<name>dfs.replication</name>
<value>2</value>
</property>

设置了reserved保留空间后,再看LIVE页面slaver8的容量变少了且正好等于(盘的容量2.7T-430G~=2.26T 计算容量的hdfs源码在FsVolumeImpl.getCapacity())。

1
hadoop-slaver8   192.168.32.28:50010 1   In Service  2.26    2.23    0.00    0.03    98.66

datanode和blockpool的平衡处理,可以参考Live Datanodes的容量和进行!

1
2
3
4
5
6
7
[hadoop@hadoop-master1 ~]$ hdfs balancer -help
Usage: java Balancer
        [-policy <policy>]      the balancing policy: datanode or blockpool
        [-threshold <threshold>]        Percentage of disk capacity

[hadoop@hadoop-slaver8 ~]$ hadoop-2.2.0/bin/hdfs getconf -confkey dfs.datanode.du.reserved
137438953472

删除一些没用的备份数据。配置好以后,重启当前slaver8节点,并进行数据平衡(如果觉得麻烦,直接丢掉原来的一个目录下的数据也行,可能更快!均衡器运行的太慢!!)

1
2
3
4
5
6
7
8
9
[hadoop@hadoop-slaver8 ~]$  ~/hadoop-2.2.0/sbin/hadoop-daemon.sh stop datanode

[hadoop@hadoop-slaver8 ~]$  for i in 6 7 8 9 10 11 12 13 14 15; do  cd /data$i/hadoop/data/current/BP-1414312971-192.168.32.11-1392479369615/current/finalized;  find . -type f -exec mv {} /data1/hadoop/data/current/BP-1414312971-192.168.32.11-1392479369615/current/finalized/{} \;; done

[hadoop@hadoop-slaver8 ~]$  ~/hadoop-2.2.0/sbin/hadoop-daemon.sh start datanode


[hadoop@hadoop-master1 ~]$ hdfs dfsadmin -setBalancerBandwidth 10485760
[hadoop@hadoop-master1 ~]$ hdfs balancer -threshold 60

查看datanode的日志,由于移动数据,有些blk的id一样,会清理一些数据。对于均衡器程序的阀值越小集群越平衡!默认是10(%),会移动很多的数据(准备看下均衡器的源码,了解各个参数以及运行的逻辑)!

参考

–END