2010-07-19 46 views
7

Tôi đang viết một công cụ mã hóa Bytecode. Ngay bây giờ, tôi đang cố gắng tìm hiểu làm thế nào để làm điều đó trong sự hiện diện của các đối tượng. Tôi muốn một số giải thích về hai dòng tôi đọc trong JVMS (phần 4.9.4):Làm rõ trên Bytecode và các đối tượng

1) "Trình xác minh loại bỏ mã sử dụng đối tượng mới trước khi nó được khởi tạo".

Câu hỏi của tôi là, "sử dụng" có nghĩa là gì ở đây? Tôi đoán rằng điều đó có nghĩa là: chuyển nó thành thuộc tính phương thức, gọi số GETFIELDPUTFIELD trên đó hoặc gọi bất kỳ phương thức thể hiện nào trên đó. Các ứng dụng bị cấm khác của họ? Và tôi tin rằng nó tuân theo các hướng dẫn khác như DUP, LOADSTORE được cho phép.

2) "Trước đây phương pháp đó gọi một phương pháp dụ khởi tạo của myClass hoặc lớp cha trực tiếp của nó trên này, hoạt động chỉ phương pháp có thể thực hiện về vấn đề này được gán lĩnh vực tuyên bố trong vòng myClass."

Có nghĩa là trong phương thức <init>, GETFIELD và PUTFIELD được phép trước khi gọi <init> khác. Tuy nhiên, trong Java, thực hiện bất kỳ thao tác nào trên một trường cá thể trước khi gọi tới số super() hoặc this() dẫn đến lỗi biên dịch. Ai đó có thể làm rõ điều này?

3) Tôi có thêm một câu hỏi. Khi nào một tham chiếu đối tượng được khởi tạo, và do đó, sẵn sàng để được sử dụng một cách tự do? Từ đọc các JVMS, tôi đã đưa ra câu trả lời cho dù một đối tượng được khởi tạo hay không, là tùy thuộc vào từng phương thức. Tại một thời điểm nhất định, đối tượng có thể được khởi tạo cho một phương thức nhưng không thể cho đối tượng kia. Cụ thể, một đối tượng được khởi tạo cho một phương thức khi <init> gọi bằng phương thức đó trả về.

Ví dụ: xem xét phương thức main() đã tạo đối tượng và được gọi là <init>, sau đó được gọi là siêu lớp <init>. Sau khi trở về từ super(), đối tượng hiện được coi là được khởi tạo bởi <init>, nhưng chưa được khởi tạo cho main(). Điều này có nghĩa là, trong <init> sau super(), tôi có thể chuyển đối tượng dưới dạng tham số cho phương thức, ngay cả trước khi trở về từ chính().

Ai đó có thể xác nhận rằng toàn bộ phân tích này là đúng? Cảm ơn bạn đã dành thời gian.

ps: Tôi đã thực sự đăng câu hỏi tương tự lên diễn đàn Sun nhưng với phản hồi. Tôi hy vọng tôi sẽ có thêm may mắn ở đây. Cảm ơn bạn.

Cập nhật

Đầu tiên cảm ơn bạn đã trả lời và thời gian của bạn. Mặc dù tôi không nhận được câu trả lời rõ ràng (tôi có nhiều câu hỏi và một số câu hỏi mơ hồ), câu trả lời và ví dụ của bạn, và các thí nghiệm tiếp theo, cực kỳ hữu ích cho tôi để hiểu sâu hơn về cách JVM hoạt động.

Điều chính tôi phát hiện là hành vi của Trình xác minh khác với các triển khai và phiên bản khác nhau (làm cho công việc thao tác bytecode phức tạp hơn nhiều).Vấn đề nằm ở sự không phù hợp với các JVMS, hoặc thiếu tài liệu từ các nhà phát triển của trình xác minh, hoặc JVMS có một số vagueness tinh tế trong khu vực của người xác minh.

Một điều cuối cùng, SO Rocks !!! Tôi đã đăng cùng một câu hỏi trong diễn đàn Sun JVM Technical chính thức, và tôi vẫn chưa có câu trả lời cho đến bây giờ.

