玩转Java字节码(上)

  Java字节码(Byte-code)是指Java源代码编译而成的,供JVM虚拟机执行的代码。用文本编辑器打开将是一团乱码,用十六进制编辑器打开能勉强看懂头部一些规范,例如魔数,主次版本。而用/bin/javap“反编译”之后可以得到一个人类可读的代码段,类似于用Wireshark来分析cap数据包。

  要看懂这个文件必须要知道字节码规范,主要有以下两个表格内容构成。其中表1是Class文件的组成部分,各段依次排列,排列紧密,无多余的分隔符。其中u1~u8表示占用字节数,1表示1个字节,2表示2个字节,4字节,8字节。例如魔数:仅有1个,占用4个字节,位于文件的前4个字节。紧接着4个字节是minor_version,major_version各占用2个字节。接下来2个字节是常量池数量统计,意味着一个Class不能有超过65535个常量池(maybe)。接下来是常量池,常量池的格式由表2来定义,下一段再来分析。依次类推可以将整个Class文件分析出来。

表1Class文件组成

  常量池中的每一个常量项通常有2~3个项目组成。例如:CONSTANT_Utf8_info这一项,第1个字节是表示定义(tag),值为1,紧接着2个字节表示该项将占用的长度(length),意味着接下来的几个字节长度将是字符串的实际内容。

  再来分析下CONSTANT_Integer_info项,从下表可以看出头一个字节tag值为3,接下来4个字节安高位在前编码表示int值,所以在Java中int数据类型的最大值是去掉一个符号位的0x7FFFFFFF。Utf8,Integer,Float,Long,Double这5个都是类似的,直接以值的形式存储。其余常量项都是存储着引用值(index),分别指向这5个值或其它地方,这里不一一介绍了。

  等等java不是八大基本数据类型吗,这里怎么只有提到了4个,没错,小于int的都当作int处理了。也就是boolean,byte,char,short都“拉长”至int级别来对待。例如:boolean a = “true” 对应的字节码指令是:iconst_1,putfield a,将int型常量值1进栈赋值给变量a。

  介于编译器可能会做一些性能优化,例如int值超过2字节(32768)才会加入到常量项,小于2字节由sipush指令在运行时分配。

表2Class 14个常量项

  分析这玩意实在有些无聊,既然本文是以玩转为主题,自然就说点有趣的。首先一个简单的Java代码如下:

package classloader;

/**
 * Created by dorole.com on 2016/6/13.
 */
public class Hello {
    public String sayHello(String name) {
        System.out.println(this.getClass().getName() + " -> " + name);
        return name;
    }
}

  这段代码自然再简单不过了,随便一个文本编辑器敲进去,javac编译,找个main方法调用完事。那能不能不编译直接调用?当然没问题,先不谈这个,先来看看编译后究竟是个什么样子。我们用WinHex来打开编译好的Class文件,截图如下:

Hello.class

  黑色标记的是全部的常量项tag值(一个个标出来,该是有多蛋疼~),自己可以对照表2慢慢分析。当然也可以借助javap来“反编译”下,输出更为直观的表格:

Constant pool:
   #1 = Utf8               classloader.Hello
   #2 = Class              #1             // "classloader.Hello"
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = NameAndType        #5:#6          // "<init>":()V
   #8 = Methodref          #4.#7          // java/lang/Object."<init>":()V
   #9 = Utf8               sayHello
  #10 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #11 = Utf8               java/lang/System
  #12 = Class              #11            // java/lang/System
  #13 = Utf8               out
  #14 = Utf8               Ljava/io/PrintStream;
  #15 = NameAndType        #13:#14        // out:Ljava/io/PrintStream;
  #16 = Fieldref           #12.#15        // java/lang/System.out:Ljava/io/PrintStream;
  #17 = Utf8               java/lang/StringBuilder
  #18 = Class              #17            // java/lang/StringBuilder
  #19 = Methodref          #18.#7         // java/lang/StringBuilder."<init>":()V
  #20 = Utf8               getClass
  #21 = Utf8               ()Ljava/lang/Class;
  #22 = NameAndType        #20:#21        // getClass:()Ljava/lang/Class;
  #23 = Methodref          #4.#22         // java/lang/Object.getClass:()Ljava/lang/Class;
  #24 = Utf8               java/lang/Class
  #25 = Class              #24            // java/lang/Class
  #26 = Utf8               getName
  #27 = Utf8               ()Ljava/lang/String;
  #28 = NameAndType        #26:#27        // getName:()Ljava/lang/String;
  #29 = Methodref          #25.#28        // java/lang/Class.getName:()Ljava/lang/String;
  #30 = Utf8               append
  #31 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #32 = NameAndType        #30:#31        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #33 = Methodref          #18.#32        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #34 = Utf8                ->
  #35 = String             #34            //  ->
  #36 = Utf8               toString
  #37 = NameAndType        #36:#27        // toString:()Ljava/lang/String;
  #38 = Methodref          #18.#37        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #39 = Utf8               java/io/PrintStream
  #40 = Class              #39            // java/io/PrintStream
  #41 = Utf8               println
  #42 = Utf8               (Ljava/lang/String;)V
  #43 = NameAndType        #41:#42        // println:(Ljava/lang/String;)V
  #44 = Methodref          #40.#43        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #45 = Utf8               Code

  这里我仅放上常量池部分,这下很好对了把。Class文件就是这样一个紧凑结构,不愧是为嵌入式而打造的一门语言。回到之前的问题,有没有可能自己来生成字节码?答案是肯定的,下回分解。

