- 浏览: 3052634 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
原帖:不用构造方法也能创建对象
把之前我引用过的一段也贴上来:
在Java语言层面看,Java类的构造器只能通过两种方式调用,一个是通过new表达式,另一个是通过反射调用构造器。这两种方式对Java程序员来说都是“整体”的,但实际新建对象的动作分两步走:
1、创建出空对象(此时类型已经是正确的了),对应字节码是new
2、调用某个版本的构造器,对应字节码是invokespecial "<init>"。
默认的Java反序列化机制同样是分两步走,但变成:
1、创建出空对象(此时类型已经是正确的了);
2、调用用户定义的反序列化方法(readObject,如果有的话)或者调用默认反序列化方法。
这就是为什么反序列化可以看作是“隐藏的构造器”。
如果想自己试试去玩创建出空对象但却不调用构造器的,可以试试sun.misc.Unsafe.allocateInstance()
用Groovy控制台来演示一下:
可以看到,创建f2指向的Foo对象时,构造器并没有被调用(没有输出"foo ctor!"),实例的状态(value)也并未按用户指定的值初始化(12345),整个对象的所有字段都处于默认状态(0或者null或者false之类)。
只是借这个话题用Unsafe举例说明Java对象的创建是分两步走、调用构造器只是其中一步。并不是说反序列化的时候就一定用了Unsafe哦,这个请注意区分 ^_^
实际上在Sun JDK的实现里,Java层面的反射类库与JVM层面的反射实现相互配合来完成反序列化。java.io.ObjectStreamClass通过跟反射方法/构造器调用类似的机制获取所谓的“序列化构造器”,在反序列化的时候调用这个版本的构造器。
创建这个“序列化构造器”时要在继承链里从最具体向最抽象的方向搜索,找出第一个不可序列化的类(没有实现Serializable接口的类),并找出它的无参构造器来调用。也就是说,反序列化的时候并不是完全不调用用户代码里声明的构造器,只是不调用实现了Serializable的类的而已。
关于构造器,之前还有别的讨论可以参考:
实例构造器是不是静态方法?
======================================================================
用以前介绍过的办法,把原帖里的例子拿来做一下实验,可以更形象的说明问题。
原帖代码(稍微修改,去掉了包名):
MyFilter:
编译的时候用:
然后先运行:
别让它退出,用jps查出它的进程ID,然后用ClassDump得到class文件:
这样就得到了./sun/reflect/GeneratedSerializationConstructorAccessor1.class文件。那么用javap就能查看到它的内容:
对应的Java代码(示意,里面的逻辑用Java无法直接表示,因为Java里new表达式同时包含了创建对象与调用构造器,而且这两个动作必须针对同一类型;而这里创建了TestClass的实例却调用了Object的无参构造器):
(注:上面代码为了省事用了Java 7的multi-catch语法)
可以留意一下一段有趣的注释:
一些相关的值得关注的方法和类有:
java.io.ObjectStreamClass.getSerializableConstructor()
sun.reflect.ReflectionFactory.newConstructorForSerialization()
sun.reflect.MethodAccessorGenerator.generateSerializationConstructor()
sun.reflect.SerializationConstructorAccessorImpl
======================================================================
上面用Sun JDK来演示了反序列化的一种可能的实现方式。事实上Sun JDK也是从1.4开始才采用这种方式的,之前使用的是别的方式。这种方式使用了无法通过校验(verification)的字节码序列,硬要说的话是与JVM规范冲突的;为了能执行它而不出错,HotSpot VM里专门开了后门。
请注意区分“规范”与实现之间的差异。
规范一般定得会比较紧,而实现则可能在许多地方“走捷径”,只要不让上面的用户能感知到走了捷径就没问题 ∩^_^∩
3 楼 ouchxp 昨天 引用
回1,2楼 看<深入JAVA虚拟机>
我也刚看个开头
<深入JAVA虚拟机>
记得确实对这些有描述
第七章吧,对象实例化,四种
new
反射
克隆
反序列化
膈应人.:twisted:
把之前我引用过的一段也贴上来:
RednaxelaFX 写道
嗯顺带推荐Effective Java, Second Edition的第74条
引用
A second cost of implementing Serializable is that it increases the likelihood
of bugs and security holes. Normally, objects are created using constructors;
serialization is an extralinguistic mechanism for creating objects. Whether
you accept the default behavior or override it, deserialization is a “hidden constructor”
with all of the same issues as other constructors. Because there is no
explicit constructor associated with deserialization, it is easy to forget that you
must ensure that it guarantees all of the invariants established by the constructors
and that it does not allow an attacker to gain access to the internals of the object
under construction. Relying on the default deserialization mechanism can easily
leave objects open to invariant corruption and illegal access (Item 76).
of bugs and security holes. Normally, objects are created using constructors;
serialization is an extralinguistic mechanism for creating objects. Whether
you accept the default behavior or override it, deserialization is a “hidden constructor”
with all of the same issues as other constructors. Because there is no
explicit constructor associated with deserialization, it is easy to forget that you
must ensure that it guarantees all of the invariants established by the constructors
and that it does not allow an attacker to gain access to the internals of the object
under construction. Relying on the default deserialization mechanism can easily
leave objects open to invariant corruption and illegal access (Item 76).
在Java语言层面看,Java类的构造器只能通过两种方式调用,一个是通过new表达式,另一个是通过反射调用构造器。这两种方式对Java程序员来说都是“整体”的,但实际新建对象的动作分两步走:
1、创建出空对象(此时类型已经是正确的了),对应字节码是new
2、调用某个版本的构造器,对应字节码是invokespecial "<init>"。
默认的Java反序列化机制同样是分两步走,但变成:
1、创建出空对象(此时类型已经是正确的了);
2、调用用户定义的反序列化方法(readObject,如果有的话)或者调用默认反序列化方法。
这就是为什么反序列化可以看作是“隐藏的构造器”。
如果想自己试试去玩创建出空对象但却不调用构造器的,可以试试sun.misc.Unsafe.allocateInstance()
用Groovy控制台来演示一下:
Groovy Shell (1.7.2, JVM: 1.6.0_23) Type 'help' or '\h' for help. ---------------------------------------------------------------- groovy:000> class Foo { groovy:001> int value = 12345; groovy:002> Foo() { println "foo ctor!" } groovy:003> int getValue() { println "getValue"; value } groovy:004> } ===> true groovy:000> f1 = new Foo() foo ctor! ===> Foo@10f0625 groovy:000> f1.value getValue ===> 12345 groovy:000> f2 = sun.misc.Unsafe.theUnsafe.allocateInstance(Foo) ===> Foo@38fff7 groovy:000> f2.value getValue ===> 0 groovy:000> quit
可以看到,创建f2指向的Foo对象时,构造器并没有被调用(没有输出"foo ctor!"),实例的状态(value)也并未按用户指定的值初始化(12345),整个对象的所有字段都处于默认状态(0或者null或者false之类)。
只是借这个话题用Unsafe举例说明Java对象的创建是分两步走、调用构造器只是其中一步。并不是说反序列化的时候就一定用了Unsafe哦,这个请注意区分 ^_^
实际上在Sun JDK的实现里,Java层面的反射类库与JVM层面的反射实现相互配合来完成反序列化。java.io.ObjectStreamClass通过跟反射方法/构造器调用类似的机制获取所谓的“序列化构造器”,在反序列化的时候调用这个版本的构造器。
创建这个“序列化构造器”时要在继承链里从最具体向最抽象的方向搜索,找出第一个不可序列化的类(没有实现Serializable接口的类),并找出它的无参构造器来调用。也就是说,反序列化的时候并不是完全不调用用户代码里声明的构造器,只是不调用实现了Serializable的类的而已。
关于构造器,之前还有别的讨论可以参考:
实例构造器是不是静态方法?
======================================================================
用以前介绍过的办法,把原帖里的例子拿来做一下实验,可以更形象的说明问题。
原帖代码(稍微修改,去掉了包名):
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.Serializable; public class TestClass implements Serializable { private static final long serialVersionUID = 0L; public TestClass() throws Exception { throw new Exception("!!!"); } public static void main(String[] args) throws Exception { byte[] head = { -84, -19, 0, 5, 115, 114, 0 }; byte[] ass = { 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 120, 112 }; String name = TestClass.class.getName(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(head); baos.write(name.length()); baos.write(name.getBytes()); baos.write(ass); baos.flush(); baos.close(); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())); TestClass o = (TestClass) ois.readObject(); ois.close(); System.out.println("Created: " + o); System.in.read(); // 暂停进程以便有足够时间来dump class } }
MyFilter:
import sun.jvm.hotspot.tools.jcore.ClassFilter; import sun.jvm.hotspot.oops.InstanceKlass; public class MyFilter implements ClassFilter { @Override public boolean canInclude(InstanceKlass kls) { String klassName = kls.getName().asString(); return klassName.startsWith("sun/reflect/GeneratedSerializationConstructorAccessor"); } }
编译的时候用:
javac -classpath ".:$JAVA_HOME/lib/sa-jdi.jar" MyFilter TestClass
然后先运行:
java TestClass
别让它退出,用jps查出它的进程ID,然后用ClassDump得到class文件:
java -classpath ".:./bin:$JAVA_HOME/lib/sa-jdi.jar" -Dsun.jvm.hotspot.tools.jcore.filter=MyFilter sun.jvm.hotspot.tools.jcore.ClassDump 7566
这样就得到了./sun/reflect/GeneratedSerializationConstructorAccessor1.class文件。那么用javap就能查看到它的内容:
Classfile /D:/experiment/test_java_deserialize/sun/reflect/GeneratedSerializationConstructorAccessor1.class Last modified 2010-12-23; size 1313 bytes MD5 checksum 6d59fc9bb0c7d58458cdc76714829a0f public class sun.reflect.GeneratedSerializationConstructorAccessor1 extends sun.reflect.SerializationConstructorAccessorImpl minor version: 0 major version: 46 flags: ACC_PUBLIC Constant pool: // ... { public sun.reflect.GeneratedSerializationConstructorAccessor1(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #36 // Method sun/reflect/SerializationConstructorAccessorImpl."<init>":()V 4: return public java.lang.Object newInstance(java.lang.Object[]) throws java.lang.reflect.InvocationTargetException; flags: ACC_PUBLIC Exceptions: throws java.lang.reflect.InvocationTargetException Code: stack=6, locals=2, args_size=2 0: new #6 // class TestClass 3: dup 4: aload_1 5: ifnull 24 8: aload_1 9: arraylength 10: sipush 0 13: if_icmpeq 24 16: new #22 // class java/lang/IllegalArgumentException 19: dup 20: invokespecial #29 // Method java/lang/IllegalArgumentException."<init>":()V 23: athrow 24: invokespecial #12 // Method java/lang/Object."<init>":()V 27: areturn 28: invokespecial #42 // Method java/lang/Object.toString:()Ljava/lang/String; 31: new #22 // class java/lang/IllegalArgumentException 34: dup_x1 35: swap 36: invokespecial #32 // Method java/lang/IllegalArgumentException."<init>":(Ljava/lang/String;)V 39: athrow 40: new #24 // class java/lang/reflect/InvocationTargetException 43: dup_x1 44: swap 45: invokespecial #35 // Method java/lang/reflect/InvocationTargetException."<init>":(Ljava/lang/Throwable;)V 48: athrow Exception table: from to target type 0 24 28 Class java/lang/ClassCastException 0 24 28 Class java/lang/NullPointerException 24 27 40 Class java/lang/Throwable }
对应的Java代码(示意,里面的逻辑用Java无法直接表示,因为Java里new表达式同时包含了创建对象与调用构造器,而且这两个动作必须针对同一类型;而这里创建了TestClass的实例却调用了Object的无参构造器):
package sun.reflect; public class GeneratedSerializationConstructorAccessor1 extends SerializationConstructorAccessorImpl { public GeneratedMethodAccessor1() { super(); } public Object newInstance(Object[] args) throws InvocationTargetException { try { // create an unitialized TestClass instance TestClass temp = newUnitializedTestClassInstance(); // new #6 // class TestClass // dup // check parameters if (args.length != 0) throw new IllegalArgumentException(); } catch (final ClassCastException | NullPointerException e) { throw new IllegalArgumentException(e.toString()); } // invoke Object() constructor try { invokeObjectConstructor(temp); // invokespecial #12 // Method java/lang/Object."<init>":()V return temp; // areturn } catch (Throwable t) { throw new InvocationTargetException(t); } } }
(注:上面代码为了省事用了Java 7的multi-catch语法)
可以留意一下一段有趣的注释:
package sun.reflect; /** <P> Java serialization (in java.io) expects to be able to instantiate a class and invoke a no-arg constructor of that class's first non-Serializable superclass. This is not a valid operation according to the VM specification; one can not (for classes A and B, where B is a subclass of A) write "new B; invokespecial A()" without getting a verification error. </P> <P> In all other respects, the bytecode-based reflection framework can be reused for this purpose. This marker class was originally known to the VM and verification disabled for it and all subclasses, but the bug fix for 4486457 necessitated disabling verification for all of the dynamically-generated bytecodes associated with reflection. This class has been left in place to make future debugging easier. </P> */ abstract class SerializationConstructorAccessorImpl extends ConstructorAccessorImpl { }
一些相关的值得关注的方法和类有:
java.io.ObjectStreamClass.getSerializableConstructor()
sun.reflect.ReflectionFactory.newConstructorForSerialization()
sun.reflect.MethodAccessorGenerator.generateSerializationConstructor()
sun.reflect.SerializationConstructorAccessorImpl
======================================================================
上面用Sun JDK来演示了反序列化的一种可能的实现方式。事实上Sun JDK也是从1.4开始才采用这种方式的,之前使用的是别的方式。这种方式使用了无法通过校验(verification)的字节码序列,硬要说的话是与JVM规范冲突的;为了能执行它而不出错,HotSpot VM里专门开了后门。
请注意区分“规范”与实现之间的差异。
规范一般定得会比较紧,而实现则可能在许多地方“走捷径”,只要不让上面的用户能感知到走了捷径就没问题 ∩^_^∩
评论
6 楼
beritha
2011-10-11
受教了
5 楼
liuleigang
2010-12-24
引用
3 楼 ouchxp 昨天 引用
回1,2楼 看<深入JAVA虚拟机>
我也刚看个开头
<深入JAVA虚拟机>
记得确实对这些有描述
第七章吧,对象实例化,四种
new
反射
克隆
反序列化
4 楼
ouchxp
2010-12-23
引用
throw new Exception("!!!");
膈应人.:twisted:
3 楼
ouchxp
2010-12-23
回1,2楼 看<深入JAVA虚拟机>
我也刚看个开头
我也刚看个开头
2 楼
xgj1988
2010-12-23
还有就是希望给一个 地址。 专门讲 CLASS文件 JAVAP 之后的 的汇编代码的指令意思。
1 楼
xgj1988
2010-12-23
看得似懂非懂的。看来需要了解一下VM 规范和JAVA规范。
发表评论
-
The Prehistory of Java, HotSpot and Train
2014-06-02 08:18 0http://cs.gmu.edu/cne/itcore/vi ... -
MSJVM and Sun 1.0.x/1.1.x
2014-05-20 18:50 0当年的survey paper: http://www.sym ... -
Sun JDK1.4.2_28有TieredCompilation
2014-05-12 08:48 0原来以前Sun的JDK 1.4.2 update 28就已经有 ... -
IBM JVM notes (2014 ver)
2014-05-11 07:16 0Sovereign JIT http://publib.bou ... -
class data sharing by Apple
2014-03-28 05:17 0class data sharing is implement ... -
Java 8与静态工具类
2014-03-19 08:43 16290以前要在Java里实现所谓“静态工具类”(static uti ... -
Java 8的default method与method resolution
2014-03-19 02:23 10465先看看下面这个代码例子, interface IFoo { ... -
HotSpot Server VM与Server Class Machine
2014-02-18 13:21 0HotSpot VM历来有Client VM与Server V ... -
Java 8的lambda表达式在OpenJDK8中的实现
2014-02-04 12:08 0三月份JDK8就要发布首发了,现在JDK8 release c ... -
GC stack map与deopt stack map的异同
2014-01-08 09:56 0两者之间不并存在包含关系。它们有交集,但也各自有特别的地方。 ... -
HotSpot Server Compiler与data-flow analysis
2014-01-07 17:41 0http://en.wikipedia.org/wiki/Da ... -
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22408(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
对C语义的for循环的基本代码生成模式
2013-10-19 23:12 21884之前有同学在做龙书(第二版)题目,做到8.4的练习,跟我对答案 ... -
Java的instanceof是如何实现的
2013-09-22 16:57 0Java语言规范,Java SE 7版 http://docs ... -
oop、klass、handle的关系
2013-07-30 17:34 0oopDesc及其子类的实例 oop : oopDesc* ... -
Nashorn各种笔记
2013-07-15 17:03 0http://bits.netbeans.org/netbea ... -
《深入理解Java虚拟机(第二版)》书评
2013-07-08 19:19 0值得推荐的中文Java虚拟机入门书 感谢作者赠与的样书,以下 ... -
豆列:从表到里学习JVM实现
2013-06-13 14:13 48397刚写了个学习JVM用的豆列跟大家分享。 豆列地址:http: ...
相关推荐
- 构造方法的名字必须与类名相同,没有返回值类型(即使是void也不行)。 #### 2. 构造方法的使用 - 默认构造方法:如果没有显式定义任何构造方法,Java编译器将自动提供一个默认构造方法。 - 显式构造方法:可以...
`Monkey`类中有默认构造方法和带参数的构造方法,后者允许我们在创建对象时立即设置属性值。 接着,我们学习了如何创建和使用对象。在Java中,通过`new`关键字和类的构造方法来创建对象。实验中,我们创建了两个`...
这提供了更大的灵活性,使得可以根据不同需求创建对象。 ```java public class MyClass { private int myValue; // 默认构造器 public MyClass() { myValue = 0; } // 带参数的构造器 public MyClass...
在Java中,类是用来创建对象的模板,它包含变量(也称为属性或成员变量)和方法(函数)。例如,`Student.java`文件可能定义了一个名为`Student`的类,用于描述学生的基本信息和行为,如姓名、学号和学习方法。 ```...
在面向对象编程中,构造方法是创建对象时必须考虑的一个重要概念。在Java语言中,构造方法用于初始化新创建的对象。其特殊之处在于它的名称与类名相同,并且它没有返回类型,不允许有return语句。构造方法在创建对象...
在编程领域,构造方法是面向对象编程中的一个重要概念,它在创建对象时起着至关重要的作用。构造方法是一种特殊的方法,用于初始化新创建的对象。在Java等面向对象语言中,构造方法的名字必须与类名完全相同,且没有...
●类的定义 ●对象的定义 ●类与对象 ●对象定义类的示例 ●实例变量 ●实例方法 ●实例成员的访问 ●对象的创建 ●构造方法 ●构造方法的特点 ●实例成员访问形式
- **参数化构造器**:当需要在创建对象时传入特定值来初始化对象时,我们会使用参数化的构造器。 - **构造器链**:通过使用`this`关键字,一个构造器可以调用同一类中的另一个构造器,以简化代码并避免重复。 - **...
当创建对象时,如果没有指定构造方法,系统会调用默认的无参构造方法。这种情况下,内存分配的过程通常是:在堆内存中为新对象分配空间,然后调用构造方法进行初始化。 然而,有时我们需要更复杂的初始化,这时就...
无参构造方法用于简单的初始化,而有参构造方法允许在创建对象时传递参数,这样可以在对象创建时设定更复杂的初始状态。 #### 四、构造方法的调用 构造方法在使用`new`关键字创建对象时被自动调用。此外,在一个类...
构造方法的主要任务是在创建对象时设置对象的初始状态,为对象成员变量赋值。当一个类被实例化时,Java会自动调用该类的构造方法来完成初始化工作。 二、构造方法的声明 构造方法的声明格式如下: ```java public ...
- 创建对象通常通过使用`new`关键字和类的构造方法来完成。 - 例如: ```java Car myCar = new Car(); ``` 3. **访问成员**: - 一旦创建了对象,就可以使用`.`操作符来访问其成员变量和方法。 - 例如: ``...
当使用`new`关键字创建对象时,系统自动调用构造方法。 ```java public class Person { String name; int age; // 构造方法 public Person(String name, int age) { this.name = name; this.age = age; } ...
构造方法是 Java 中一个特殊的方法,它用来初始化对象的状态,提供了对象创建和初始化的机制。构造方法的特点是,它只能被调用一次,即在对象创建时;它不能被继承和重写。 知识点二:继承和构造方法 在 Java 中,...
构造方法是类的一个特殊方法,当创建类的新实例时自动调用。在PHP中,构造方法的名称是`__construct()`。我们可以利用构造方法来初始化对象的属性,如下所示: ```php class Person { public $name; private $age...
在编程中,我们经常使用构造函数或set方法来创建对象,但当对象的构造过程变得复杂,尤其是涉及到多个可选参数时,传统的构造方式可能会导致代码可读性和维护性降低。这时,建造者模式就显得尤为重要。 构造函数...
在反序列化过程中,Java会自动调用对象的无参构造方法来创建对象。为了使一个类可以被反序列化,该类需要实现Serializable接口。示例代码如下: ```java ObjectInputStream ois = new ObjectInputStream(new ...
4. **参数可选**:构造方法可以带有参数,允许在创建对象时传递初始值,也可以没有参数,提供一个默认的构造方式。 在农业信息系统的人员信息管理部分,可能需要创建教师和学生类。例如,教师类(Teacher)包含姓名...
在Java中,构造方法是一个特殊的方法,它在创建对象时被自动调用,用于初始化新创建的对象的状态。本章节我们将深入探讨Java中的构造方法及其重要性。 首先,构造方法的命名必须与类名完全相同,不包含任何返回类型...