Trả lời

1

Tôi khuyên bạn nên tải xuống bản sao của các nguồn OpenJDK và xem những gì người xác minh đang thực sự kiểm tra. Nếu không có gì khác, điều đó có thể giúp bạn hiểu những gì đặc tả JMV đang nói.

(Tuy nhiên, @Joachim là đúng. Dựa vào những gì thực hiện xác minh làm hơn là những gì các đặc điểm kỹ thuật nói là khá mạo hiểm.)

+0

Nó không phải là một ý tưởng tồi, nhưng bạn nên cẩn thận với điều này . Trong quá khứ, trình xác minh thực tế được sử dụng ít nghiêm ngặt hơn về các quy tắc hơn so với đặc điểm kỹ thuật. Hoàn toàn có thể là những trường hợp như vậy vẫn xảy ra. –

+0

Có, tôi phát hiện ra rằng các cách triển khai khác nhau của Trình xác minh có mức độ nghiêm ngặt khác nhau. Ví dụ, trình xác minh JustIce của Apache BCEL không cho phép STORE trên các tham chiếu đối tượng chưa được khởi tạo, trong khi Java HotSpot 10.0 thực hiện –

4

"Các thẩm bác bỏ mã mà sử dụng đối tượng mới trước khi nó đã được khởi tạo. "

Trong xác minh bytecode, vì trình xác minh hoạt động ở thời gian liên kết, các loại biến cục bộ của phương pháp được suy ra. Các kiểu của các đối số phương thức được biết là chúng có trong chữ ký phương thức trong tệp lớp. Các loại biến cục bộ khác không được biết và được phỏng đoán, vì vậy tôi giả sử "sử dụng" trong tuyên bố ở trên liên quan đến điều này.

EDIT: Phần 4.9.4 của JVMS đọc:

Phương pháp dụ khởi tạo (§3.9) cho lớp myClass thấy đối tượng chưa được khởi tạo mới như nó lập luận này trong biến địa phương 0. Trước khi phương pháp mà gọi một phương thức khởi tạo thể hiện khác của myClass hoặc lớp cha trực tiếp của nó về điều này, hoạt động duy nhất mà phương thức có thể thực hiện trên đây là gán các trường được khai báo trong myClass.

Việc gán trường trong câu lệnh trên là khởi tạo "ban đầu" của biến mẫu thành giá trị mặc định ban đầu (như int là 0, float là 0.0f, v.v.) khi bộ nhớ cho đối tượng được cấp phát. Có một "khởi tạo" đúng hơn các biến mẫu khi máy ảo gọi phương thức khởi tạo thể hiện (constructor) trên đối tượng.


link do John Horstmann cung cấp đã giúp làm rõ mọi thứ. Vì vậy, các câu lệnh này không đúng. "Điều này DOESNOT có nghĩa là trong một phương pháp <init>, getfieldputfield được phép trước khi gọi <init> khác." Hướng dẫn getfieldputfield được sử dụng để truy cập (và thay đổi) các biến mẫu (trường) của một lớp (hoặc trường hợp của một lớp). Và điều này chỉ có thể xảy ra khi các biến dụ (trường) được khởi tạo "

Từ JVM:. Phương pháp khởi

Mỗi dụ (§3.9), ngoại trừ đối với phương pháp khởi dụ bắt nguồn từ hàm tạo của lớp đối tượng, phải gọi phương thức khởi tạo khác phương thức khởi tạo này hoặc phương thức khởi tạo mẫu của siêu lớp trực tiếp siêu trước thành viên thể hiện được truy cập. Tuy nhiên, các trường mẫu của trường hợp này là được khai báo trong lớp hiện tại có thể được gán trước khi gọi bất kỳ phương thức khởi tạo thể hiện .

