博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从内存分配分析程序初始化和存储
阅读量:5172 次
发布时间:2019-06-13

本文共 4098 字,大约阅读时间需要 13 分钟。

从内存分配分析程序初始化和存储

一.类中各成员的执行顺序

属性、方法、构造方法和自由块都是类中的成员,在创建类的对象时,类中各成员的执行顺序:

1.父类静态成员和静态初始化块,按在代码中出现的顺序依次执行。
2.子类静态成员和静态初始化块,按在代码中出现的顺序依次执行。
3. 父类的实例成员普通类变量和实例初始化块,按在代码中出现的顺序依次执行。
4.执行父类的构造方法。
5.子类实例成员和实例初始化块,按在代码中出现的顺序依次执行。
6.执行子类的构造方法。

注意:程序启动需要触发main方法的时候,虚拟机会先触发这个类的初始化

使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、JIT时放入常量池的静态字段除外)、调用一个类的静态方法,会触发初始化

当初始化一个类的时候,如果其父类没有初始化,则需要先触发其父类的初始化

 

public class InitialOrderTest {    /**     * Description     *      * @param args     */    public static void main(String[] args) {        System.out.println("Parent.str4:"+Parent.str4);        Son s = new Son();        System.out.println("s.str5:"+s.str5);    }}class Parent {    {        String str1 = "parent构造块中的变量";//        System.out.println("parent中的构造块");    }    String str2 = "parent类变量";//    static {        String str3 = "parent构造块中的变量";//        System.out.println("parent中static初始化块");    }    static String str4 = "parent类静态变量";//    public Parent() {        System.out.println("parent构造方法");    }}class Son extends Parent {    {        System.out.println("son中的初始化块");    }    String str5 = "son类变量";//    static String str6 = "son类静态变量";//    static {        String str7 = "son类变量";//        System.out.println("son中的static初始化块");    }    public Son() {        System.out.println("son构造方法");    }} 输出: parent中static初始化块 Parent.str4:parent类静态变量 son中的static初始化块 parent中的构造块 parent构造方法 son中的初始化块 son构造方法 s.str5:son类变量

 

二.类初始化顺序的JVM解释

类初始化顺序受到JVM类加载机制的控制,类加载机制包括加载、验证、准备、解析、初始化等步骤。不管是在继承还是非继承关系中,类的初始化顺序主要受到JVM类加载时机、解析和clinit()初始化规则的影响。

加载时机

加载是类加载机制的第一个阶段,只有在5种主动引用的情况下,才会触发类的加载,而在其他被动引用的情况下并不会触发类的加载。关于类加载时机和5中主动引用和被动引用详见。其中3种主动引用的形式为:

程序启动需要触发main方法的时候,虚拟机会先触发这个类的初始化

使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、JIT时放入常量池的静态字段除外)、调用一个类的静态方法,会触发初始化

当初始化一个类的时候,如果其父类没有初始化,则需要先触发其父类的初始化

 

代码1中触发main()方法前,需要触发主类InitialOrderWithoutExtend的初始化,主类初始化触发后,对静态代码区和静态成员进行初始化后,打印”第1个主类对象:”,之后遇到newInitialOrderWithoutExtend ts = new InitialOrderWithoutExtend();,再进行其他普通变量的初始化。

代码2是继承关系,在子类初始化前,必须先触发父类的初始化。

类解析在继承关系中的自下而上递归

类加载机制的解析阶段将常量池中的符号引用替换为直接引用,主要针对的是类或者接口、字段、类方法、方法类型、方法句柄和调用点限定符7类符号引用。关于类的解析过程详见

而在字段解析、类方法解析、方法类型解析中,均遵循继承关系中自下而上递归搜索解析的规则,由于递归的特性(即数据结构中栈的“后进先出”),初始化的过程则是由上而下、从父类到子类的初始化顺序。

初始化clinit()方法

初始化阶段是执行类构造器方法clinit() 的过程。clinit() 是编译器自动收集类中所有类变量(静态变量)的赋值动作和静态语句块合并生成的。编译器收集的顺序是由语句在源文件中出现的顺序决定的。JVM会保证在子类的clinit() 方法执行之前,父类的clinit() 方法已经执行完毕。

因此所有的初始化过程中clinit()方法,保证了静态变量和静态语句块总是最先初始化的,并且一定是先执行父类clinit(),在执行子类的clinit()。

代码顺序与对象内存布局

在前面的分析中我们看到,类的初始化具有相对固定的顺序:静态代码区和静态变量先于非静态代码区和普通成员,先于构造函数。在相同级别的初始化过程中,初始化顺序与变量定义在程序的中顺序是一致的。

而代码顺序在对象内存布局中同样有影响。(关于JVM对象内存布局详见。)

HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。而实例数据是对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。

无论是从父类继承还是子类定义的,都需要记录下来,这部分的存储顺序JVM参数和字段在程序源码中定义顺序的影响。HotSpot虚拟机默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oop,从分配策略中可以看出,相同宽度的字段总是分配到一起。满足这个条件的前提下,父类中定义的变量会出现在子类之前。不过,如果启用了JVM参数CompactFields(默认为true,启用),那么子类中较窄的变量也可能会插入到父类变量的空隙中。

 

静态代码声明的变量存储在哪里?为什么不能声明静态变量?

构造代码块声明的变量存储在哪里?为什么不能声明静态变量?

Str1与str2的区别

Str3与str4的区别

 

执行顺序:(优先级从高到低)静态代码块>main方法>构造代码块>构造方法。

普通代码块:在方法或语句中出现的{}就称为普通代码块。普通代码块和一般的语句执行顺序由他们在代码中出现的次序决定--“先出现先执行”

构造块:直接在类中定义且没有加static关键字的代码块称为{}构造代码块。构造代码块在创建对象时被调用,每次创建对象都会被调用,并且构造代码块的执行次序优先于类构造函数。

 

静态代码块:在java中使用static关键字声明的代码块。静态块用于初始化类,为类的属性初始化。每个静态代码块只会执行一次。由于JVM在加载类时会执行静态代码块,所以静态代码块先于主方法执行。//如果类中包含多个静态代码块,那么将按照"先定义的代码先执行,后定义的代码后执行"。

注意:1 静态代码块不能存在于任何方法体内。

2 静态代码块不能直接访问静态实例变量和实例方法,需要通过类的实例对象来访问。

JVM将内存划分为6个部分:PC寄存器(也叫程序计数器)、虚拟机栈、堆、方法区、运行时常量池、本地方法栈

PC寄存器(程序计数器):用于记录当前线程运行时的位置,每一个线程都有一个独立的程序计数器,线程的阻塞、恢复、挂起等一系列操作都需要程序计数器的参与,因此必须是线程私有的。

java 虚拟机栈:在创建线程时创建的,用来存储栈帧,因此也是线程私有的。java程序中的方法在执行时,会创建一个栈帧,用于存储方法运行时的临时数据和中间结果,包括局部变量表、操作数栈、动态链接、方法出口等信息。这些栈帧就存储在栈中。如果栈深度大于虚拟机允许的最大深度,则抛出StackOverflowError异常。

局部变量表:方法的局部变量列表,在编译时就写入了class文件

操作数栈:int x = 1; 就需要将 1 压入操作数栈,再将 1 赋值给变量x

java 堆java堆被所有线程共享,堆的主要作用就是存储对象。如果堆空间不够,但扩展时又不能申请到足够的内存时,则抛出OutOfMemoryError异常。

方法区:方发区被各个线程共享,用于存储静态变量、运行时常量池等信息。

本地方法栈:本地方法栈的主要作用就是支持native方法,比如在java中调用C/C++

栈中主要存放局部变量。

堆中存放new出来的东西。

static 的变量或者字符串常量 则存在在 data segment(数据区)中;

那么类中方法的话,是存在在 code segment(代码区)中了。

转载于:https://www.cnblogs.com/interfaceone/p/7514329.html

你可能感兴趣的文章
Mac端SVN工具CornerStone详解
查看>>
Opengl es2.0 学习笔记(十)VBO、IBO和FBO
查看>>
KVM通过qemu实现USB重定向
查看>>
2015年7月15日 JS第一课(JS,声明变量,数据类型)
查看>>
poj3683 Priest John's Busiest Day 2011-12-26
查看>>
STM32串口通信(使用C8T6)
查看>>
netty使用(5)client_server一发一回阐释ByteBuffer的使用
查看>>
Eclipse中经常出现的问题解决方案
查看>>
CSS
查看>>
Mysql之左连接右连接内连接——示例
查看>>
孪生素数问题--nyoj26
查看>>
Cheatsheet: 2016 02.01 ~ 02.29
查看>>
万能数据库查询分析器 5.05发布,本人为之撰写的相关技术文章达65篇
查看>>
剑指offer【书】之简历抒写
查看>>
css-a:visited
查看>>
javaScript 中创建json/转换字符串为json
查看>>
JS中for循环里面的闭包问题的原因及解决办法
查看>>
idea安装Scala插件
查看>>
mysql之查询
查看>>
Mybatis小结
查看>>