一次DDOS攻击记录

  就在昨天儿童节的时候,大概9点半左右收到微信公众号的报警信息,发现是服务器没有响应,5分钟内6次请求没有及时返回。当时并没有过多在意,因为之前也有过1~2次失败,都是是偶然因素。过了几分钟又收到dnspod的邮件报警,内容是snapast.com主站连接超时,这才意识到可能服务器宕机了。

  立刻手动访问一次,果然是打不开了,ssh也连接不上,下意识认为可能是服务器被重启了,因为之前托管在linkcloud的时候,隔个把月总会挂掉一下,但迁移到阿里云后一直很稳定,不太会出问题。

  登录到阿里云后台,在实例监控看到CPU一直满负荷运行,入网流量达到22M/s(云盾基础防护中显示有接近30M/s,忘了PPS多少来着),出网也已经塞满2M带宽。

实例监控

  通过阿里的终端登录到主机上,发现CPU是被4个nginx进程占满,后端几个tomcat负载并不高,说明并不是一般的大规模http请求(cc攻击)。而且nginx access log也是正常,但error log有很多xxx worker_connections are not enough,说明确实连接数较高,超过设定值。通过netstat统计居然有1.6w左右的连接数,远高于平时的300左右。

root@snapast:~# netstat -an | wc -l
16089

  到这里基本能判断极有可能是SYN Flood,想到云盾有一定抵抗DDOS的能力,试着开启流量清洗看看,原来人家的默认触发条件是流量100M/s,PPS是1w,我这点攻击还没办法触发呢,于是手动设置了一个最低条件达到10M/s就开始清洗,然而事实证明清洗毫无用处,依旧连接超时。但有个好处是开始清理后可以抓包。

  下载cap到本地用Wireshark打开一看,好家伙,果然是SYN握手攻击,大量的不同IP(美国为主)往443端口发SYN请求,极少数有回应RST,我猜应该是上层路由回的。云盾的抓包并不是你主机网卡包,而是在接入口的设备上。这下明了了,对于这种TCP层的攻击也没啥好办法抵御的,考虑到目前仅有相册用了https,所以先停掉nginx监听443端口,将机器负载降了下来,但也没办法彻底解决。有意思的是当时阿里云解析也出了故障没办法修改域名解析,屋漏偏逢连夜雨。

抓包

  就这么瞎折腾了2个小时,攻击才停止,一切也都恢复正常。今天来看,在微信接口调用统计中失败次数最为“壮观”的一天。

  现在想想其实也有一些办法可以减小宕机损失:比如将受攻击的域名临时解析到别处。再如将多个业务分离开来,用不同的域名,不同的IP接入,iptables将多次密集连接请求的IP drop掉。

Flat-UI 正确打开方式

  Flat-UI 是一款基于Bootstrap扁平风格的UI工具包,个人觉得风格比较美观,简洁,控件齐全,加之扁平化趋势,故用在了花图影铺项目中。有免费版和Pro版($39)可供选择,Pro版多了PSD原件,也用不到,选择免费就可以。

  众所周知,前端的项目一直以来都是拷贝HTML,CSS,JS到工程下直接引用就完事了,花图影铺之前就是这么做的,但这种做法已经非常过时了,自己做了修改后将无法再与官方的新版合并,而且有些细微的调整涉及到很多个地方要修改。对于Bootstrap这种巨复杂的设计,要定制化就几乎是不可能的事了。为了解决这些问题,前端开始有了新的玩法,从开发到测试再到构建都需要配套跟上。而Flat-UI 就是采用Grunt来进行自动化构建,用Bower来管理JS依赖,用Less来与编译CSS,等等等等。简单说下是如何构建Flat-UI的。

  Git clone下来的Flat-UI 包含了以下一些重要东西:

bower.json

这个是bower要用到的依赖声明文件,可以看到里面依赖了jQuery,bower会自动下载指定版本的jQuery到bower_components目录之下,以备后用。全局安装bower:npm install -g bower,新项目也可以用bower init来生成。

package.json

工程说明文件,包括工程名,作者,版本,Grunt依赖包等一些信息。如果一个新的项目,可以用grunt init打开一个向导一步一步填写。

Gruntfile.js

