1. Knowledge points about object and memory control
1. The initialization process of java variables, including local variables, member variables (instance variables and class variables).
2. In the inheritance relationship, when the compile-time type and run-time type are different from the compile-time type of the object reference variable used, there is a difference in the properties and methods of accessing the object.
3. Final modifier characteristics.
2. The division and initialization process of java variables
Variables of Java programs can be roughly divided into member variables and local variables. Member variables can be divided into instance variables (non-static variables) and class variables (static variables). Generally, the local variables we encounter will appear in the following situations:
(1) Formal parameter: Local variables defined in the method signature are assigned by the caller and disappear as the method ends.
(2) Local variables within the method: Local variables defined in the method must be initialized (assign an initial value) in the method, and disappears when the variable initialization begins and ends.
(3) Local variables in code block: Local variables defined in code block must be initialized (assign initial values) that must be displayed in code blocks. They will take effect as the initialization is completed and die as the code block ends.
package com.zlc.array; public class TestField { { String b ; //If not initialized, the compiler will report The local variable b may not have been initialized System.out.println(b); } public static void main(String[] args) { int a ; //If not initialized, the compiler will report The local variable a may not have been initialized System.out.println(a); }} Member variables modified with static are class variables, which belong to the class itself. Member variables that are not modified with static are instance variables. Instances belonging to this class, in the same JVM, each class can only correspond to one Class object, but each class can create multiple Java objects. (That is, a class variable only requires one piece of memory space, and every time the class creates an instance, it needs to allocate a piece of space to the instance variable)
Initialization process of instance variables: From a syntax perspective, the program can perform initialization of instance variables in three places:
(1) Specify the initial value when defining an instance variable.
(2) Specify the initial value for instance variables in non-static blocks.
(3) Specify the initial value for instance variables in the constructor.
Among them, the initialization time of the two methods (1) and (2) is earlier than that of (3) in the constructor, and the two initialization orders (1) and (2) are determined in the order they are arranged in the source code.
package com.zlc.array; public class TestField { public TestField(int age){ System.out.println("Initialize this.age in the constructor = "+this.age); this.age = age; } { System.out.println("Initialize in non-static blocks"); age = 22; } //Initialize int age = 15; public static void main(String[] args) { TestField field = new TestField(24); System.out.println("final age = "+field.age); }} The run result is: Initialize this.age = 15 in the initialization constructor in the non-static block
Final age = 24
If you know how to use javap, you can use javap -c XXXX (class file) to see how the java class is compiled.
When defining an instance variable, specify the initial value. In the initialization block, the status of the statement specifying the initial value for the instance variable is equal. After the compiler is compiled and processed, they are all mentioned in the constructor. The above-mentioned int age = 15 will be divided into the following two steps to execute:
1) int age; When creating a java object, the system allocates memory to the object according to the statement.
2)age = 15; This statement will be extracted into the constructor of the java class and executed.
Initialization process of class variables: From a syntax perspective, a program can initialize and assign values to class variables from two places.
(1) Specify the initial value when defining a class variable.
(2) Specify the initial value for class variables in a static block.
The two execution orders are the same as their arrangement in the source code. Let's give a small abnormal example:
package com.zlc.array; class TestStatic { //Class Member DEMO TestStatic Instance Final static TestStatic DEMO = new TestStatic(15); //Class Member static int age = 20; //Instance variable curAge int curAge; public TestStatic(int years) { // TODO Auto-generated constructor stub curAge = age - years; }} public class Test{ public static void main(String[] args) { System.out.println(TestStatic.DEMO.curAge); TestStatic staticDemo = new TestStatic(15); System.out.println(staticDemo.curAge); }} The output result is printed in two lines. One is to print the instance variable of the TestStatic class attribute DEMO, and the second is to output the instance attribute of TestStatic through the staticDemo of the java object. According to the initialization process of the instance variable and class variables we analyzed above, we can infer it:
1) In the first stage of initialization, when loading the class, allocate memory space for the class variables DEMO and age. At this time, the default values of DEMO and age are null and 0 respectively.
2) In the second stage of initialization, the program assigns initial values to DEMO and age in sequence. TestStatic(15) needs to call the constructor of TestStatic. At this time, age = 0, so the printing result is -15. When staticDemo is initialized, age has been assigned to 20, so the output result is 5.
3. The difference between inheriting member variables and inheriting member methods in inheritance relationships
When creating any java object, the program will always call the non-static block and parent class constructor of the parent class first, and finally call the non-static block and constructor of this class. Calling the constructor of the parent class through the constructor of the subclass is generally divided into two situations: one is an implicit call, and the other is a super display to call the constructor of the parent class.
The child class method can call the instance variable of the parent class. This is because the child class inherits the parent class and will obtain the member variables and methods of the parent class. However, the parent class method cannot access the instance variable of the child class because the parent class does not know which class it will inherit and what kind of member variables its subclass will add. Of course, in some extreme examples, the parent class can still call the child class variable. For example: the child class rewrites the parent class method and generally prints the default value, because the instance variable of the child class has not been initialized at this time.
package com.zlc.array;class Father{ int age = 50; public Father() { // TODO Auto-generated constructor stub System.out.println(this.getClass()); //this.sonMethod(); cannot call info(); } public void info(){ System.out.println(age); }}public class Son extends Father{ int age = 24; public Son(int age) { // TODO Auto-generated constructor stub this.age = age; } @Override public void info() { // TODO Auto-generated method stub System.err.println(age); } public static void main(String[] args) { new Son(28); } // Subclass-specific method public void sonMethod(){ System.out.println("Son method"); }} According to our normal inference, the parent class constructor is implicitly called through the subclass, and the info() method is called in the parent class constructor (note: I didn't say that the parent class is called). In theory, it outputs the age instance variable of the parent class. The print result is expected to be 50, but the actual output result is 0. Analysis of the reason:
1) The memory allocation of java objects is not completed in the constructor. The constructor only completes the initialization assignment process. That is, before calling the constructor of the parent class, jvm has classified the memory space for the Son object. This space stores two age attributes, one is the age of the subclass and the other is the age of the parent class.
2) When calling new Son(28), the current this object represents an object that is a subclass Son. We can print the object.getClass() and get the result of class com.zlc.array.Son. However, the current initialization process is carried out in the constructor of the parent class, and it cannot be called through this.sonMethod(), because this compilation type is Father.
3) When the compile-time type of the variable is different from the runtime type, when accessing the instance variable of its reference object through the variable, the value of the instance variable is determined by the type of the declared variable. However, when the instance method of the object it references through the variable, the behavior of the method is determined by the object it actually references. Therefore, the info method of the subclass is called here, so the age of the subclass is printed. Since the age has not yet been urgently initialized, the default value is 0.
In layman's terms, when the declared type is inconsistent with the real new type, the attribute used is the parent class and the method called is the child class.
Through javap -c, we can more directly understand why there is a big difference between inheriting attributes and methods. If we remove the info rewrite method of the subclass Son in the example above, the info method of the parent class will be called at this time, because when compiling, the info method of the parent class will be transferred to the subclass, and the reputation member variable will be left in the parent class and not transferred. In this way, the subclass and the parent class have instance variables with the same name. If the subclass rewrites the method of the parent class with the same name, the subclass method will completely overwrite the parent class's method (as for why Java is designed like this, I am not very clear). Variables with the same name can exist and do not overwrite at the same time. Subclasses of methods with the same name will completely overwrite the parent class's same name method.
In general, for a reference variable, when accessing an instance variable of the object it references through the variable, the value of the instance variable depends on the type when the variable is declared, and when accessing the method of the object it references through the variable, the method behavior depends on the type of the object it actually references.
Finally, I will review it with a small case:
package com.zlc.array;class Animal{ int age ; public Animal(){ } public Animal(int age) { // TODO Auto-generated constructor stub this.age = age; } void run(){ System.out.println("animal run "+age); }}class Dog extends Animal{ int age; String name; public Dog(int age,String name) { // TODO Auto-generated constructor stub this.age = age; this.name = name; } @Override void run(){ System.out.println("dog run "+age); }}public class TestExtends { public static void main(String[] args) { Animal animal = new Animal(5); System.out.println(animal.age); animal.run(); Dog dog = new Dog(1, "xiaobai"); System.out.println(dog.age); dog.run(); Animal animal2 = new Dog(11, "wangcai"); System.out.println(animal2.age); animal2.run(); Animal animal3; animal3 = dog; System.out.println(animal3.age); animal3.run(); }} If you want to call the parent class method: you can call it through super, but the super keyword does not refer to any object, and it cannot be used as a real reference variable. Interested friends can study it yourself.
The above are example variables and methods. Class variables and class methods are much simpler, so using class names directly. The methods are much more convenient and you won't encounter so much trouble.
4. Use of final modifiers (especially macro replacement)
(1) Inal can modify variables. After the variable modified by final is assigned an initial value, it cannot be assigned again.
(2) Inal can modify the method, and final modified method cannot be rewritten.
(3) Inal can modify classes, and classes modified by final cannot derive subclasses.
The specified initial value that the variable modified by final must be displayed:
For instance variables that are final modified, the initial value can only be assigned at the following three specified positions.
(1) Specify the initial value when defining the final instance variable.
(2) Specify the initial value for the final instance variable in a non-static block.
(3) Specify the initial value for the final instance variable in the constructor.
They will eventually be mentioned in the constructor for initialization.
For class variables specified with final: initial values can only be assigned in two specified places.
(1) Specify the initial value when defining the final class variable.
(2) Specify the initial value for the final class variable in a static block.
Also processed by the compiler, unlike instance variables, class variables are all mentioned to assign initial values in static blocks, while instance variables are mentioned to constructors.
There is another feature of class variables modified by final, which is "macro replacement". When the modified class variable satisfies the initial value when defining the variable, the initial value can be determined during compilation (for example: 18, "aaaa", 16.78 and other direct quantities), then the class variable modified by final is not a variable, and the system will treat it as a "macro variable" (which is what we often call a constant). If the initial value can be determined during compilation, it will not be mentioned in the static block for initialization, and the initial value will be directly replaced by the final variable in the class definition. Let's give an example of age minus year:
package com.zlc.array; class TestStatic { //Class Member DEMO TestStatic Instance Final static TestStatic DEMO = new TestStatic(15); //Class Member Age final static int age = 20; //Instance variable curAge int curAge; public TestStatic(int years) { // TODO Auto-generated constructor stub curAge = age - years; }} public class Test{ public static void main(String[] args) { System.out.println(TestStatic.DEMO.curAge); TestStatic static1 = new TestStatic(15); System.out.println(static1.curAge); }} At this time, the age is modified by final, so when compiling, all the ages in the parent class become 20, not a variable, so that the output result can meet our expectations.
Especially when comparing strings, it can be displayed more
package com.zlc.array; public class TestString { static String static_name1 = "java"; static String static_name2 = "me"; static String static statci_name3 = static_name1+static_name2; final static String final_static_name1 = "java"; final static String final_static_name2 = "me"; //Add final or not, it can be replaced by a macro in the front. Final String final_statci_name3 = final_static_name1+final_static_name2; public static void main(String[] args) { String name1 = "java"; String name2 = "me"; String name3 = name1+name2; //(1) System.out.println(name3 == "javame"); //(2) System.out.println(TestString.statci_name3 == "javame"); //(3) System.out.println(TestString.final_statci_name3 == "javame"); }} There is nothing to say about using final modification methods and classes, just that one cannot be rewritten by subclasses (like private), and the other cannot derive subclasses.
When modifying local variables with final, Java requires that local variables accessed by internal classes are modified with final. There is a reason. For ordinary local variables, their scope remains within the method. When the method ends, the local variable disappears, but the internal class may generate an implicit "closure", which causes the local variable to remain separated from the method where it is located.
Sometimes, a thread will be new in a method and then the local variable of the method is called. At this time, the change variable needs to be declared as final modified.
5. Calculation method of object occupancy memory
Use the freeMemory(), totalMemory(), and maxMemory() methods in the java.lang.Runtime classes to measure the size of a Java object. This method is usually used when a lot of resources need to be precisely determined. This method is almost useless to implement production system cache. The advantage of this method is that the data type is independent of the size, and different operating systems can obtain the memory occupied.
It uses the reflection API to traverse the hierarchy of member variables of an object and calculate the size of all original variables. This approach does not require so many resources and can be used for cached implementations. The disadvantage is that the original type size is different and different JVM implementations have different calculation methods.
After JDK5.0, the Instrumentation API provides the getObjectSize method to calculate the memory size occupied by the object.
By default, the size of the referenced object is not calculated. In order to calculate the referenced object, you can use reflection to obtain it. The following method is an implementation provided in the above article that calculates the size of the reference object:
public class SizeOfAgent { static Instrumentation inst; /** initializes agent */ public static void premain(String agentArgs, Instrumentation instP) { inst = instP; } /** * Returns object size without member sub-objects. * @param o object to get size of * @return object size */ public static long sizeOf(Object o) { if(inst == null) { throw new IllegalStateException("Can not access instrumentation environment./n" + "Please check if jar file containing SizeOfAgent class is /n" + "specified in the java's /"-javaagent/" command line argument."); } return inst.getObjectSize(o); } /** * Calculates full size of object iterating over * its hierarchy graph. * @param object to calculate size of * @return object size */ public static long fullSizeOf(Object obj) { Map<Object, Object> visited = new IdentityHashMap<Object, Object>(); Stack<Object> stack = new Stack<Object>(); long result = internalSizeOf(obj, stack, visited); while (!stack.isEmpty()) { result += internalSizeOf(stack.pop(), stack, visited); } visited.clear(); return result; } private static boolean skipObject(Object obj, Map<Object, Object> visited) { if (obj instanceof String) { // skip interned string if (obj == ((String) obj).intern()) { return true; } } return (obj == null) // skip visited object || visited.containsKey(obj); } private static long internalSizeOf(Object obj, Stack<Object> stack, Map<Object, Object> visited) { if (skipObject(obj, visited)){ return 0; } visited.put(obj, null); long result = 0; // get size of object + primitive variables + member points result += SizeOfAgent.sizeOf(obj); // process all array elements Class clazz = obj.getClass(); if (clazz.isArray()) { if(clazz.getName().length() != 2) {// skip primitive type array int length = Array.getLength(obj); for (int i = 0; i < length; i++) { stack.add(Array.get(obj, i)); } } return result; } // process all fields of the object while (clazz != null) { Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { if (!Modifier.isStatic(fields[i].getModifiers())) { if (fields[i].getType().isPrimitive())) { continue; // skip primitive fields } else { fields[i].setAccessible(true); try { // objects to be estimated are put to stack Object objectToAdd = fields[i].get(obj); if (objectToAdd != null) { stack.add(objectToAdd); } } catch (IllegalAccessException ex) { assert false; } } } } } clazz = clazz.getSuperclass(); } return result; }}