In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-20 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article mainly introduces the Java class and object initialization process example analysis, has a certain reference value, interested friends can refer to, I hope you can learn a lot after reading this article, the following let the editor take you to understand it.
Problem introduction
Recently, I was debugging an enumerated parser program, which loads more than 10,000 enumerated codes in the database into the cache, in order to quickly locate enumerated code and all enumerated elements of specific enumerated categories. This class adopts two strategies to index enumerated code while loading it. Because this class is a public service class and is used at all levels of the program, I implement it as a singleton class. This class works fine before I reposition the class instantiation statement, but when I adjust the class instantiation statement to the static initialization statement, my program no longer works for me. Here is the sample code that I have simplified:
[listing 1]
Package com.ccb.framework.enums
Import java.util.Collections
Import java.util.HashMap
Import java.util.Map
Public class CachingEnumResolver {
/ / singlet instance all problems are caused by this line
Private static final CachingEnumResolver SINGLE_ENUM_RESOLVER = new CachingEnumResolver ()
/ * MSGCODE- > Category memory index * /
Private static Map CODE_MAP_CACHE
Static {
CODE_MAP_CACHE = new HashMap ()
/ / to illustrate, I initialize a piece of data here
CODE_MAP_CACHE.put ("0", "Beijing")
}
/ / private, for single instance
Private CachingEnumResolver () {
/ / initializing loading data causes problems, and this method should also be held responsible.
InitEnums ()
}
/ * initialize all enumerated types * /
Public static void initEnums () {
/ / ~ the problem begins to be exposed here ~ / /
If (null = = CODE_MAP_CACHE) {
System.out.println ("CODE_MAP_CACHE is empty, the problem begins to be exposed here.")
CODE_MAP_CACHE = new HashMap ()
}
CODE_MAP_CACHE.put ("1", "Beijing")
CODE_MAP_CACHE.put ("2", "Yunnan")
/ /. Other code...
}
Public Map getCache () {
Return Collections.unmodifiableMap (CODE_MAP_CACHE)
}
/ * get singularity instance * * @ return * /
Public static CachingEnumResolver getInstance () {
Return SINGLE_ENUM_RESOLVER
}
Public static void main (String [] args) {
System.out.println (CachingEnumResolver.getInstance () .getCache ())
}
}
Presumably everyone will feel at a loss after reading the above code, this class seems to be no problem, ah, this is indeed a typical hungry Han monomorphic mode, how can there be a problem?
Yes, he does seem to be all right, but if you run him up, the result is that he won't work correctly for you. Run this class, and its execution result is:
[listing 2]
CODE_MAP_CACHE is empty, and the problem begins to expose here. {0 = Beijing}
How could my program be like this? Why is CODE_MAP_CACHE empty in the initEnum () method? Why do I output CODE_MAP_CACHE content with only one element and the other two elements?!!
See here, if you are debugging the program, you must feel very strange at the moment, is there something wrong with my Jvm? No! If not, what happened to my program? This is definitely not what I want. But in fact, no matter how much you modify the initEnum () method will not help, at least at first I certainly do not suspect that the problem may be in the creation of CachingEnumResolver instances. It took me three or four hours-about half a working day-to believe in the way I created CachingEnumResolver instances, and because there was a misunderstanding of the underlying principles of Java class initialization and object instantiation.
So what exactly is the problem? Why did such a strange thing happen? Before we solve this problem, let's take a look at the underlying mechanism of class and object initialization in JVM.
The life cycle of a class
The figure shows the flow of the class life cycle; in this article, I'm only going to talk about the "initialization" and "object instantiation" phases of the class.
Class initialization
The class "initialization" phase, which is the last piece of work before a class or interface is used for the first time, is responsible for assigning the correct initial value to the class variable.
The Java compiler collects all the class variable initialization statements and static initializers of types into the method, which can only be called by Jvm and specially undertakes the initialization work.
In addition to the interface, before initializing a class, you must ensure that its immediate superclass has been initialized, and that the initialization process is thread-safe by Jvm. In addition, not all classes will have a () method, and the class will not have a () method under the following conditions:
This class declares neither any class variables nor static initialization statements; the class declares class variables, but does not explicitly initialize them with class variable initialization statements or static initialization statements; the class contains only class variable initialization statements of static final variables, and the class variable initialization statements are compile-time constant expressions.
Object initialization
After the class is loaded, connected, and initialized, the class can be used at any time. Object instantiation and initialization is the activity of the initial stage of object life. Here we mainly discuss the characteristics of object initialization.
When the Java compiler compiles each class, it generates at least one instance initialization method for the class-- the "()" method. This method corresponds to each constructor in the source code. If the class does not explicitly declare any constructor, the compiler generates a default parameterless constructor for the class, which only calls the parent class's parameterless constructor. At the same time, it also generates a "()" method corresponding to the default constructor.
Generally speaking, the content of the code included in the () method is probably: calling another () method; initializing the instance variable; and the code in the corresponding constructor. If the constructor explicitly starts by calling another constructor in the same class, its corresponding () method includes a call to the class's () method and all bytecode within the application constructor.
If the constructor does not start by calling another constructor of its own class, and the object is not an Object object, then the () method includes a call to the parent class () method; the bytecode of the initialization method on the instance variable; and finally the method body bytecode corresponding to the constructor.
If the class is Object, its () method does not include a call to the parent class () method.
The initialization time of the class
So far in this article, we have a rough idea of what stages have been experienced in the class life cycle, but when did class loading, the beginning of the class life cycle, be triggered? When was the class initialized? Let's continue to look for answers with these three questions.
The Java virtual machine specification strictly defines the timing of class initialization: "initialize on first active use"-"initialization on first active use". This rule directly affects the mechanism of class loading, connecting, and initializing classes-because the type must be connected before it can be initialized, but it must be loaded before it can be connected.
On the issue of class loading timing related to initialization timing, the Java virtual machine specification does not make a strict definition of it, which makes JVM can provide different loading strategies according to its own characteristics. We can think about the implementation principle of the Jboss AOP framework, which is tampering with your class file loading process by inserting the relevant intercept bytecode of AOP, which makes it completely transparent to the programmer, even if you create an object instance with the new operator, it can also be intercepted by the AOP framework-- the corresponding Spring AOP, you must obtain the managed object proxied by AOP through his BeanFactory. Of course, the disadvantage of Jboss AOP is also obvious-it is closely bound to the JBOSS server, and you can't easily port it to other servers. Um. At this point, it's a little beside the point, you know, the AOP implementation strategy is enough to write a thick book, hey, stop here.
Having said that, the time to initialize a class is "when it is used actively for the first time," so under what circumstances does it meet the requirements for first-time active use?
First-time unsolicited use:
When you create a new instance of a class-- new, reflection, cloning, or deserialization
When calling a static method of a class
When using or assigning values to a static field of a class or interface (except the final field)
When calling some reflection methods of Java
When initializing a subclass of a class
One of the startup classes that contains the main () method when the virtual machine starts.
With the exception of the above cases, all other ways of using JAVA types are passive, and they do not cause class initialization.
What exactly is my problem?
Well, after understanding the class initialization and object initialization mechanism of JVM, we have a theoretical basis and can analyze the problem rationally.
Let's take a look at the bytecode translated by the JAVA source code in the previous [listing 1]:
[listing 3]
Public class com.ccb.framework.enums.CachingEnumResolver extendsjava.lang.Object {
Static {}
Code: 0: new # 2
/ / class CachingEnumResolver
3: dup
4: invokespecial # 14
/ / Method "": () V ①
7: putstatic # 16
/ / Field SINGLE_ENUM_RESOLVER:Lcom/ccb/framework/enums/CachingEnumResolver
10: new # 18
/ / class HashMap ②
13: dup
14: invokespecial # 19
/ / Method java/util/HashMap. "() V
17: putstatic # 21
/ / Field CODE_MAP_CACHE:Ljava/util/Map
20: getstatic # 21
/ / Field CODE_MAP_CACHE:Ljava/util/Map
23: ldc # 23
/ / String 0
25: ldc # 25
/ / String Beijing
27: invokeinterface # 31, 3
/ / InterfaceMethod java/util/Map.put: (Ljava/lang/Object;Ljava/lang/Object;) Ljava/lang/Object; ③
32: pop 33: returnprivate com.ccb.framework.enums.CachingEnumResolver ()
Code: 0: aload_0 1: invokespecial # 34
/ / Method java/lang/Object. ": () V 4: invokestatic # 37
/ / Method initEnums: () V ④ 7: returnpublic static void initEnums ()
Code: 0: getstatic # 21
/ / Field CODE_MAP_CACHE:Ljava/util/Map
⑤ 3: ifnonnull 24 6: getstatic # 44
/ / Field java/lang/System.out:Ljava/io/PrintStream
9: ldc # 46
/ / String CODE_MAP_CACHE is empty, and the problem begins to be exposed here.
11: invokevirtual # 52
/ / Method java/io/PrintStream.println: (Ljava/lang/String;) V 14: new # 18
/ / class HashMap 17: dup 18: invokespecial # 19
/ / Method java/util/HashMap. "" () V ⑥ 21: putstatic # 21
/ / Field CODE_MAP_CACHE:Ljava/util/Map
24: getstatic # 21
/ / Field CODE_MAP_CACHE:Ljava/util/Map
27: ldc # 54
/ / String 1 29: ldc # 25
/ / String Beijing 31: invokeinterface # 31,3
/ / InterfaceMethod java/util/Map.put: (Ljava/lang/Object
Ljava/lang/Object;) Ljava/lang/Object
⑦ 36: pop 37: getstatic # 21
/ / Field CODE_MAP_CACHE:Ljava/util/Map
40: ldc # 56
/ / String 2 42: ldc # 58
/ / String Yunnan Province 44: invokeinterface # 31,3
/ / InterfaceMethod java/util/Map.put: (Ljava/lang/Object;Ljava/lang/Object;) Ljava/lang/Object
⑧ 49: pop 50: returnpublic java.util.Map getCache ()
Code: 0: getstatic # 21
/ / Field CODE_MAP_CACHE:Ljava/util/Map
3: invokestatic # 66
/ / Method java/util/Collections.unmodifiableMap: (Ljava/util/Map;) Ljava/util/Map
6: areturnpublic static com.ccb.framework.enums.CachingEnumResolver getInstance ()
Code: 0: getstatic # 16
/ / Field SINGLE_ENUM_RESOLVER:Lcom/ccb/framework/enums/CachingEnumResolver
⑨ 3: areturn}
If [listing 1] above shows that the contents of the list are bytecode content in the JDK1.4 environment, maybe the list is not very attractive to a large number of brothers, because these JVM instructions are really not as beautiful and easy to understand as the source code. But it is indeed the most direct way to find and locate the problem, and the answer we want is in this JVM instruction list.
Now, let's analyze the code execution trajectory in listing 1 for the whole process of class initialization to object instance initialization.
As mentioned earlier, class initialization is the last previous work when the class is actually available, which is responsible for initializing the correct values for all classes, which is thread-safe and JVM ensures multi-thread synchronization.
Step 1: call the class initialization method CachingEnumResolver. (), which is invisible to the outside world, in other words, a special method within JVM, which includes all the initialization statements of class variables with specified initial values in CachingEnumResolver. It is important to note that not every class has this method, and the details have been described earlier.
Step 2: inside the () method, let's look at the "①" line in the bytecode, which, combined with the above two lines, represents a CachingEnumResolver object instance of new, which itself refers to the call to the () method of the CachingEnumResolver class. Each Java class has a () method that is generated by the Java compiler at compile time and is invisible to the outside world. The () method includes all instance variable initialization statements with specified initialization values and all statements within the constructor of the java class. Objects are initialized by this method when they are instantiated. At this point, however, a potential problem has been ambushed here, waiting for you to commit it.
Step 3: let's look down in the execution order, the "④" line, where the method is the constructor of the class, which first calls the constructor () of the parent class to initialize the parent object, and then calls the CachingEnumResolver.initEnum () method to load the data.
Step 4: the "⑤" line, which gets the value of the "CODE_MAP_CACHE" field, which is null at run time. Note that the problem is starting to show up. As a programmer, you must hope that the field has been initialized, when in fact it has not been initialized. By judgment, because the field is NULL, the program will continue to execute to the "⑥" line, instantiating the field as HashMap ().
Step 5: in the "⑦" and "⑧" lines, the function is to fill in two pieces of data for the "CODE_MAP_CACHE" field.
Step 6: exit the object initialization method () and initialize the generated object instance to the class field "SINGLE_ENUM_RESOLVER". Note that the class variable in the object instance is not fully initialized at the moment, and the class variable "CODE_MAP_CACHE" assigned by the () call to the initEnum () method is that the () method has not yet initialized the field, and it will be overridden again in the later class initialization process.
Step 7: continue to execute the successor code in the () method, the "②" line, which instantiates the "CODE_MAP_CACHE" field as an HashMap instance (note: this field has been assigned to another instance when the object is instantiated, and now the class variable value of the instance referenced by the "CODE_MAP_CACHE" variable is overwritten, so our question has been answered).
Step 8: the class is initialized and the instantiation of the singleton class is completed.
Through the analysis of the bytecode execution process above, you may have a clear understanding of the underlying cause of the error, or you may have been confused by the above analysis process, but it has not been broken. Although I can also explain the problem from the perspective of source code, it is not deep enough, and it will be suspected of being only a personal point of view and not credible.
How to solve
To solve the problem with the above code, it is simple to transfer the initialization assignment statement of the "SINGLE_ENUM_RESOLVER" variable to the getInstance () method. In other words, avoid instantiating the class internally or referencing uninitialized fields during initialization when the class is not yet initialized.
Write at the end
Calm down and think carefully about whether you have really mastered the knowledge derived from the topic of this article. If you think you have completely or basically mastered it, then good. In the end, I will make some changes to the previous code. Please consider whether the following two groups of programs will have the same problems.
Program one
Public class CachingEnumResolver {
Public static Map CODE_MAP_CACHE
Static {
CODE_MAP_CACHE = new HashMap ()
/ / to illustrate, I initialize a piece of data here
CODE_MAP_CACHE.put ("0", "Beijing")
InitEnums ()
}
Procedure two
Public class CachingEnumResolver {
Private static final CachingEnumResolver SINGLE_ENUM_RESOLVER
Public static Map CODE_MAP_CACHE
Static {
CODE_MAP_CACHE = new HashMap ()
/ / to illustrate, I initialize a piece of data here
CODE_MAP_CACHE.put ("0", "Beijing")
SINGLE_ENUM_RESOLVER = new CachingEnumResolver ()
InitEnums ()
}
Thank you for reading this article carefully. I hope the article "sample Analysis of the initialization process of Java classes and objects" shared by the editor will be helpful to you. At the same time, I also hope that you will support and pay attention to the industry information channel. More related knowledge is waiting for you to learn!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.