Basic methods for resolving object memory allocation and control in Java programs

  • 2020-05-10 18:04:45
  • OfStack

1. Knowledge of object and memory control

1. Initialization process of java variables, including local variables, member variables (instance variables and class variables).
2. In inheritance relationships, when an object is used to reference a variable of a different compile-time type than a run-time type, the properties and methods of that object are accessed differently.
3.final modifier properties.

2. Division and initialization of java variables

Variables of java program can be roughly divided into member variables and local variables, and member variables can be divided into instance variables (non-static variables) and class variables (static variables). Local variables we encounter will appear in the following situations:
(1) formal parameters: local variables defined in the method signature are assigned by the caller and die with the end of the method.
(2) local variables in the method: the initialization (assignment of initial value) of local variables defined in the method must be displayed in the method, which will die when the variable initialization is completed and the method ends.
(3) local variables in the code block: the initialization (assignment of initial value) of local variables defined in the code block must be displayed in the code block, which takes effect with the completion of initialization and dies with the end of the code block.


package com.zlc.array;
 
public class TestField {
  {
    String b ;
    // If it is not initialized, the compiler will declare it The local variable b may not have been initialized
    System.out.println(b);
  }
  public static void main(String[] args) {
    int a ;
    // If it is not initialized, the compiler will declare it The local variable a may not have been initialized
    System.out.println(a);
  }
}

The member variable modified with static is a class variable, which belongs to the class itself. The member variable not modified with static is an instance variable, which belongs to the class. In the same JVM, each class can only correspond to one Class object, but each class can create multiple java objects. (that is to say, a class variable only needs 1 block of memory space, and every time an instance of this class is created, it needs to allocate 1 block of space for the instance variable.)
     
Instance variable initialization: syntactically, a program can initialize an instance variable in three places:
(1) specify the initial value when defining the instance variable.
(2) specify the initial value of the instance variable in the non-static block.
(3) the constructor specifies the initial value of the instance variable.
Both (1) and (2) are initialized earlier in the constructor than (3), and (1) and (2) are initialized according to the order in which they are arranged in the source code.  
 


package com.zlc.array;
 
public class TestField {
  public TestField(int age){
    System.out.println(" Initialization in the constructor  this.age = "+this.age);
    this.age = age;
  }
  {
    System.out.println(" Class in a non-static block ");
    age = 22;
  }
  // Initialization at definition time 
  int age = 15;
  public static void main(String[] args) {
    TestField field = new TestField(24);
    System.out.println(" In the end  age = "+field.age);
  }
}

 

The result is: initialization in a non-static block
Initializes this.age = 15 in the constructor
age = 24
If you know how to use javap, you can see how the javap-c XXXX (class file) changes the java class to compile.
When defining an instance variable, specify the initial value. In the initialization block, specify the initial value for the instance variable. The statement status is equal. The following two steps are performed:
1) int age; When the java object is created, the system allocates memory for the object based on this statement.
2) age = 15; This statement is extracted and executed in the constructor of the java class.
     
Class variable initialization: grammatically, a program can initialize class variables from two places.
(1) specify the initial value when defining class variables.
(2) the initial value of class variable is specified in the static block.
The two execution orders are in the same order as they are in the source code. Let's take an example of a little pervert:


package com.zlc.array;
 
 class TestStatic {
  // Members of the class  DEMO TestStatic The instance 
  final static TestStatic DEMO = new TestStatic(15);
  // Members of the class  age
  static int age = 20;
  // The instance variables  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 has two lines of printing, one is the instance variable of TestStatic class attribute DEMO, the second is the instance attribute of TestStatic via java object staticDemo, which can be inferred according to the initialization process of instance variable and class variable we analyzed above:
1) at the first stage of initialization, memory space is allocated for class variables DEMO and age when the class is loaded. At this point, the default values of DEMO and age are null and 0, respectively.
2) at the second stage of initialization, the program assigns initial values to DEMO and age in order. TestStatic(15) needs to call the constructor of TestStatic. At this point, age = 0, so the printed result is -15.

3. Differences between inherited member variables and inherited member methods in inheritance relationships

When creating any java object, the program always calls the non-static block of the parent class, the parent constructor, and finally the non-static block and constructor of the class. Constructor 1, which calls a parent class through the constructor of a subclass, is generally divided into two cases: one is implicitly called, and one is shown calling the constructor of the parent class through super.
Subclasses method can call the instance variables of the parent, this is because the subclass inherits the parent class will get member variables and methods of the parent class, but the parent class method cannot access the subclass instance variable, because the parent class didn't know which kind of inheritance, it will be a subclass of it will increase what kind of member variables, of course, in some extreme example 1 or parent class can be called subclasses of variables, such as: a subclass overrides the superclass method, 1 will print out the default values, because this time the subclass instance variable has not been initialized.


package com.zlc.array;
class Father{
  int age = 50;
  public Father() {
    // TODO Auto-generated constructor stub
        System.out.println(this.getClass());
        //this.sonMethod(); Can't 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);
  }
    // Subclasses-specific methods 
    public void sonMethod(){
         System.out.println("Son method");
    }
}