这个是Grunt执行脚本,会从上面的文件中读取值,里面定义了一些处理事件,比如清理之前构建,测试,压缩CSS,JS,生成文档,复制等等,甚至可以起一个静态服务器来调试。无比强大,类似于Maven pom文件。

  以上文件在Flat-UI工程目录中基本上不用修改,只需要在当前目录下执行bower install 下载依赖js包,grunt install 下载grunt工具,再执行grunt dist 就能在dist/目录下获得最新的可发布版本了,将该目录文件复制到web工程中引用就好了。

  关于less目录就包含了Flat-UI 所有CSS配置地方,主要修改的就是这里了,比如改变导航背景颜色等等。其中按模块归类的非常好,一眼就能看出该修改哪里,variables.less 定义了所用到的颜色,全局样式,排版,小图标,表格等等。修改起来非常容易,尤其还有一些联动的取色,通过一个基准色,计算出偏亮或者偏暗值,而不需要一个一个去查色板。修改完后只需要grunt dist 就能生成好CSS。

  Flat-UI 不仅是一个优秀的UI工具包,同时也是一个学习前后端分离构建的好例子,当然这只是针对我这以后端为主的开发者而言。

  参考:https://github.com/designmodo/Flat-UI

最优化使用阿里OSS云存储

  上一篇文章讲到了为何要迁移到阿里OSS,经过几天时间的实际验证,发现还是踩了不少的坑,这里记录下,供参考。

  首先有几个先决条件如下:

  1. 全站所有请求必须HTTPS
  2. 全站仅允许出现自有二级域名
  3. 上行和下行流量均不经过ECS服务器

  (代码洁癖 8)

  阿里OSS我们可以简单的认为分两个部分:

一是存储有关的,提供了两个访问域名:一个是公网,一个内网

<bucket>.oss-cn-shenzhen.aliyuncs.com (A域名)
<bucket>.oss-cn-shenzhen-internal.aliyuncs.com (B域名)

二是为图像处理(缩图,转码)有关的,也提供了一个访问域名:

<bucket>.img-cn-shenzhen.aliyuncs.com (C域名)

我们在这个图像处理的功能上面绑定了一个自有的二级域名:

image.snapast.com (D域名)

默认分配并指向了下面的CDN加速节点域名:

image.snapast.com.w.kunlunca.com (E域名)

  暂且称为A~E域名,注意其中细微差别。其中A和B域名本身是支持HTTPS的,但C不行,在绑定了D之后,可以上传SSL证书开启。A域名也可以绑定自有域名,但不支持HTTPS。图像处理设置了禁止原图访问,也就是C,D域名仅允许通过“样式”访问到缩略图。但A,B不受限制,本来就不同的系统。

于是第一个方案是这样的:

用户上传:HTTPS POST A域名
用户访问:HTTPS GET D域名

  初看没啥问题,除了引入一个第三方域名,不符合预定规范,但也运行良好,但有个很严重的问题,会泄漏原图,将D域名的路径拼接到A上面就能拿到原图。故pass掉,和客服聊了后给了个鸡贼的方案:

方案二:

用户上传:HTTPS POST upload.snapast.com -> proxy_pass -> B域名
用户访问:HTTPS GET D域名

  在nginx上将用户上行的POST请求转发到B域名,这样可以在nginx层面拦截原图访问,并可以支持自有域名HTTPS。虽然又点不符合规范了,虽然ECS下行带宽还是蛮大的,proxy_pass也是内网地址,似乎也无大碍。

方案三:
将bucket设置为私有,这样POST不变,GET需要增加signature,想想就复杂,搞的URL又长又丑,没再尝试。

  后来想想,能不能直接POST到D域名试试,居然成功了。Object 管理中证实了文件是传成功的,但感觉怪怪的,原来CDN还能upload。官方文档似乎也没有说这样用合不合理,暂且先如此把。

风光摄影的一些tips

  风光摄影里面离不开超广角,滤镜,长爆这些,但比这还要重要的是体力。上周迁移完了相册,去了一趟背仔角,南山区过去光坐车就消耗了2个小时。穿过华侨公墓径直走到海边,放下包裹,组装好脚架,滤镜支架,结果发现没带快门线,临时用iPhone来控制。站站蹲蹲几个小时,海边那些风化的石头也不太好坐,累的够呛。

  1. 优先选择原厂的有线快门,不为啥,稳定。之前拍夜景准备了JJC的无线快门,时不时的触发失败
  2. 构图稍低一点,利用广角畸变带来冲击力
  3. 准备好镜头纸,随时擦拭意外沾上的小水珠,几率很高哦
  4. 上ND镜后使用实时取景会非常耗电,预览噪点非常严重,自动提升ISO导致,所以多备电池吧
  5. 接上一条,CMOS过热严重,画面暗部涂抹感,彩噪严重,所以多关机休息
  6. 关闭防抖,自动对焦
  7. 准备小板凳,重要!!

 接下来就是好好享受吧~~

深圳盐田背仔角