Biaobiaoqi的博客

对Java字符串的探究

| Comments

问题的出发点

在网上看到一道题:

1
String str = new String("abc");

以上代码执行过程中生成了多少个 String 对象?

答案写的是两个。”abc”本身是一个,而 new 又生成了一个。

“abc”是什么

查看这句程序的字节码,如下:

1
2
3
4
5
NEW String
    DUP
    LDC "abc"
    INVOKESPECIAL String.<init>(String) : void
    ASTORE 1

指令ldc indexbyte的含义:将两字节的值从 indexbyte 索引的常量池中加载到方法栈上。

指令LDC "abc"说明了”abc”并不是直接以对象存在的,而是存在于常量池的索引中。String 的构造函数调用命令实际使用的就是 String 类型作为参数,那么,栈上应该有一个 String 类型的索引。

由此我们得出,在字节码中,ldc 命令在常量池中找到了能索引到“abc”那个 String 对象的索引值。

常量池

常量池是类文件(.class)文件中的一部分,记录了许多常量信息,索引的字符串信息。

由于 Java 是动态加载的,类文件并没有包含程序运行时的内存布局,方法调用等无法直接记录出方法的物理位置,常量池通过索引的方法解决了这个问题。

AOP实践:java.lang.instrument的使用

| Comments

背景

rcjp 项目中,需要调用 ASM API(用于字节码处理的开源库)对字节码进行处理,目标是实现对 Java 程序运行时各种对象的动态跟踪,并进一步分析各个对象之间的关系。在此之前,需要考虑如何获取程序运行的入口。

首先,我考虑到了自定义类加载器(详情见参考资料),即在程序的 main 入口处,首先加载自定义的类加载器,然后通过反射技术使用这个类加载器加载并调用测试程序。这个方法缺点是:每次都必须先找到测试程序的入口类,而对于有的封装成 jar 的程序集合,这一点相对比较难控制。

于是,有了这里介绍的方法:通过 java.lang.instrument 实现的 java agent 对象操作字节码,是一种 AOP 的方法。

程序中,除了 ASMAgent 以外的所有类都是调用 ASM API 实现对测试程序中各个对象的构造、方法调用、属性赋值等操作行为的记录(其中对 Collection 子类的处理着实费了一番心血= =,字节码操作很细节,容易出错)。

Java构造方法中的执行顺序

| Comments

这道题来自 stackoverflow

问题描述:

有如下代码,求其输出内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test
{
    public int a = 10;
    Test(){System.out.println("1");}

    {System.out.println("2");}

    static{System.out.println("3");}

    public static void main(String args[])
    {
        new Test();
    }
}

分析

作为静态区段的语句,容易知道,3 是会最先出现的。容易弄错的一点是 1 和 2 的出现顺序。

我们可以参考字节码来分析。在 eclipse 中使用 ASM bytecode 插件,得到相应的字节码:

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
// class version 50.0 (50)  
// access flags 0x21  
public class Test {
  // compiled from: Test.java  
  static <clinit>() : void
    GETSTATIC System.out : PrintStream
    LDC "3"
    INVOKEVIRTUAL PrintStream.println(String) : void
    RETURN

  <init>() : void
    ALOAD 0: this
    INVOKESPECIAL Object.<init>() : void
    ALOAD 0: this
    BIPUSH 10
    PUTFIELD Test.a : int
    GETSTATIC System.out : PrintStream
    LDC "2"
    INVOKEVIRTUAL PrintStream.println(String) : void
    GETSTATIC System.out : PrintStream
    LDC "1"
    INVOKEVIRTUAL PrintStream.println(String) : void
    RETURN


  public static main(String[]) : void
    NEW Test
    INVOKESPECIAL Test.<init>() : void
    RETURN
}

正如我们所想,3 是被放在类构造方法中,这是类的初始化函数,固然在类的初始化时出现。

Java类的实例化总结

| Comments

java 类的实例化(instantiation)具有显性的和隐性的区别。

写 Java 代码时,我们所使用 new 的方法实例化最简单直接的显性实例化。而隐性的实例化则出现在 java 程序的整个生命周期中,包括 String、Class,StringBuffer 或者 StringBuilder 的实例化等等。

显性的实例化

new 关键字实例化对象

调用相应的构造函数完成实例化。(类中的非静态成员变量如果有初始化语句,都会被隐式的加入到构造函数中)代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test  {

    String strA = "xyz";
    String strB ;

    public Test(String str){
        strB = str ;
    }
    public static void main(String[] args){
            Test t = new Test("abc");
    }

}

在 eclipse 中装了 ASM bytecode 插件后,观察.class 文件中的构造函数对应的字节码如下:

Java类加载的延迟初始化

| Comments

《Java 类的装载、链接和初始化》中提到,链接的最后一步是解析,即对符号引用的解析。但这不是必须的,可以等到相应的符号引用第一次使用时再解析。

