一

{"type":"编程笔记"}


  • Home

  • Archives
  • Search

java override and overload

Posted on 2016-11-12   |   In java

方法的重写Overriding和重载Overloading是java多态性的不同表现,重写是父类与子类之间的多态性的一种表现,而重载则是主要是在同一个类中表现出多态性。

相关术语说明

  1. 多态:polymorphism,在面向对象的程序设计语言中,多态是继数据封装和继承之后的第三个基本特征。多态是分离做什么和怎么做,从另一个角度将接口和实现分离开来,将改变的事物与未变的事物分离开来。多态也称作动态绑定、后期绑定或者运行时绑定。将一个方法调用和一个方法体连接起来就称为绑定,C语言在编译期就连接了实际运行的方法体,所以就是前期绑定。
  2. 方法重写:Override,在子类中重写覆盖父类中对于子类可见的方法,方法重写也被称为运行时多态。
  3. 方法重载:Overload,重载是在一个类里面,方法名字相同而参数不同,返回类型可以相同也可以不同的方法,可以是静态方法,也可以是实例方法,最常用的地方就是构造器的重载,方法重载也被称为编译时多态。
  4. 方法调用:方法调用不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本,即调用哪个签名的方法,不涉及方法运行的内部细节,在编译期就已经确定。
  5. 方法执行:方法执行是在运行期,根据方法的接收者的实际类型,连接方法到JVM方法区的直接引用,执行引用指向的方法体。
  6. 方法签名:方法签名包括方法名称和参数的数量和类型,返回类型和抛出异常并不认为是一个方法签名的一部分。另外在JVM中还有一个字节码的特征签名,特征签名中还包括了方法返回值和受检异常表,从而在字节码中形成一个真正独一无二的方法ID。
  7. 方法体:运行期执行的实际类型的方法所包括的代码。
  8. 方法接收者:Receiver,即运行期被执行方法的所有者,方法所在的实例对象。
  9. 静态方法:即类方法,而对象实例调用的方法则称为实例方法。
  10. 静态类型:方法签名中参数变量声明的类型称为静态类型(static type)或者是外观类型(apparent type),而参数传入对象的实际类型(actual type)可以是静态类型的子类或者实现类。
  11. 实际类型:方法执行的接收者的实际类型,方法参数传入的实际类型,参数通过方法暴露出来的可能是接口名或者基类名,实际传入的是其子类或者实现类的实例。
  12. 符号引用:一个类文件,它会包含它引用的其他类的全名和描述符,并跟他们建立符号引用,符号引用是一种虚拟的,非物理连接的方式。java在编译期间已经将方法调用的完整符号引用生成出来,作为方法调用指令的参数存储到class文件中。这个符号引用包含了此方法定义在哪个具体类型中,方法的名字和参数顺序,参数类型和方法返回值等信息。反编译class文件可以看到,字节码的方法调用就以class常量池中指向方法的符号引用作为参数,这些符号引用一部分如static方法调用,在类加载解析阶段或者第一次使用时就被转化为直接引用,这部分称为静态解析,而虚方法的符号引用到运行期才会转化为直接引用,这部分称为动态连接。
  13. 直接引用:当程序第一次执行到符号引用的位置时,JVM会检查这个符号链接的正确性,然后建立真正的物理引用,即直接引用,使用直接引用指向一个类、字段或方法的指针或偏移量。虚拟机会记住这个直接引用,这样当它以后再次遇到同样的引用时,就可以直接使用,而不需要重新解析该符号引用了。
  14. 编译期:通过javac这样的编译器将java源文件编译生成class文件。
  15. 运行期:在JVM中把class文件翻译成机器语言后运行,HotSpot中默认是编译器加解释器混合模式协作完成字节码的运行。
  16. 虚方法:除final方法(private方法也是final方法)之外的所有实例方法就是虚方法,也就是public、protected、default的三种实例方法都是虚方法,虚方法对应的虚拟机指令是invokevirtual。
  17. 非虚方法:java中除了static方法和final方法(private方法实际上是final方法)之外,其他所有方法都是虚方法,是后期绑定的,之所以将定义方法为final,为了避免被子类继承外,这样也可以有效的“关闭”动态绑定,告诉编译器不需要动态绑定,在类加载的解析阶段就会连接方法的直接引用。static方法和private方法与之对应的虚拟机指令是invokestatic和invokespecial,final方法的虚拟机指令是invokevirtual。构造方法是invokespecial指令执行的,不是static方法,因为默认传入了新new的对象,并在其中操作实例属性和方法,更多类似于实例方法,但却不是虚方法,即没有后期绑定的。
  18. 类加载:虚拟机通过类加载器对某个类进行加载操作,类加载会经过加载、验证、准备、解析、初始化、使用、卸载这些阶段,非虚方法的符号引用在解析阶段就会被连接到JVM方法区的目标方法的直接引用。

