代码之旅

I love Coding !

Java引用

在JDK1.2之前,Java中的引用的定义很传统: 如果reference类型(栈中的引用类型)的数据中存储的数值代表另一块内存的起始地址,就称这块内存代表一个引用。在这种情况下,对象只有被引用和没有被引用两种状态。

我们希望有这样一类对象,当内存空间还足够时,保存在内存中;而如果内存空间在垃圾回收后还是紧张,则可以抛弃这些对象。

JDK1.2后,java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phanton Reference)这四种,引用强度依次减弱。主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。

阅读全文 »

使用 JOL 分析 Java 对象内存

前面介绍了Java对象在JVM中的内存布局。那么有什么方法可以方便的计算Java对象的内存占用?

本篇文章我们将介绍如何使用JOL分析Java对象内存。JOL是一个用来分析JVM中Object布局的小工具。包括Object在内存中的占用情况,实例对象的引用情况等等。JOL可以在代码中使用,也可以独立的以命令行中运行。

非特殊说明,本文中使用java默认为jdk8。

阅读全文 »

Java虚拟机-对象内存布局

Java对象的内存布局:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)。

1
2
3
4
5
6
7
+------------------+
| Header |
+------------------+
| Instance Data |
+------------------+
| Padding |
+------------------+

对象大小分为:

  • 自身的大小(Shadow heap size)
  • 所直接或间接引用的对象的大小(Retained heap size)。

本文讨论的是Java 对象的 Shadow heap size。Retained heap size可以通过遍历引用得到。本文Java 默认使用Oracle 1.8(即Hotspot虚拟机)。

阅读全文 »

Java虚拟机-Instrumentation

java Instrumentation(插桩)指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等。

从Java SE5开始使用JVM TI替代了JVM PI和JVM DI。提供一套代理机制,支持独立于JVM应用程序之外的程序以代理的方式连接和访问JVM。java.lang.instrument是在JVM TI的基础上提供的Java版本的实现。

Instrument 设计的目的是将字节码添加到方法中,收集工具使用的数据。出于这个目的,更改纯粹是附加的,因此这些工具不会修改应用程序状态或行为。此类良性工具的示例包括监控代理、分析器、覆盖分析器和事件记录器。

当然,由于这种检测机制提供了修改现有已编译 Java 类的字节码或添加字节码的能力,所以我们也可以用来动态修改运行的程序代码。

阅读全文 »

Java 对象初始化顺序

Java 对象启动顺序如下:

  1. java 对象初始化时,会先加载对应的类,随后加载其基类(如果存在基类);
  2. 先从基类执行static 初始化,后执行子类static 初始化。static初始化顺序:静态变量和静态初始化块的执行顺序取决于它们的代码位置顺序。
  3. 此时,类加载完毕,开始对象的初始化。先执行基类初始化,后执行衍生类初始化。
    • 对象初始化的顺序:
      • 属性得到初值:基本值类型为默认值;对象句柄为null;
        • 定义为final非静态基本数据类型的成员变量此时也会被初始化, 注意Integer等包装类不行;
        • 有且只有定义为final非静态的String成员变量,采用的“=”赋值初始化会被执行(非new)。
      • 属性定义时的初始化(例如int i =1)和代码块,的执行顺序取决于它们的代码位置顺序。
      • 构造函数;
阅读全文 »

Java泛型

泛型在定义类,接口和方法时使类型(类和接口)成为参数。与方法声明中使用的更熟悉的形式参数非常相似,类型参数为您提供了一种使用不同输入重复使用相同代码的方法。区别在于形式参数的输入是值,而类型参数的输入是类型。使用泛型带来了下面几点优势:

  • 消除代码中的直接转型。

    1
    2
    3
    List<String> list = new ArrayList<String>();
    list.add("hello");
    String s = list.get(0); // no cast
  • Java编译器对泛型代码进行强类型检查,如果代码违反类型安全,则会发出错误。修复编译时错误比修复运行时错误容易,后者可能很难找到。

  • 通过使用泛型,可以实现对不同类型的集合工作的泛型算法,可以对其进行自定义,类型安全且易于阅读。