Khi Java Virtual Machine tạo ra một thể hiện mới của một lớp, hoặc ngầm hay rõ ràng, đầu tiên nó cấp phát bộ nhớ trên heap để giữ biến dụ của đối tượng. Bộ nhớ được cấp phát cho tất cả các biến được khai báo trong lớp của đối tượng và trong tất cả các lớp siêu lớp của nó, bao gồm các biến mẫu được ẩn. Ngay sau khi máy ảo đã đặt sang một bên bộ nhớ heap cho một đối tượng mới, nó ngay lập tức khởi tạo các biến cá thể thành các giá trị mặc định ban đầu. Một khi máy ảo đã cấp phát bộ nhớ cho đối tượng mới và khởi tạo các biến cá thể thành các giá trị mặc định, nó đã sẵn sàng cung cấp cho các biến cá thể các giá trị ban đầu thích hợp của chúng. Máy ảo Java sử dụng hai kỹ thuật để làm điều này, tùy thuộc vào việc đối tượng đang được tạo ra do một lời gọi clone(). Nếu đối tượng đang được tạo ra vì một bản sao(), máy ảo sẽ sao chép các giá trị của các biến cá thể của đối tượng đang được nhân bản vào đối tượng mới. Nếu không, máy ảo sẽ gọi một phương thức khởi tạo thể hiện trên đối tượng. Phương thức khởi tạo thể hiện khởi tạo các biến cá thể của đối tượng thành các giá trị ban đầu thích hợp của chúng. Và chỉ sau này bạn có thể sử dụng getfieldputfield.

Trình biên dịch java tạo ra ít nhất một phương thức khởi tạo thể hiện (constructor) cho mỗi lớp mà nó biên dịch. Nếu lớp không khai báo một cách rõ ràng, trình biên dịch tạo ra một hàm tạo no-arg mặc định, chỉ cần gọi hàm khởi tạo no-arg của lớp cha. Và đúng như vậy, hãy thực hiện bất kỳ thao tác nào trên một trường cá thể trước khi gọi tới số super() hoặc this() dẫn đến lỗi biên dịch.

Phương thức <init> có thể chứa ba loại mã: yêu cầu một phương thức khởi tạo biến khác và mã cho phần thân của hàm tạo. Nếu một constructor bắt đầu bằng một lời gọi rõ ràng của nhà xây dựng khác trong cùng một lớp (một invocation this()) của nó tương ứng <init> phương pháp sẽ bao gồm hai phần:

  • một lời gọi của cùng một lớp <init> phương pháp
  • các bytecode mà thực hiện cơ thể của các nhà xây dựng tương ứng

Nếu một constructor không bắt đầu bằng một invocation this() và lớp không được đối tượng, các <init> phương pháp sẽ có ba thành phần:

  • một lời gọi của một lớp cha <init> phương pháp
  • các bytecode cho bất kỳ trường hợp initializers biến
  • các bytecode mà thực hiện cơ thể của các nhà xây dựng tương ứng


Nếu một hàm tạo không bắt đầu bằng lời gọi this() ation và lớp là Object (và Object không có superclass), sau đó phương thức <init> của nó không thể bắt đầu bằng cách gọi phương thức <init> siêu lớp. Nếu một hàm tạo bắt đầu bằng lời gọi rõ ràng của hàm tạo siêu lớp (yêu cầu super()), phương thức <init> sẽ gọi phương thức <init> của lớp cha tương ứng.



Tôi nghĩ điều này trả lời câu hỏi đầu tiên và thứ hai của bạn.

Cập nhật:

Ví dụ,

class Demo 
    { 
    int somint; 

    Demo() //first constructor 
    { 
     this(5); 
     //some other stuff.. 
    } 

    Demo(int i) //second constructor 
    { 
     this.somint = i; 
     //some other stuff...... 
    } 
    Demo(int i, int j) //third constructor 
    { 
     super(); 
     //other stuffff...... 
    } 
    } 

Heres bytecode cho ba nhà xây dựng trên từ trình biên dịch (javac):

Demo(); 
    Code: 
    Stack=2, Locals=1, Args_size=1 
    0: aload_0 
    1: iconst_5 
    2: invokespecial #1; //Method "<init>":(I)V 
    5: return 

Demo(int); 
    Code: 
    Stack=2, Locals=2, Args_size=2 
    0: aload_0 
    1: invokespecial #2; //Method java/lang/Object."<init>":()V 
    4: aload_0 
    5: iload_1 
    6: putfield  #3; //Field somint:I 
    9: return 