重写 - Override

在子类中重写覆盖父类中对于子类可见的方法,实现子类的特定功能,如需执行父类被覆盖的方法,可以通过super进行引用。

Override规则

  1. 参数列表必须完全与被重写方法的相同;
  2. 返回类型必须完全与被重写方法的返回类型相同;
  3. 访问权限不能比父类中被重写的方法的访问权限更高。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
  4. 父类的成员方法只能被它的子类重写。
  5. 声明为final的方法不能被重写。
  6. 声明为static的方法不能被重写,但是能够被再次声明。
  7. 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
  8. 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
  9. 重写的方法能够抛出任何非受检异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的受检异常,或者比被重写方法声明的更广泛的受检异常,反之则可以。
  10. 重写的方法能够抛出更少或更有限的异常,也就是说,被重写的方法声明了异常,但重写的方法可以什么也不声明。
  11. 构造方法不能被重写。
  12. 如果不能继承一个方法,则不能重写这个方法。

Override示例

public class OverrideExample {    static class OverrideParent {        public void print(Number number) {            System.out.println(number);            System.out.println("Parent#print(Number) method print : " + this.getClass().getSimpleName());        }        // 这个方法不但重载了 print(Number) 方法,而且被子类重写了        // 父类的被重写的方法抛出的异常要宽于子类抛出的异常        public void print(Integer integer) throws IOException {            System.out.println(integer);            System.out.println("Parent#print(Integer) method print : " + this.getClass().getSimpleName());        }    }    static class OverrideChild extends OverrideParent {        @Override        public void print(Integer integer) throws EOFException {            System.out.println(integer);            System.out.println("Child#print(Number) method print : " + this.getClass().getSimpleName());        }    }    public static void main(String[] args) throws IOException {        OverrideParent parent = new OverrideParent();        OverrideParent child = new OverrideChild();        Number number = new Long("1");        Integer integer = new Integer("2");        parent.print(number);        parent.print(integer);        child.print(number);        child.print(integer);    }}

子类重写父类的方法时,可以不抛出异常,或者是更小的异常,比如示例中的IOException的子类EOFException,但不能抛出更广的异常,如Exception。

程序运行后输出:

1

Parent#print(Number) method print : OverrideParent

2

Parent#print(Integer) method print : OverrideParent

1

Parent#print(Number) method print : OverrideChild

2

Child#print(Number) method print : OverrideChild

Override示例运行结果详解

为了更好的说明方法重写,将上述代码生成的class文件反编译,查看class中生成的java字节码:

