该如何创建字符串,使用“ ”还是构造函数

实例一
String a = "abcd";
String b = "abcd";
System.out.println("a == b : "+(a == b)); // true
System.out.println("a.equals(b) : "+(a.equals(b))); // true

当相同的字符串常量被多次创建时,只会保存字符串常亮的一份副本,这称为“字符串驻留”。在Java中,所有编译时字符串常亮都是驻留的。

实例二
String c = new String("abcd");
String d = new String("abcd");
System.out.println("c == d : "+(c == d)); // false
System.out.println("c.equals(d) : "+(c.equals(d))); // true

运行时字符串驻留

String c = new String("abcd").intern();
String d = new String("abcd").intern();
System.out.println("c == d : "+(c == d)); // true
System.out.println("c.equals(d) : "+(c.equals(d))); // true    (JDK1.7)
2016/3/7 posted in  Java

面向GC的Java编程

GC分代的基本假设

GC分代的基本假设是:

绝大部分对象的生命周期都非常短暂,存活时间短。

基于这个前提,在编码过程中,我们应该尽可能地缩短对象的生命周期。

结论:

  • 分配小对象的开销非常小,不要吝啬去创建
  • GC最喜欢这种小而短命的对象
  • 让对象的生命周期尽可能短,例如在方法体内创建,使其能尽快地在YoungGC中被回收,不会晋升到年老代(Old Generation

对象分配的优化

基于大部分对象都是小而短命,并且不存在多线程的数据竞争。这些小对象的分配,会优先在线程私有的TLAB中分配,TLAB中创建的对象,不存在锁甚至是CAS的开销。

TLAB占用的空间在Eden Generation

当对象比较大,TLAB的空间不足以放下,而JVM又认为当前线程占用的TLAB剩余空间还足够时,就会直接在Eden Generation上分配,此时是存在并发竞争的,所以会有CAS的开销,但是也还好。

当对象大到Eden Generation放不下时,JVM只能尝试去Old Generation分配,这种情况需要尽可能避免,因为一旦在Old Generation上分配,这个对象只能被Old GenerationGC或者是Full GC回收了。

不可变对象的好处

GC算法在扫描存活对象时通常需要从ROOT节点开始,扫描所有存活对象的引用,构建出对象图。

不可变对象对GC的优化,主要体现在Old Generation中。

如果存在Old Generation的对象引用了Young Generation的对象,那么在每次YoungGC的过程中,就必须考虑到这种情况。

卡表(Card Table)

Old Generation中的对象发生对Young Generation中的对象产生新的引用关系或释放引用时,都会在卡表中相应的标记上标为脏(dirty);而当YoungGC时,只需要扫描这些dirty的项就可以了。

可变对象对其他对象的引用关系可能会频繁变化,并且有可能在运行过程中持有越来越多的引用,特别是容器。这些都会导致对应的卡表项被频繁标记为dirty

而不可变对象的引用关系非常稳定,在扫描卡表时就不会扫到它们对应的项了。

这里的不可变对象,不是指仅仅自身引用不可变的final对象,而是真正的Immutable Objects

指定容器初始化大小

如果采用默认无参构造函数,构建一个ArrayList,不断增加元素知道OOM,那么在此过程中会导致:

  • 多次数组扩容,重新分配更大空间的数组
  • 多次数组拷贝
  • 内存碎片

对象池

为了减少对象分配开销,提高性能,可能有人会采用对象池的方式来缓存对象集合,作为复用的手段。

但是对象池中的对象由于在运行期长期存活,大部分会晋升到Old Generation,因此无法通过YoungGC回收。

可以借助成熟的开源框架,例如Apache Commons Pool

对象作用域

尽可能缩小对象的作用域,即生命周期

  • 如果可以在方法内声明的局部变量,就不要声明为实例变量
  • 除非你的对象是单例的或者不变的,否则尽可能少地声明static变量

Reference

http://coolshell.cn/articles/11541.html

2016/3/4 posted in  Java

浅析`Java`虚拟机结构与机制

JVM的主要结构

类加载器子系统(Class Loader

类加载器子系统负责加载编译好的.class字节码文件,并装入内存,使JVM可以实例化或以其它方式使用加载后的类

JVM的类加载子系统支持在运行时的动态加载。

动态加载的优点有很多,例如可以节省内存空间、灵活地从网络上加载类;另一好处是可以通过命名空间的分割来实现类的隔离,增强了整个系统的安全性。

ClassLoader的分类:

  • 启动类加载器(Bootstrap class loader):负责加载rt.jar文件中所有的Java类,即Java的核心类都是由该ClassLoader加载。
  • 扩展类加载器(Extension class loader):负责加载一些扩展功能的jar包。
  • 系统类加载器(System class loader):负责加载启动参数中指定的classpath中的jar包及目录,通常我们自己写的Java类也是由该ClassLoader加载。
  • 用户自定义类加载器(User defined class loader):由用户自定义类的加载规则,可以手动控制加载过程中的步骤。

ClassLoader的工作原理

类加载分为装载、链接、初始化三步。

加载

通过类的全限定名和ClassLoader加载类,主要是将指定的.class文件加载至JVM当类被加载以后,在JVM内部就以“类的全限定名+ClassLoader实例ID”来标明类。

在内存中,ClassLoader实例和类的实例都位于堆中,它们的类信息都位于方法区。

装载过程采用一种被称为“双亲委派模型(Parent Delegation Model)”的方式,当一个ClassLoader要加载类时,它会先请求它的双亲ClassLoader加载类,而它的双亲ClassLoader会继续把加载请求提交再上一级的ClassLoader,直到启动类加载器。只有其双亲ClassLoader无法加载指定的类时,它才会自己加载类。

双亲委派模型是JVM的第一道安全防线,它保证了类的安全加载,这里同时依赖了类加载器隔离的原理:不同类加载器加载的类之间是无法直接交互的,即使是同一个类,被不同的ClassLoader加载,它们也无法感知到彼此的存在。这样即使有恶意的类冒充自己在核心包(java.lang)下,由于它无法被启动类加载器加载,也造成不了危害

链接

链接的任务是把二进制的类型信息合并到JVM运行时状态中去。

链接分为以下三步:

  • 验证:校验.class文件的正确性,确保该文件是符合规范定义的,并且适合当前JVM使用。
  • 准备:为类分配内存,同时初始化类中的静态变量赋值为默认值。
  • 解析(可选):主要是把类的常量池中的符号引用解析为直接引用,这一步可以在用到相应的引用时再解析。
初始化

初始化类中的静态变量,并执行类中的static代码、构造函数。

JVM规范严格定义了何时需要对类进行初始化:

  • 通过new关键字、反射、clone、反序列化机制实例化对象时
  • 调用类的静态方法时
  • 调用类的静态字段或对其赋值时
  • 通过反射调用类的方法时
  • 初始化该类的子类时(初始化子类前其父类必须已经被初始化)
  • JVM启动时被标记为启动类的类

JAVA栈(Java Stack

Java栈由栈帧组成,一个帧对应一个方法调用。调用方法时压入栈帧,方法返回时弹出栈帧并抛弃。Java栈的主要任务是存储方法参数、局部变量、中间运算结果,并且提供部分其它模块工作需要的数据。

Java栈是线程私有的,这就保证了线程安全性,使得程序员无需考虑栈同步访问的问题,只有线程本身可以访问它自己的局部变量区。

它分为三部分:局部变量区、操作数栈、桢数据区。

局部变量区

局部变量区是以字长为单位的数组,在这里,byteshortchar类型会被转换成int类型存储,除了longdouble类型占两个字长以外,其余类型都只占用一个字长。

boolean类型在编译时会被转换成intbyte类型,boolean数据会被当做byte类型数据来处理。局部变量区也会包含对象的引用,包括类引用、接口引用以及数组引用。

局部变量区包含了方法参数和局部变量,此外,实例方法隐含第一个局部变量this,它指向调用该方法的对象引用。对于对象,局部变量区中永远只有指向堆的引用。

操作数栈

操作数栈也是以字长为单位的数组,但是正如其名,它只能进行入栈出栈的基本操作。在进行计算时,操作数被弹出栈,计算完毕后再入栈。

帧数据区

帧数据区的任务主要有:

  • 记录指向类的常量池的指针,以便于解析
  • 帮助方法的正常返回,包括恢复调用该方法的栈帧,设置PC寄存器指向调用方法对应的下一条指令,把返回值压入调用栈帧的操作数栈中
  • 记录异常表,发生异常时将控制权交由对应异常的catch字句,如果没有找到对应的catch字句,会恢复调用方法的栈帧并重新抛出异常。

局部变量区和操作数栈的大小依照具体方法在编译时就已经确定。调用方法时会从方法区中找到对应类的类型信息,从中得到具体方法的局部变量区和操作数栈的大小,依次分配栈帧内存,压入Java

本地方法栈(Native Method Stack)

本地方法栈类似于Java栈,主要存储了本地方法调用的状态。

方法区(Methods Area)

类型信息和类的静态变量都存储在方法区中。

方法区中对于每个类存储了以下数据:

  • 类及其父类的全限定名
  • 类的类型(class还是interface)
  • 访问修饰符(public,abstract,final)
  • 实现的接口的全限定名的列表
  • 常量池
  • 字段信息
  • 方法信息
  • 静态常量
  • ClassLoader引用
  • Class引用

可见类的所有信息都存储在方法区中。由于方法区是所有线程共享的,所以必须保证线程安全。举例来说,如果两个类同时要加载一个尚未被加载的类,那么一个类会请求它的ClassLoader去加载需要的类,另一个类只能等待而不会重复加载。

此外,为了加快调用方法的速度,通常还会为每个非抽象类创建私有的方法表,方法表是一个数组,存放了实例可能被调用的实例方法的直接引用。

堆(Heap)

堆用于存储对象实例以及数组值。

堆中有指向类数据的指针,该指针指向了方法区中对应的类型信息。

堆中还可能存放了指向方法表的指针。

堆是所有线程共享的,所以在进行实例化对象等操作时,需要解决同步问题。

新生代(New Generation)

大多数情况下,新对象都被分配在新生代中,新生代由Eden Space和两块相同大小的Survivor Space组成,后两者主要用于Minor GC时对象复制。

JVM在Eden Space中会开辟一小块独立的TLAB(Thread Local Allocation Buffer)区域用于更高效的内存分配。

在堆上分配内存需要锁定整个堆,而在TLAB上则不需要,JVM在分配对象时会尽量在TLAB上分配,以提高效率。

旧生代(Old Generation/Tenuring Generation)

在新生代中存活时间较久的对象将会被转入旧生代,旧生代进行垃圾收集的频率没有新生代高。

执行引擎

执行引擎是JVM执行Java字节码的核心,执行方式主要分为解释执行、编译执行、自适应优化执行、硬件芯片执行方式。

JVM的指令集是基于栈而非寄存器的,这样做的好处在于可以使指令尽可能紧凑,便于快速地在网络上传输,同时也很容易适应通用寄存器较少的平台,并且有利于代码优化,由于Java栈PC寄存器是线程私有的,线程之间无法互相干涉彼此的栈。每个线程拥有独立的JVM执行引擎实例。

JVM指令由单字节操作码和若干操作数组成。对于需要操作数的指令,通常是先把操作数压入操作数栈,即使是对局部变量赋值,也会先入栈再赋值。

解释执行

解释执行中有几种优化方式:

  • 栈顶缓存

将位于操作数栈顶的值直接缓存在寄存器上,对于大部分只需要一个操作数的指令而言,就无需再入栈,可以直接在寄存器上进行计算,结果压入操作数栈。这样便减少了寄存器和内存的交换开销。

  • 部分栈帧共享

被调用方法可将调用方法栈帧中的操作数栈作为自己的局部变量区,这样在获取方法参数时减少了复制参数的开销。

  • 执行机器指令

在一些特殊情况下,JVM会执行机器指令以提高速度。

编译执行

为了提升执行速度,SUN JDK提供了将字节码编译为机器指令的支持,主要利用了JIT编译器在运行时进行编译,它会在第一次执行时编译字节码为机器码并缓存,之后就可以重复利用了。

自适应优化执行

自适应优化执行的思想是程序中10%-20%的代码占据了80%-90%的执行时间,所以通过将那少部分代码编译为优化过的机器码就可以大大提升执行效率。

2016/3/4 posted in  Java

Java static初始化顺序

初始化顺序

class中,总是先初始化字段,字段定义的先后顺序决定了初始化的顺序,然后再初始化构造器

class Window {
  Window(int marker) { print("Window(" + marker + ")"); }
}

class House {
  Window w1 = new Window(1); // Before constructor
  House() {
    // Show that we're in the constructor:
    print("House()");
    w3 = new Window(33); // Reinitialize w3
  }
  Window w2 = new Window(2); // After constructor
  void f() { print("f()"); }
  Window w3 = new Window(3); // At end
}

public class OrderOfInitialization {
  public static void main(String[] args) {
    House h = new House();
    h.f(); // Shows that construction is done
  }
} 
/* Output:
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()

static数据的初始化

加上static字段后,这个字段的拥有者不再是对象而是类。无论创建多少个对象,static数据都只有一份。

在class中总是先初始化static字段,再初始化一般字段,接着初始化构造器。

如果不创建这个类的对象,那么这个对象不会初始化;初始化也之后执行一次。

2016/3/3 posted in  Java

Java习惯用法总结

实现equals()

class Person {
  String name;
  int birthYear;
  byte[] raw;
 
  public boolean equals(Object obj) {
    if (!obj instanceof Person)
      return false;
 
    Person other = (Person)obj;
    return name.equals(other.name)
        && birthYear == other.birthYear
        && Arrays.equals(raw, other.raw);
  }
 
  public int hashCode() { ... }
}
  • foo.equals(null)必须返回falsenull instanceof 任意类总是返回false
  • 覆盖equals()时,记得要相应地覆盖hashCode()

实现hashCode()

class Person {
  String a;
  Object b;
  byte c;
  int[] d;
 
  public int hashCode() {
    return a.hashCode() + b.hashCode() + c + Arrays.hashCode(d);
  }
 
  public boolean equals(Object o) { ... }
}
  • 使用Arrays.hashCode(一个数组)

实现compareTo()

class Person implements Comparable<Person> {
  String firstName;
  String lastName;
  int birthdate;
 
  // Compare by firstName, break ties by lastName, finally break ties by birthdate
  public int compareTo(Person other) {
    if (firstName.compareTo(other.firstName) != 0)
      return firstName.compareTo(other.firstName);
    else if (lastName.compareTo(other.lastName) != 0)
      return lastName.compareTo(other.lastName);
    else if (birthdate < other.birthdate)
      return -1;
    else if (birthdate > other.birthdate)
      return 1;
    else
      return 0;
  }
}
  • 总是实现泛型版本Comparable而不是实现原始类型Comparable

实现clone()

class Values implements Cloneable {
  String abc;
  double foo;
  int[] bars;
  Date hired;
 
  public Values clone() {
    try {
      Values result = (Values)super.clone();
      result.bars = result.bars.clone();
      result.hired = result.hired.clone();
      return result;
    } catch (CloneNotSupportedException e) {  // Impossible
      throw new AssertionError(e);
    }
  }
}
  • 使用super.clone()Object类负责创建新的对象
  • 基本类型域都可以正确地复制了。同样,我们不需要去克隆StringBigInteger等不可变类型

反转字符串

String reverse(String s) {
  return new StringBuilder(s).reverse().toString();
}
  • 使用reverse()方法
2016/3/2 posted in  Java