而类的初始化是在链接之后的(注意了,根据不同 JVM 有不同的实现方式,在类初始化的时候,可能已经完成了所有的符号引用的解析,也可能没有),本文所写的就是类的初始化的时机问题。

Java 类的动态加载机制规定,在类被主动使用(active use)之前,必须已经完成类的初始化。既然有主动调用,那么就有被动调用了。这两者有哪些区别呢?

下面列出所有主动使用的情况,用以区分两者:

  • 1.创造该类的一个新的实例
  • 2.调用这个类中的静态方法
  • 3.获取类或者接口中的非常量的静态变量
  • 4.利用反射调用方法
  • 5.初始化该类的某子类
  • 6.被制定为 JVM 开始运行时必须初始化的类

注意,3 中为何是“非常量的静态变量”。如果是常量,即声明为 final 的话,并不会出现对类的构造,虽然调用时有类名出现,但实际调用会直接使用常量,绕过了类的限制(详情见相关常量池和运行时常量池的知识)。

只有当一个非常量的静态变量被显示的在类或接口中声明时,它的调用才属于主动调用。对于父类中某非常量静态变量的调用属于被动使用(positive use)。

Java类的装载、链接和初始化

| Comments

加载(Loading)

按如下三步执行

  • 1.通过类的全名产生对应类的二进制数据流。(注意,如果没找到对应类文件,只有在类实际使用时才抛出错误。)
  • 2.分析并将这些二进制数据流转换为方法区(JVM 的架构:方法区、堆,栈,本地方法栈,pc 寄存器)特定的数据结构(这些数据结构是实现有关的,不同 JVM 有不同实现)。这里处理了部分检验,比如类文件的魔数的验证,检查文件是否过长或者过短,确定是否有父类(除了 Obecjt 类)。
  • 3.创建对应类的 java.lang.Class 实例(注意,有了对应的 Class 实例,并不意味着这个类已经完成了加载链链接!)。

链接(Linking)

链接的过程比加载过成复杂不少,这是实现 Java 的动态性的重要一步。分为三部分:验证,准备和解析。

  • 1.验证(verification)

    链接的第三部解析会把类中成员方法、成员变量、类和接口的符号引用替换为直接引用,而在这之前,需要检测被引用的类型正确性和接入属性是否正确(就是 public ,private 的的问题),诸如检查 final class 又没有被继承,检查静态变量的正确性等等。(注意到实际上有一部分验证过程已经在加载的过程中执行了。)

Java类加载器编程实践

| Comments

文本通过实现自定义类加载器,实践 Java 类加载的流程。

阅读此文前,需要了解 Java 类加载的基本原理,参见如下两篇博文:

以上博文中所提及的 Java 类加载机制,都是 Java1.2 及以后的版本,而在最早的 Java1.1 中类加载器是没有父子关系的模式的。这里将分别对 Java1.1 和 Java1.2 及以后的类加载版本进行展示。

Java1.1 中的实现

原理介绍

Java1.1 的类加载机制相对单一,用户自定义加载器的重写比较复杂。

主要需要重写加载器中的 Class loadClass(String name)方法。

Class loadClass(String name)或 loadClass(String name , boolean resolve)方法是加载的核心。它根据类的全名(比如 String 类的全名是 java.lang.String)获得对应类的二进制数据,然后通过 Class defineClass(byte[] b) 将二进制数据加载到 JVM 的方法区,并返回对应类的 Class 实例,然后根据可选的参数 resolve 决定是否需要现在解析这个类。最后将这个 Class 实例作为 loadClass 方法的返回值。

如果无法加载和 defineClass,即无法通过本加载器直接加载类的情况,则使用 Class findSystemClass(String name) 将类加载任务委派给系统类加载器查找。如果能找到则加载,否则抛出 ClassNotFoundException 异常。

编程实例

以下用实例来展示这一过程:

Java 类加载器

| Comments

背景知识

Java 平台无关的特性是由 JVM(Java 虚拟机)支撑的。不同平台有不同的 JVM 支持。

计算机领域有这么一句话:

计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。

JVM 其实也可以看做是运行的 Java 程序和实际的硬件架构之间的一个新抽象层。

一个 Java 程序从编写到执行的流程一句话概括如下:首先将 Java 源代码(*.java 文件)编译成字节码(*.class 文件,字节码之于 Java 源码,类比于汇编代码之于 C 源码),然后由 JVM 运行那些字节码文件。

JVM 工作原理如下图

JVM framework

Java 中所有的类文件都需要由类加载器(Class Loader)装载到 JVM 中。可以简单的将 JVM 理解为一个工厂,类文件就是等待加工的原料,加载器则是装载货物的工人。Java 类被装载之后,才能进入到 JVM 的运行时机制中,开始运行。

类加载器的作用

顾名思义,Java 类加载器的作用是向 JVM 中装载类。

这种动态装载的技术是 Java 的一种创新,让类能够动态加载到 JVM 中执行(更详细的介绍参见 Java programming dynamics, Part 1: Java classes and class loading)。