 javap -c com.example.test.lang.OverrideExample

-c Prints out disassembled code, that is, the instructions that comprise the Java bytecodes, for each of the methods in the class. These are documented in the Java Virtual Machine Specification.

Compiled from "OverrideExample.java"public class com.example.test.lang.OverrideExample {  public com.example.test.lang.OverrideExample();    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."<init>":()V       4: return  public static void main(java.lang.String[]) throws java.io.IOException;    Code:       0: new           #2                  // class com/example/test/lang/OverrideExample$OverrideParent       3: dup       4: invokespecial #3                  // Method com/example/test/lang/OverrideExample$OverrideParent."<init>":()V       7: astore_1       8: new           #4                  // class com/example/test/lang/OverrideExample$OverrideChild      11: dup      12: invokespecial #5                  // Method com/example/test/lang/OverrideExample$OverrideChild."<init>":()V      15: astore_2      16: new           #6                  // class java/lang/Long      19: dup      20: ldc           #7                  // String 1      22: invokespecial #8                  // Method java/lang/Long."<init>":(Ljava/lang/String;)V      25: astore_3      26: new           #9                  // class java/lang/Integer      29: dup      30: ldc           #10                 // String 2      32: invokespecial #11                 // Method java/lang/Integer."<init>":(Ljava/lang/String;)V      35: astore        4      37: aload_1      38: aload_3      39: invokevirtual #12                 // Method com/example/test/lang/OverrideExample$OverrideParent.print:(Ljava/lang/Number;)V      42: aload_1      43: aload         4      45: invokevirtual #13                 // Method com/example/test/lang/OverrideExample$OverrideParent.print:(Ljava/lang/Integer;)V      48: aload_2      49: aload_3      50: invokevirtual #12                 // Method com/example/test/lang/OverrideExample$OverrideParent.print:(Ljava/lang/Number;)V      53: aload_2      54: aload         4      56: invokevirtual #13                 // Method com/example/test/lang/OverrideExample$OverrideParent.print:(Ljava/lang/Integer;)V      59: return}

关于符号引用

javap反编译时加上-verbose参数可以看到更详细的信息,如主次版本,常量池中的类、方法、变量的符号引用,每个方法的栈深和局部变量数量等信息。

常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

  1. 类和接口的全限定名
  2. 字段名称和描述符
  3. 方法名称和描述符
 javap -c -verbose com.example.test.lang.OverrideExample

命令输出如下:

Classfile /Users/david/github.com/learning-programming/java/target/test-classes/com/example/test/lang/OverrideExample.class  Last modified Nov 15, 2016; size 1117 bytes  MD5 checksum bc597130acffb600f30c658477471ad3  Compiled from "OverrideExample.java"public class com.example.test.lang.OverrideExample  minor version: 0  major version: 52  flags: ACC_PUBLIC, ACC_SUPERConstant pool:   #1 = Methodref          #15.#41        // java/lang/Object."<init>":()V   #2 = Class              #42            // com/example/test/lang/OverrideExample$OverrideParent   #3 = Methodref          #2.#41         // com/example/test/lang/OverrideExample$OverrideParent."<init>":()V   #4 = Class              #43            // com/example/test/lang/OverrideExample$OverrideChild   #5 = Methodref          #4.#41         // com/example/test/lang/OverrideExample$OverrideChild."<init>":()V   #6 = Class              #44            // java/lang/Long   #7 = String             #45            // 1   #8 = Methodref          #6.#46         // java/lang/Long."<init>":(Ljava/lang/String;)V   #9 = Class              #47            // java/lang/Integer  #10 = String             #48            // 2  #11 = Methodref          #9.#46         // java/lang/Integer."<init>":(Ljava/lang/String;)V  #12 = Methodref          #2.#49         // com/example/test/lang/OverrideExample$OverrideParent.print:(Ljava/lang/Number;)V  #13 = Methodref          #2.#50         // com/example/test/lang/OverrideExample$OverrideParent.print:(Ljava/lang/Integer;)V  #14 = Class              #51            // com/example/test/lang/OverrideExample  #15 = Class              #52            // java/lang/Object  #16 = Utf8               OverrideChild  #17 = Utf8               InnerClasses  #18 = Utf8               OverrideParent  #19 = Utf8               <init>  #20 = Utf8               ()V  #21 = Utf8               Code  #22 = Utf8               LineNumberTable  #23 = Utf8               LocalVariableTable  #24 = Utf8               this  #25 = Utf8               Lcom/example/test/lang/OverrideExample;  #26 = Utf8               main  #27 = Utf8               ([Ljava/lang/String;)V  #28 = Utf8               args  #29 = Utf8               [Ljava/lang/String;  #30 = Utf8               parent  #31 = Utf8               Lcom/example/test/lang/OverrideExample$OverrideParent;  #32 = Utf8               child  #33 = Utf8               number  #34 = Utf8               Ljava/lang/Number;  #35 = Utf8               integer  #36 = Utf8               Ljava/lang/Integer;  #37 = Utf8               Exceptions  #38 = Class              #53            // java/io/IOException  #39 = Utf8               SourceFile  #40 = Utf8               OverrideExample.java  #41 = NameAndType        #19:#20        // "<init>":()V  #42 = Utf8               com/example/test/lang/OverrideExample$OverrideParent  #43 = Utf8               com/example/test/lang/OverrideExample$OverrideChild  #44 = Utf8               java/lang/Long  #45 = Utf8               1  #46 = NameAndType        #19:#54        // "<init>":(Ljava/lang/String;)V  #47 = Utf8               java/lang/Integer  #48 = Utf8               2  #49 = NameAndType        #55:#56        // print:(Ljava/lang/Number;)V  #50 = NameAndType        #55:#57        // print:(Ljava/lang/Integer;)V  #51 = Utf8               com/example/test/lang/OverrideExample  #52 = Utf8               java/lang/Object  #53 = Utf8               java/io/IOException  #54 = Utf8               (Ljava/lang/String;)V  #55 = Utf8               print  #56 = Utf8               (Ljava/lang/Number;)V  #57 = Utf8               (Ljava/lang/Integer;)V{  public com.example.test.lang.OverrideExample();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."<init>":()V         4: return      LineNumberTable:        line 9: 0      LocalVariableTable:        Start  Length  Slot  Name   Signature            0       5     0  this   Lcom/example/test/lang/OverrideExample;  public static void main(java.lang.String[]) throws java.io.IOException;    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=3, locals=5, args_size=1         0: new           #2                  // class com/example/test/lang/OverrideExample$OverrideParent         3: dup         4: invokespecial #3                  // Method com/example/test/lang/OverrideExample$OverrideParent."<init>":()V         7: astore_1         8: new           #4                  // class com/example/test/lang/OverrideExample$OverrideChild        11: dup        12: invokespecial #5                  // Method com/example/test/lang/OverrideExample$OverrideChild."<init>":()V        15: astore_2        16: new           #6                  // class java/lang/Long        19: dup        20: ldc           #7                  // String 1        22: invokespecial #8                  // Method java/lang/Long."<init>":(Ljava/lang/String;)V        25: astore_3        26: new           #9                  // class java/lang/Integer        29: dup        30: ldc           #10                 // String 2        32: invokespecial #11                 // Method java/lang/Integer."<init>":(Ljava/lang/String;)V        35: astore        4        37: aload_1        38: aload_3        39: invokevirtual #12                 // Method com/example/test/lang/OverrideExample$OverrideParent.print:(Ljava/lang/Number;)V        42: aload_1        43: aload         4        45: invokevirtual #13                 // Method com/example/test/lang/OverrideExample$OverrideParent.print:(Ljava/lang/Integer;)V        48: aload_2        49: aload_3        50: invokevirtual #12                 // Method com/example/test/lang/OverrideExample$OverrideParent.print:(Ljava/lang/Number;)V        53: aload_2        54: aload         4        56: invokevirtual #13                 // Method com/example/test/lang/OverrideExample$OverrideParent.print:(Ljava/lang/Integer;)V        59: return      LineNumberTable:        line 38: 0        line 39: 8        line 41: 16        line 42: 26        line 44: 37        line 45: 42        line 47: 48        line 48: 53        line 49: 59      LocalVariableTable:        Start  Length  Slot  Name   Signature            0      60     0  args   [Ljava/lang/String;            8      52     1 parent   Lcom/example/test/lang/OverrideExample$OverrideParent;           16      44     2 child   Lcom/example/test/lang/OverrideExample$OverrideParent;           26      34     3 number   Ljava/lang/Number;           37      23     4 integer   Ljava/lang/Integer;    Exceptions:      throws java.io.IOException}SourceFile: "OverrideExample.java"InnerClasses:     static #16= #4 of #14; //OverrideChild=class com/example/test/lang/OverrideExample$OverrideChild of class com/example/test/lang/OverrideExample     static #18= #2 of #14; //OverrideParent=class com/example/test/lang/OverrideExample$OverrideParent of class com/example/test/lang/OverrideExample

上面第56行虚拟机指令invokevirtual的参数是一个符号引用#13,而#13在常量池中是由#2.#50组合形成的,再从常量池里找到#2和#50的符号引用,组合之后就看到了调用方法中符号引用的完整信息。

#2  = Class              #42            // com/example/test/lang/OverrideExample$OverrideParent#50 = NameAndType        #55:#57        // print:(Ljava/lang/Integer;)V

由反编译生成的class文件可以看到其中第39、45、50、56行的invokevirtual指令指向的符号引用都是OverrideExample$OverrideParent.print()V方法,尤其是注意第56行,其符号引用还是指向父类的方法。编译期只知道接收者的声明时的静态类型和方法参数的静态类型,并且根据这些静态类型,确定了要执行哪个方法。

静态方法也可以有方法重载,因为静态方法与final的实例方法无须对方法接收者进行多态选择,所以在类加载的解析阶段就会把涉及的符号引用直接转变为实际要执行方法的直接引用。

编译期只是确定了做什么(调用哪个签名的方法),而怎么做却是在运行期决定的(执行哪个实例对象的方法体)。编译期并不知道这个方法要在哪个对象上执行,其执行的方法体是什么,这需要到运行期才会绑定,所以从多态这个概念是运行期绑定的角度来说,只有方法重写才是java多态的表现形式。

java代码运行中大致会经历以下这些步骤:

  1. 在JVM执行之前,需要先将java源码进行编译。
  2. 在编译期根据方法参数的静态类型就已经决定了需要调用哪个签名的重载方法版本。方法调用不等同于方法执行,编译器此时知道参数的静态类型,并以此为依据将对应的这个方法的符号引用写到方法的invokevirtual指令的参数中,但编译器此时是不知道方法是在哪个对象上执行的,这需要到在JVM中的运行期才会绑定,方法调用在class文件中存储的都只是方法的符号引用,而不是方法实际运行的JVM方法区内存中的直接引用。
  3. JVM会加载执行main()方法所在的类,及方法运行中使用new指令引入的相关类。
  4. 类加载会经过加载、验证、准备、解析、初始化、使用、卸载这些阶段,非虚方法的符号引用在解析阶段就会被连接到JVM方法区的目标方法的直接引用。
  5. 编译期确定了方法调用的符号引用,在实例方法运行时,再根据方法的接收者(即当前实例),真正绑定目标方法在内存中的直接引用,这就是java的动态绑定,或者叫运行时绑定,这个过程也就是java方法重写的本质,因此方法重写是在运行期才体现出作用。

上面示例程序中前面2个输出结果属于方法重载,方法接收者是同一个对象,根据重载方法的参数静态类型不同,执行相应方法。

程序第3个输出child.print(number)的结果,因为子类并没有重写父类的对应方法,所以子类运行的就是父类继承的方法。

最后一行代码child.print(integer)输出体现了方法重写的作用,在编译期确定了方法调用的版本,在运行期时确定接收者的实际类型为child对象,然后在child对象中检查是否有此签名的方法,因为child对象中正好重写了父类的此方法,此时连接方法的符号引用到子类重写方法的直接引用上,调用子类重写的方法,这就是java中的运行时绑定,也叫延迟绑定,是java多态的主要表现方式。

Read more »

java threadlocal

Posted on 2016-10-26   |   In java

ThreadLocal是什么

ThreadLocal的官方API解释为:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

从上面ThreadLocal官方的文档说明中可以看到,这个与多线程,以及线程间变量共享没有关系,可以这么理解:

  1. ThreadLocal提供了一种访问某个变量的特殊方式:访问到的变量属于当前线程,即保证每个线程的变量不一样,而同一个线程在任何地方拿到的变量都是一致的,这就是所谓的线程封闭。
  2. 每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中Local所要表达的意思。
  3. 如果要使用ThreadLocal,通常定义为private static类型,也可以定义为private static final类型。
  4. ThreadLocal的作用是提供线程内的全局变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量传递的复杂度。

ThreadLocal的接口方法

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下。

  1. void set(T value):设置当前线程的线程局部变量的值。
  2. public T get():该方法返回当前线程所对应的线程局部变量。
  3. public void remove():将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法,实际上在JDK 1.4源码里就已经有提供,只是被作者注释了。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以帮助GC工作,避免内存溢出,加快内存回收的速度。
  4. protected T initialValue():返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的默认实现直接返回一个null。

ThreadLocal源码分析

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在Thread类中有一个Map,用于在每一个线程中存储ThreadLocal变量副本,Map中元素的键为ThreadLocal对象本身,而值是ThreadLocal对象set的对象。

下图是ThreadLocal相关对象之间的引用关系图,实线表示强引用,虚线表示弱引用。

首先看一下java.lang.Thread类中部分源码,其中每个线程对象中有个ThreadLocal.ThreadLocalMap对象,这是一个包可见的属性。

public class Thread implements Runnable {    // 其他代码省略 ......    /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;    /*     * InheritableThreadLocal values pertaining to this thread. This map is     * maintained by the InheritableThreadLocal class.     */    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;    // 其他代码省略 ......}

接着看一下ThreadLocal.ThreadLocalMap的相关代码,由类的说明可以知道这是一个类似WeakHashMap的数据结构,实际数据是存储在Entry[]数组中,而Entry是继承WeakReference的,当entry.get()为null时,即ThreadLocal对象为null,则说明此引用关联的ThreadLocal对象已经被GC回收,对应的entry也可以从Entry[]数组中移除,并将对应的value置为null,帮助GC回收对象空间,线程运行结束时,线程中ThreadLocalMap变量的所有Entry[]会在下一次GC时被回收。

/** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread.  To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. */static class ThreadLocalMap {    /**     * The entries in this hash map extend WeakReference, using     * its main ref field as the key (which is always a     * ThreadLocal object).  Note that null keys (i.e. entry.get()     * == null) mean that the key is no longer referenced, so the     * entry can be expunged from table.  Such entries are referred to     * as "stale entries" in the code that follows.     */    static class Entry extends WeakReference<ThreadLocal<?>> {        /** The value associated with this ThreadLocal. */        Object value;        Entry(ThreadLocal<?> k, Object v) {            // 此处调用 new WeakReference(threadLocal) 构造方法            // 如果 ThreadLocal 在外部没有强引用时,entry.get() 会返回 null            // 与 WeakHashMap 实现相似            super(k);            value = v;        }    }    /**     * The initial capacity -- MUST be a power of two.     */    private static final int INITIAL_CAPACITY = 16;    /**     * The table, resized as necessary.     * table.length MUST always be a power of two.     */    private Entry[] table; // 当前线程的 ThreadLocal 对象及其 value 实际存放的数组    /**     * Set the value associated with key.     *     * @param key the thread local object     * @param value the value to be set     */    private void set(ThreadLocal<?> key, Object value) {        // We don't use a fast path as with get() because it is at        // least as common to use set() to create new entries as        // it is to replace existing ones, in which case, a fast        // path would fail more often than not.        Entry[] tab = table;        int len = tab.length;        int i = key.threadLocalHashCode & (len-1); // 计算当前对象在数组中的索引位置        for (Entry e = tab[i];             e != null;             e = tab[i = nextIndex(i, len)]) {            ThreadLocal<?> k = e.get(); // entry 继承 WeakReference,其 get() 方法返回弱引用关联的对象            if (k == key) { // 对象直接用 == 比较其内存地址,判断是否同一个对象                e.value = value;                return;            }            if (k == null) {                // 遍历当前线程的threadLocals时,如发现有key已经被GC回收了,就在清理 table                // 并将 value 重置为 null,帮助GC回收对象,避免内存溢出                replaceStaleEntry(key, value, i);                return;            }        }        tab[i] = new Entry(key, value);        int sz = ++size;        if (!cleanSomeSlots(i, sz) && sz >= threshold)            rehash();    }    // 其他代码省略 ......}

下面看一下ThreadLocal关键的get()和set(T)方法。

  1. get()方法比较简单,根据算法得到当前ThreadLocal对象在数组中的索引,再到Entry[]数组获取相应的Entry,并返回其value。
  2. set(T)方法中,每个ThreadLocal对象将自身作为ThreadLocalMap的key,和传入的参数value一起被保存到ThreadLocalMap中。
  3. 另外还有个关键方法getMap(Thread t),就是通过这个方法将ThreadLocal与当前线程关联起来的。
Read more »

request.getsession()方法说明

Posted on 2016-10-25   |   In java

HttpServletRequest接口中getSession()方法有3种用法,说明如下:

  1. request.getSession()就是request.getSession(true)。
  2. request.getSession(false)与上二者的区别在于,如果当前Context中没有Session时(如用户第一次访问网站时),并不创建新Session对象,直接返回null。

以下根据tomcat-8.0.24中的源码进行简单说明。

getSession()与getSession(true)

从这个org.apache.catalina.connector.RequestFacade类中可以看到getSession()方法其实就是调用getSession(true)。

public HttpSession getSession(boolean create) {    if (request == null) {        throw new IllegalStateException(                        sm.getString("requestFacade.nullRequest"));    }    if (SecurityUtil.isPackageProtectionEnabled()){        return AccessController.            doPrivileged(new GetSessionPrivilegedAction(create));    } else {        return request.getSession(create);    }}@Overridepublic HttpSession getSession() {    if (request == null) {        throw new IllegalStateException(                        sm.getString("requestFacade.nullRequest"));    }    return getSession(true);}
Read more »

java weak references

Posted on 2016-10-18   |   In java

java四种引用类型

  1. 强引用(StrongReference):就是在代码中普遍存在的,类似Object object = new Object()这类的引用,只要强引用还存在,GC永远不会回收掉被引用的对象。
  2. 软引用(SoftReference):用来描述一些还有用但非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常时,将会把这些对象列入回收范围之中进行回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  3. 弱引用(WeakReference):也是用来描述非必须对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到了下一次GC发生之前。当GC工作时,无论当时内存是否足够,都会回收只被弱引用关联的对象,GC不会调用弱引用的clear()方法,直接回收弱引用对象的内存。
  4. 虚引用(PhantomReference):虚引用也称幽灵引用或者影子引用,它是最弱的一种引用关系,也无法通过虚引用来取得一个对象实例,为一个对象设置虚引用的唯一目的就是在这个对象被GC回收时,收到一个系统通知,在完成虚可及对象相关的资源清理之后,最后调用虚引用的clear()方法,完全对虚可及对象的致命一击,将虚引用目标对象由虚可及状态导向它的生命周期的终点,即不可及状态。

对象在内存中的状态

java对象在堆内存有向图中的状态分成3种:

  1. 可及状态:当一个对象被创建后,有一个以上的引用指向它,在有向图中可以从起始顶点导航到该对象,那么它就处于可及状态。
  2. 可恢复状态:对于一个处于可及状态的对象,如果不再有任何引用指向它,在有向图中从起始顶点不能导航到该对象,那么它进入可恢复状态。在这个状态下,JVM的垃圾回收器准备回收该对象占用的内存。但在回收该对象之前,JVM会调用该对象的finalize()进行资源清理。如果在调用finalize()期间重新让一个以上引用指向该对象,则该对象会再次进入可及状态;否则,该对象将进入不可及状态。
  3. 不可及状态:当一个对象进入不可及状态后,JVM的垃圾回收器才会真正回收该对象所占用的内存。

针对上面四种引用方式,对象还有以下这几个状态:

  1. 软可及状态:对象不是强可及的,但是可以从根节点开始通过一个或者多个未被清除的软引用对象触及。当垃圾收集器清除一个和引用队列有关联的软引用对象时,它把该软引用对象加入队列。
  2. 弱可及状态:对象即不是强可及的也是不是软可及的,但是从根节点开始可以通过一个或多个未被清除的弱引用对象时触及。垃圾收集器必须归还弱可及对象所占据的内存。在发生GC时,GC会清除所有到此弱可及对象的弱引用。当GC清除一个和引用队列有关联的弱引用对象时,它把该引用对象加入队列。
  3. 虚可及状态:对象不是强可及、软可及,也不是弱可及,并已经被断定不会被任何finalize()方法复活,如果它自己定义了终结方法,那它的finalize()方法之前也已经运行过一次了,并且它可以从根节点开始通过一个或者多个虚引用对象触及。一个虚引用的引用目标变为虚可及状态,GC会立即将此引用对象加入引用队列。GC从不会清除一个虚可及对象,即不会执行虚引用的Reference#clear()方法,所有的虚可及都必须由程序明确的调用clear()方法清除。

Reference#clear() 方法源码

public void clear() {    this.referent = null;}

四种引用类型比较

引用类型 取得目标对象方式 垃圾回收条件 是否可能内存泄漏
强引用 直接调用 不回收 可能
软引用 通过 get() 方法 视内存情况回收 不可能
弱引用 通过 get() 方法 永远回收 不可能
虚引用 无法取得 不回收 可能
Read more »

java jvm and gc

Posted on 2016-10-13   |   In java

jvisualvm截图

Heap and Non-Heap Memory

The JVM memory consists of the following segments:

  1. Heap Memory, which is the storage for Java objects
  2. Non-Heap Memory, which is used by Java to store loaded classes and other meta-data
  3. JVM code itself, JVM internal structures, loaded profiler agent code and data, etc.

Heap

The JVM has a heap that is the runtime data area from which memory for all class instances and arrays are allocated. It is created at the JVM start-up.

Used heap memory consists of live and dead objects.

  1. Live objects are accessible by the application and will not be a subject of garbage collection.
  2. Dead objects are those which will never be accessible by the application but have not been collected yet by the garbage collector. Such objects occupy the heap memory space until they are eventually collected by the garbage collector.

The heap size may be configured with the following VM options:

  1. -Xmx<size> - to set the maximum Java heap size
  2. -Xms<size> - to set the initial Java heap size

By default, the maximum heap size is 64 Mb.

Heap memory for objects is reclaimed by an automatic memory management system which is known as a garbage collector. The heap may be of a fixed size or may be expanded and shrunk, depending on the garbage collector's strategy.

Non-Heap

Also, the JVM has memory other than the heap, referred to as non-heap memory. It is created at the JVM startup and stores per-class structures such as runtime constant pool, field and method data, and the code for methods and constructors, as well as interned Strings.

Unfortunately, the only information JVM provides on non-heap memory is its overall size. No detailed information on non-heap memory content is available.

The abnormal growth of non-heap memory size may indicate a potential problem, in this case you may check up the following:

  1. If there are class loading issues such as leaked loaders. In this case, the problem may be solved with the help of Class loaders view.
  2. If there are strings being massively interned. For detection of such problem, Object allocation recording may be used.

If the application indeed needs that much of non-heap memory and the default maximum size of 64 Mb is not enough, you may enlarge the maximum size with the help of -XX:MaxPermSize VM option. For example, -XX:MaxPermSize=128m sets the size of 128 Mb.

Heap组成部分说明

  1. Heap区分两大块,一块是young generation,另一块是old generation。上图中的permanent generation不属于Heap。
  2. 在young generation中,有一个叫eden的空间,主要是用来存放新生的对象,还有两个survivor spaces(from, to),即部分示例图中的s0和s1,它们的大小总是一样,它们用来存放每次垃圾回收后存活下来的对象。
  3. 在old generation中,主要存放应用程序中生命周期长的内存对象。
  4. 在young generation块中,垃圾回收一般用copying的算法,速度快。每次gc的时候,存活下来的对象首先由eden拷贝到某个survivor space,当survivor space空间满了后,剩下的live对象就被直接拷贝到old generation中去。因此,每次gc后,eden内存块会被清空。
  5. 在old generation块中,垃圾回收一般用mark-compact的算法,速度慢些,但减少内存要求。
  6. 垃圾回收分多级,0级为全部(full)的垃圾回收,会回收old段中的垃圾,1级或以上为部分垃圾回收,只会回收new中的垃圾,内存溢出通常发生于old段或perm段垃圾回收后,仍然无内存空间容纳新的java对象的情况。

JVM内存申请过程

  1. jvm会试图为相关java对象在eden中初始化一块内存区域。
  2. 当eden空间足够时,内存申请结束;否则到下一步。
  3. JVM试图释放在eden中所有不活跃的对象(这属于1或更高级的垃圾回收),释放后若eden空间仍然不足以放入新对象,则试图将部分eden中活跃对象放入survivor区。
  4. survivor区被用来作为eden及old的中间交换区域,当old区空间足够时,survivor区的对象会被移到old区,否则会被保留在survivor区。
  5. 当old区空间不够时,jvm 会在old区进行完全的垃圾收集(0级)。
  6. 完全垃圾收集后,若survivor及old区仍然无法存放从eden复制过来的部分对象,导致jvm无法在eden区为新对象创建内存区域,则出现out of memory错误。
Read more »
1…151617…99
yuweijun

yuweijun

492 posts
12 categories
RSS
GitHub Twitter
© 2021 yuweijun
Powered by Hexo
Theme - NexT.Mist.KISS