Demo(int, int); 
    Code: 
    Stack=1, Locals=3, Args_size=3 
    0: aload_0 
    1: invokespecial #2; //Method java/lang/Object."<init>":()V 
    4: return 

Trong xây dựng đầu tiên, phương pháp <init> bắt đầu bằng cách gọi cùng một loại <init> meth od và sau đó thực hiện phần thân của hàm tạo tương ứng. Bởi vì hàm khởi tạo bắt đầu bằng một số this(), phương thức <init> tương ứng của nó không chứa bytecode để khởi tạo các biến mẫu.

Trong constructor thứ hai, phương pháp <init> cho các nhà xây dựng có

  • siêu phương pháp lớp <init>, tức là, gọi của constructor lớp cha (không có phương pháp arg), trình biên dịch tạo này theo mặc định vì không rõ ràng super() được tìm thấy làm tuyên bố đầu tiên.
  • mã bytecode để khởi tạo biến mẫu someint.
  • bytecode cho phần còn lại của nội dung trong cấu trúc hàm tạo .
+0

PS: Tôi đã viết một bytecode instrumenter nhưng tôi đã đọc các công cụ trên JVM gần giống như bên trong JVM. – Zaki

+0

Cảm ơn bạn lần đầu tiên !!! Tôi vẫn có vài câu hỏi: Bạn nói rằng một hàm tạo không có this() có 3 thành phần. Tuy nhiên, tôi đã đọc bytecode, và trình biên dịch không bao giờ thêm bytecode cho các biến khởi tạo biến thể (0 cho int, null cho các đối tượng). Dường như JVM khởi tạo các trường thể hiện mà không cần bytecode rõ ràng làm điều đó. –

+0

@HH, xem câu trả lời cập nhật – Zaki

3

Trái với những gì ngôn ngữ java định, ở mức độ bytecode nó thể truy cập vào các lĩnh vực của một lớp trong một constructor trước khi gọi các nhà xây dựng lớp cha. Các mã sau đây sử dụng thư viện asm để tạo ra một lớp như:

package asmconstructortest; 

import java.io.FileOutputStream; 
import org.objectweb.asm.*; 
import org.objectweb.asm.util.CheckClassAdapter; 
import static org.objectweb.asm.Opcodes.*; 

public class Main { 

    public static void main(String[] args) throws Exception { 
     //ASMifierClassVisitor.main(new String[]{"/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test.class"}); 
     ClassWriter cw = new ClassWriter(0); 
     CheckClassAdapter ca = new CheckClassAdapter(cw); 

     ca.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "asmconstructortest/Test2", null, "java/lang/Object", null); 

     { 
      FieldVisitor fv = ca.visitField(ACC_PUBLIC, "property", "I", null, null); 
      fv.visitEnd(); 
     } 

     { 
      MethodVisitor mv = ca.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 
      mv.visitCode(); 
      mv.visitVarInsn(ALOAD, 0); 
      mv.visitInsn(ICONST_1); 
      mv.visitFieldInsn(PUTFIELD, "asmconstructortest/Test2", "property", "I"); 
      mv.visitVarInsn(ALOAD, 0); 
      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); 
      mv.visitInsn(RETURN); 
      mv.visitMaxs(2, 1); 
      mv.visitEnd(); 
     } 

     ca.visitEnd(); 

     FileOutputStream out = new FileOutputStream("/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test2.class"); 
     out.write(cw.toByteArray()); 
     out.close(); 
    } 
} 

õ lớp này hoạt động tốt, không có bất kỳ lỗi nào xác minh:

package asmconstructortest; 

public class Main2 { 
    public static void main(String[] args) { 
     Test2 test2 = new Test2(); 
     System.out.println(test2.property); 
    } 
} 
+1

Tôi cũng đã thử nghiệm ý tưởng này bằng cách sử dụng jbe (trình soạn thảo bytecode java) và nó vượt qua trên trình xác minh HotSpot 10 (nhưng không phải trên Apache BCEL của JustIce) –

Các vấn đề liên quan