octopress第三方插件:博文同步工具syncPost

| Comments

为了增加外链等考虑,独立博客往往有将博文同步到其他博客社区的需求。自己人肉黏贴的方式笨拙、重复,对于程序猿而言,着实不可取。

我在 github 上找到了 syncPost 这个针对 octopress 的第三方工具,能够通过一些论坛提供的 metaWeblog 服务实现 octopress 最新一篇博文的同步提交。

这大概就是我要找的东西吧。不过,其中有些细节并不是我想要的:

  • 1.在本地配置文件存储论坛账户的密码。虽然可以设置为 ignore 不提交到 git 代码库中,但这也并不安全。
  • 2.只能同步最新的一篇博客,没有整体的博文搬家功能。
  • 3.先比其他的 octopress 插件,原来版本的代码结构难于维护,比如有自己单独的配置文件,而不是使用全局的_config.yml(在那个版本中大概是为了不把明文密码提交到版本库中),比如 ruby 文件单独在一个_custom文件夹下等

基于这些点,我 fork 了 huangbowen 大哥的代码,定制成了它现在的样子

《俞敏洪口述》

| Comments

质朴、诚恳和坚韧

这本书是某次在 Amazon 上购书『满 100 送书一本』时挑的。放在抽屉里有段日子了。

一直挺崇拜俞敏洪老师。初次了解到他是高中的时候看的『赢在中国』,俞敏洪老师是嘉宾点评。而后混在考 T 的大流中去上了新东方的课程,几个老师讲得不错,比较可惜的是远离了老俞的时代。恰好前两天热映的『中国合伙人』,又把他拉回视野前沿。周日,趁着没心情码代码,忙里偷闲的读完了这本自传体的文字。

我不太喜欢读『活人』的传记。没有盖棺定论的事儿,或多或少掺杂些美华。但读这本书,却丝毫没有表现出浮夸,就像他的性格一般质朴、诚恳和坚韧。

或许在某些名人的传记里,永远不会看到他通过喝酒打通关系搞定了某个问题,而老俞有。书里多次的描述了他是怎样融入了这个『人情社会』。不可否认,他的本分和待人诚恳让他有人缘,这是他的优势。

他能在面对东方学校过河拆桥的举动下,沉住气,以和为贵,用合理的手段占据主动。

他能在北大读本科的阶段受众人嘲讽的环境下成长,不断学习,在毕业时,喊出『你们干十年成的我干二十年,你们二十年成的我干四十年。如果实在不行,我会保持心情愉快、身体健康,到八十岁以后把你们送走了我再走』。

凡此种种……

世界观

从每个成功的人的经历中,都能总结出数不清的箴言。成功的理由永远说不完。而这本书里,对我而言感触最深的无非是下面这些话:

人生就是这样,你不受这个苦就会受那个苦。一个人如果从苦中能找到乐和幸福,那他就是幸运的。……我深刻的意识到什么也不做的痛苦比任何其他痛苦更加深刻,所以我一定要做事,做事的标准就是必须做对社会有好处的事情,以最大的努力在痛苦的世界中尽力而为。

对我而言,这句话实在受用。我无法证明这些字在未来的时间里能对我造成的影响,却打在我最近几年来受困扰最多的点上。

乐观让人笑着面对生活。生活总归是有各种委屈、各种阻力,笑脸的背后,时常是受伤的心,区别在于受伤的心能埋得多深。我宁愿相信,乐观是因为能接受苦,能从苦中找到快乐,而不是没有苦。

时常的,我被不好的状态左右。那是种很奇怪的感觉,是面对压力的逃避、拖延,独自缩在没有人看得见的角落,在电脑屏幕前刷着网页,时间慢慢流走。然后心情越来越差,因为一事无成的颓废感。我是闲不下来的人。是的,『什么也不做的痛苦,比任何其他痛苦更加深刻』!

曾经在 ASES 的分享会上,听 dd 讲起过『积极心理学』。其中有个观点是:『享乐是心灵资本的消费,而心流中的满足则是心灵资本的投资』。简单的讲,创造价值的事情会让我得到心流的满足,而简单的娱乐则会消费这种满足感。或许当两者之差为负的时候,我就感受到了那种空虚和煎熬,不止什么都不做,还消费了更多。

正是对社会责任的追求,老俞没有沉浸再新东方的飞速发展中。对他而言,现在的新东方的经营一切都要权衡公司的利益和教育理念。虽然他的新东方充满了实用主义和人文情怀,虽然他能让新东方尽到企业的社会责任,但这依然不是他想要的。他正在筹划办私立高校,用他的教育理念,给中国的教育发展贡献力量。这是中国的希望。

最后,以老俞的一句话结尾:

人生的苦难肯定是没有尽头的,人要做的是在苦难中奋发起来,做自己能够做的和应该做的事儿,这就是我的世界观。