阅读全文 »

Java 序列化

"持久化"意味着对象的“生存时间”并不取决于程序是否正在执行——它存活于程序的每一次调用之间。通过序列化一个对象,将其写入磁盘,以后在程序再次调用时,通过反序列化,重新恢复那个对象,就能圆满实现一种“持久”效果。

什么是序列化和反序列化?(注意这里仅指 java 自身提供的序列化功能)

  • 把对象转换为字节序列的过程称为对象的序列化。
  • 把字节序列恢复为对象的过程称为对象的反序列化。
阅读全文 »

Java克隆

什么是clone()?

Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:

  1. 拷贝对象返回的是一个新对象,而不是一个引用。
  2. 拷贝对象与用 new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。

clone()的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CloneClass implements Cloneable {
public int aInt;

public Object clone() {
CloneClass o = null;
try {
// 调用父类的super.clone方法(父类必须实现clone方法)
//这个方法将最终调用Object的中native型的clone方法完成浅拷贝
o = (CloneClass) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}

实现Cloneable接口,Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对 Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了 super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。

阅读全文 »

字节

字节(英语:Byte),通常用作计算机信息计量单位,不分数据类型,是通信和数据存储的概念。一个字节代表八个比特。比特(英语:Bit,亦称二进制位)指二进制中的一位,是信息的最小单位。假设一事件以A或B的方式发生,且A、B发生的概率相等,都为0.5,则一个二进位可用来代表A或B之一。二进位可以用电路的高低电位来表示。

字节顺序,又称端序或尾序(英语:Endianness),在计算机科学领域中,指电脑内存中或在数字通信链路中,组成多字节的字的字节的排列顺序。

在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如在C语言中,一个类型为int的变量x地址为0x100,那么其对应地址表达式&x的值为0x100。且x的四个字节将被存储在电脑内存的0x100, 0x101, 0x102, 0x103位置。

字节的排列方式有两个通用规则。例如,将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序;反之则称大端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。

例如假设上述变量x类型为int,位于地址0x100处,它的值为0x01234567,地址范围为0x100~0x103字节,其内部排列顺序依赖于机器的类型。大端法从首位开始将是:0x100: 0x01,0x101: 0x23,…。而小端法将是:0x100: 0x67, 0x101: 0x45,…。

阅读全文 »

Java多态-继承绑定

属性与静态方法

Java中属性与静态方法和类绑定(静态绑定)。静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了同名静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成。

方法绑定

程序绑定的概念:

绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对java来说,绑定分为静态绑定和动态绑定;或者叫做前期绑定和后期绑定。

静态绑定(早绑定 编译器绑定):

在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。针对java可以理解为程序编译期的绑定;特别说明一点,java当中的方法只有final,static,private和构造方法是前期绑定

动态绑定(迟绑定 运行期绑定):

后期绑定:在运行时根据具体对象的类型进行绑定。若一种语言实现了后期绑定,同时必须提供一些机制在运行期间判断对象的类型,并分别调用适当的方法。也就是说编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。可以这样认为:它们都要在对象中安插某些特殊类型的信息。

动态绑定的过程:

  • 虚拟机提取对象的实际类型的方法表
  • 虚拟机搜索方法签名
  • 调用方法

总结

了解三者的概念之后,我们发现java属于后期绑定。在java中,几乎所有的方法都是后期绑定,在运行时动态绑定方法属于子类还是基类。但也有特殊,针对static方法和final方法由于不能被继承,因此在编译时就可以确定他们的值,他们是属于前期绑定。特别说明的一点,private声明的方法和成员变量不能被子类继承,所有的private方法都被隐式的指定为final的(由此我们知道:将方法声明为final类型,一是为了防止方法被覆盖,二是为了有效的关闭java中的动态绑定)。java中的后期绑定是由JVM来实现的,我们不用去显式的声明它。

阅读全文 »