According to our normal inference, we implicitly call the constructor of the parent class through the subclass, and call the info() method in the constructor of the parent class (note: I did not say call the parent class here), which is supposed to output the age instance variable of the parent class. The printed result is expected to be 50, but the actual output result is 0.
1) the memory allocation of java object is not completed in the constructor, the constructor just completes the initialization and assignment process, that is, before calling the constructor of the parent class, jvm has classified the memory space of this Son object. This space holds two age attributes, one is age of the subclass, and the other is age of the parent class.
2) in the call new Son (28), the current this object represents the subclass Son object, we can through the object. getClass print () will get class com. zlc. array. Son results, but the current initialization process is done in the constructor of the parent class, through this. sonMethod () could not be called again, This is because the compiled type of this is Father.
3) in the variable compile-time type and run-time type is not at the same time, through this variable to access the reference object instance variable, the instance variable's value is determined by the declaration of the variable type, but to call it through the variable reference object instance methods, the method of behavior is determined by its actual reference object, so it is called a subclass of info method, so the print is a subclass of age, because age haven't come urgent initialization print the default value of 0.
In layman's terms, when the declared type does not correspond to the true new type, the property used is of the parent class and the method called is of the subclass.
Through javap - c more directly to understand why we inherit properties and methods can make a big difference, if we put in the above example, subclass Son info rewrite method take out, this time the call would be the parent class info method, because at the time of compiling will compile the superclass info method transfer into subclasses, and became a member of the variable remains in the parent class will not transfer, so that parents and children have the instance variables of the same name, and if the subclasses override the parent class method of the same name, The subclass method completely overrides the parent method (it's not clear why java was designed this way). Variables with the same name can exist without overwriting, and subclasses of methods with the same name completely override methods with the same name of the parent class.
In general, for a reference variable, when accessing the instance variable of the object it refers to through the variable, the value of the instance variable depends on the type of the object it refers to when declaring the variable, and when accessing the method of the object it refers to through the variable, the method behavior depends on the type of the object it actually refers to.
Finally, take a small case to review:


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();
  }
}

Want to call the superclass method: can be called through super, but super keyword does not refer to any object, it can not be used as a real reference variable, interested friends can study their own.
All of the above are instance variables and methods. Class variables and class methods are much easier to use and use the class name directly.

4. Use of final modifiers (especially macro substitution)

(1) inal can modify the variable, and the variable modified by final cannot be re-assigned after its initial value is assigned.

(2) inal can modify methods, but methods modified by final cannot be overwritten.

(3) inal can modify the class, and the class modified by final cannot derive subclasses.


The specified initial value that the variable modified by final must display:
For instance variables that are modified by final, the initial values can only be assigned in the following three specified locations.
(1) specify the initial value when defining the final instance variable.
(2) specify an 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.
Eventually it will all be referred to the constructor for initialization.
For class variables specified with final: you can only assign an initial value 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 the static block.
Unlike instance variables, which are also handled by the compiler, class variables are referred to as initializers in static blocks, while instance variables are referred to constructors.
Another feature of     class variable modified by final is "macro substitution". When the class variable modified satisfies the initial value specified when the variable is defined, the initial value can be determined at compile time (e.g. : 18, "aaaa", 16.78 1) directly, so the final modified class variable is a variable, the system will as "macro variables" (that is, we often say the constant), if the initial values can be determined at compile time, will not be mention initialized in the static block, directly in the class definition directly give the initial value for final variables. Let's take that age minus year:


package com.zlc.array;
 
 class TestStatic {
  // Members of the class  DEMO TestStatic The instance 
  final static TestStatic DEMO = new TestStatic(15);
  // Members of the class  age
  final static int age = 20;
  // The instance variables  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 point, age is modified by final, so at compile time, all age in the parent class becomes 20 instead of 1 variable, so the output will be what we expect.
This is especially true when comparing strings


package com.zlc.array;
 
public class TestString {
  static String static_name1 = "java";
  static String static_name2 = "me";
  static String statci_name3 = static_name1+static_name2;
   
  final static String final_static_name1 = "java";
  final static String final_static_name2 = "me";
  // add final  Or not   The front can be replaced by a macro 
  final static 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 modifying methods and classes with final, except that one cannot be subclassed (like private1) and one cannot be subclassed.
Local variables, with final Java requirements by inner classes to visit local variables are final decorate, this is a reason, for the ordinary local variables, its scope is to stay in this method, when at the end of the method, the local variables will be gone, but the inner class may produce the implicit "closure", closures makes local variable continues to exist out of his way.
Sometimes a thread of new within a method calls a local variable of that method, and the change is declared as modified by final.

5. Calculation method of object occupying memory
system.gc () and java.lang.Runtime classes freeMemory(),totalMemory(),maxMemory() are used to measure the size of Java objects. This method is often used when many resources need to be precisely sized. This approach is almost useless for production system caching implementations. The advantage of this method is that the data type size is independent, different operating systems, can get occupied memory.

It USES reflection API to traverse the hierarchy of member variables of an object and to calculate the size of all the original variables. This approach does not require so many resources and can be used for caching implementations. The disadvantage is that the original type size is different and different JVM implementations should have different calculation methods.
After JDK 5.0, Instrumentation API provides the getObjectSize method to calculate the memory size of the object.
The size of the reference object is not computed by default. To compute the reference object, you can use reflection fetch. The following method is an implementation of a calculation containing the size of a reference object provided in the above article:


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 obj 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 pointers 
    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;
  }
} 


Related articles: