涉及关键词:
虚拟机运行时内存 java内存划分 类加载顺序  类加载时机  类加载步骤  对象初始化顺序  构造代码块顺序 构造方法 顺序 内存区域   java内存图  堆 方法区 虚拟机栈 本地方法栈 程序计数器  局部变量表   栈帧  java堆 运行时常量池   直接内存
 
 本文从三个部分理解java的初始化

1).java虚拟机运行时的内存区域

2).类的加载过程

3).初始化过程

 

java虚拟机运行时内存空间区域分配

 
 
虚拟机栈中每个方法执行都会创建栈帧,每个栈帧中有局部变量表
方法区中有运行时常量
 

 
线程私有的,也就是每个线程都需要程序计数器 
 
 

java虚拟机栈 也是线程私有的
虚拟机栈描述的是java方法执行的内存模型,每个方法执行的同时都会创建栈帧
用于存储局部变量表/操作数栈/动态链接/方法出口等信息
一般所说的栈就是指的这里

本地方法栈跟虚拟机栈类似
只不过是运行的本地方法,虚拟机实现中有的直接把方法合二为一

可以右键新标签页面打开看大图


java堆是java虚拟机管理的最大一块内存,所有线程共享
启动时创建
唯一目的就是存放对象实例
几乎所有的对象实例都是在这里分配内存
垃圾回收的主要管理区域

 
 
 
 


方法区是与堆一样的线程共享的
存储被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据

 
 
 

 

运行时常量池是方法区的一部分
Class文件中有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用
这部分内容类加载之后进入方法区的运行时常量池中存放

 

 
 不是虚拟机运行的内存区域
 
 
 
 

类的加载

java代码编译成class文件之后,就形成了类的信息-类的二进制字节流
想要使用,肯定要加载

生命周期

加载/验证/准备/初始化/卸载 5个阶段顺序是确定的
解析不确定,可能在初始化阶段之后,为了支持java的运行时绑定

加载时机

1) new关键字实例化对象/读取或者配置类的静态字段/调用类的静态方法
2) java.lang.reflect 包的方法对类进行反射调用 如果没有初始化 触发
3) 初始化类的时候,发现父类没有初始化 触发父类初始化
4)虚拟机启动需要指定一个main方法的主类 先初始化这个类

加载

而且,对于非数组类的加载阶段,准确的说是加载阶段中获取类的二进制字节流的动作行为
是多样性的
可以使用系统提供的引导类加载器
也可以用户自定义的类加载器
开发人员可以通过定义自己的类加载器去控制字节流的获取方式(重写类加载器的loadCalss()方法)

数组类不同
数组类本身不通过类加载器创建 由java虚拟机直接创建
但是数组的元素类型 最终是靠类加载器去创建的

验证
确保Class文件的字节流中包含的信息符合当前虚拟机要求
并且不会危害虚拟机
因为字节码文件可以随便编写,由其他语言编译出来,或者直接十六进制编辑器直接书写
所以需要校验

文件格式校验
是否符合Class文件格式的规范
魔数/主次版本号/编码/文件完整性....相当于是格式上的硬校验

元数据校验
字节码描述的信息进行语义分析,确保其描述的信息符合语言规范要求
比如是否有父类 是否继承了不允许被继承的类等等
针对字节码描述的信息

字节码验证
通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的
第二阶段对元数据信息中的数据类型做完校验后,这个阶段将对类的方法体进行校验分析
保证被校验类的方法在运行时不会做出危害虚拟机安全的事情

符号引用验证
虚拟机将符号引用转化为直接引用的时候 解析过程中发生
符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)

准备

正式为类变量分配内存并设置类变量初始值的阶段
这些变量所使用的内存都将在方法区中进行分配
注意不包括实例变量 实例变量将会在对象实例化时随着对象一起分配在java堆中
基本数据类型的初始化

通常情况下是这样,如果是常量
public static final int value = 123; 准备阶段就会设置

解析

虚拟机将常量池内的符号引用替换为直接引用的过程
符号引用:
一组符号来描述所引用的目标,说白了是逻辑意义上的
符号可以是任何形式的字面量,只要能无歧义的定位到目标即可
符号引用与虚拟机实现的内存布局无关,引用的目标不一定加载到内存中
虽然各种虚拟机实现不同
但是能够接受的符号引用必须是一致的

直接引用:
可以是直接指向目标的指针,相对偏移量或者一个能间接定位到目标的句柄
也就是物理上的,能够真的定位到指定的内存区域
跟虚拟机的实现的内存布局相关的
同一个符号引用不同虚拟机可能有不同的直接引用,而且一般是不同的

CONSTANT_Class_info
CONSTANT_Fieldref_info
CONSTANT_Methodref_info
CONSTANT_InterfaceMethodref_info
CONSTANT_MethodType_info
CONSTANT_MethodHandle_info
CONSTANT_InvokeDynamic_info

 

初始化

在接下来是初始化,初始化也属于类加载的一步,不过这一步骤是程序员最关心的,单独拿出来说

类加载过程的最后一步,到了这个阶段才真正开始执行类中定义的Java程序代码(或者说是字节码)

初始化阶段是执行类构造器 <clinit>()方法的过程

所有的-->  类变量静态语句块

<clinit>() 对于类或者接口并不是必须的,如果一个类没有静态语句块
也没有对变量的赋值操作
编译器可以不为这个类生成<clinit>()方法

接口中不能使用静态语句块 但是仍然有变量初始化,所以接口与类一样,也会生成这个方法
但是与类不同的是,不需要先执行父接口的<clinit>()方法 
只有当父接口定义的变量使用时,父接口才会初始化

虚拟机会保证一个类的<clinit>()方法在多线程环境中能够被正确的加锁同步

 

从加载到对象初始化全过程

一加载时机

1) new关键字实例化对象/读取或者配置类的静态字段/调用类的静态方法
2) java.lang.reflect 包的方法对类进行反射调用 如果没有初始化 触发
3) 初始化类的时候,发现父类没有初始化 触发父类初始化
4)虚拟机启动需要指定一个main方法的主类 先初始化这个类

也就是在上面这些情况的时候 会发生类的加载

满足加载时机之后,然后经过类加载的几个过程

ps:  对于下面的初始化      同级别也就是相同优先级的变量的顺序是按照代码书写顺序来的

二初始化静态(类变量)

然后就是准备阶段中:

类变量 也就是static变量分配内存 并且 初始化数据默认值  注意实例对象的变量此时没有操作

另外如果是final 修饰的常量,此时一并直接赋值

三 构造器方法  <clinit>()

此时所有类的构造器方法执行

而且父类早于子类,所以最早执行的肯定是Object的

此方法把所有类的静态变量也就是类变量的赋值动作执行结束,而且静态代码块也已经执行结束,而且顺序是父类早于子类

也就是说至此,所有的静态变量都已经分配内存空间,也都是已经设置好的值了,包括父类的所有静态变量

静态变量以及静态代码块的执行都是在这里,显然他们是早于构造方法的执行的

但是如果静态变量赋值或者代码块中赋值中使用到了其他的方法,那么这个方法将会提早执行

如果使用new 构建了对象,不仅仅是构造方法,实例化的步骤都会执行的

而且如果是构造方法,那么new对象实例化的时候还会再次执行

输出结果:

1).main方法所在的类会被加载,所以会加载In这个类,

2).然后会处理static类型的变量,以及static代码块

3).这个变量赋值使用了new 所以会调用构造方法,如果是调用其他方法,一样先执行

静态变量和静态代码块优先级相同,代码块在下面,所以先打印了  ""构造方法""   然后打印了""静态方法""

4).此时类加载结束了进入主函数,主函数中先打印了分隔符"-----------------"

5).然后new对象又调用了构造方法,看起来怪怪的,其实逻辑很清晰

四  对象实例化

只有需要产生对象的时候才会有对象实例化,仅仅是加载类的话,上面的前三步就结束了

而且虽然说是一般最后但是也不一定,比如上面提到的如果静态变量调用new 就会提前触发

1.在堆上分配对象足够的内存空间

2.然后就是空间擦除清零,也即是设置为默认值

3.然后按照实例字段定义的顺序,顺序执行赋值初始化   初始化代码块 和直接定义变量的初始化 优先级别一样 按照定义顺序进行先后

4.实例的构造方法调用

对象的实例化是一个整体的,调用了new 就会按部就班的执行这些步骤

补充说明:

  1. 静态的初始化仅仅是类加载的时候发生,仅仅发生一次,类的加载时机看上面的---加载时机
  2. 静态变量也就是类变量都有默认的初始化值的,局部变量都没有默认值的,想要使用必须赋值,否则报错
  3. static不能修饰局部变量 
  4. 静态变量不能向前引用,比如先使用了值,接下来才定义
  5. 成员属性值的初始化方式:并不是只能定义的时候赋值的

    类内声明时直接赋值
    构造方法 -----如果构造方法中只是初始化了部分属性值的话,其他的值还是默认值的
    调用成员方法进行初始化(方法可以有参数,不过参数必须是已经初始化了的)
    初始化块---只要构造对象,初始化块就会执行的,而且早于构造方法

   6.每个类都有默认的构造方法,如果你不定义他永远有一个默认的,如果定义了,默认的就不存在了,当你还需要new 对象(  ) 这种形式的话就不行了,按需添加

  7.数组的初始化定义的是一个引用,需要显式的初始化,否则引用为null,数组类型和普通的类加载是不一样的

  8.相同优先级别的根据定义的顺序决定初始化顺序;不同的优先级别的,不管你怎么写,优先级别高的始终会早于优先级低的

    比如静态的你写到构造方法下面还是静态的先执行;(特殊情况是上面提到的static变量用new 对象赋值)

    初始化代码块总会早于构造方法的执行

  9.继承结构中除非有特殊情况,否则顺序一般都是下面这样子的

    先执行静态的初始化
    所有的静态初始化结束
    执行最顶级初始化块
    执行最顶级构造方法

    ......
    执行子类初始化块
    执行子类构造方法

  10,如果对象中有其他类的成员变量,这个变量的静态,初始化块,构造方法的顺序(他们三个是一起的不分割的),跟这个类本身的初始化块的优先级是一样的,按照定义的顺序

  

      

      比如  Test 中有T1   T1的静态初始化块,初始化块,构造方法是一起的,然后他们和Test的初始化块的顺序是不固定的

      

好了,把这些点都记住的话,基本上就可以彻底理清楚了

初始化的过程是很复杂的,所以要掌握好优先级和规则

否则 包含的变量又有很多父类 等 各个类里面调用各种方法初始化就会让人彻底懵逼了

java 虚拟机内存划分,类加载过程以及对象的初始化的更多相关文章

  1. (转)java 虚拟机内存划分

    深入理解java虚拟机(一):java内存区域(内存结构划分)深入理解java虚拟机(二):java内存溢出实战  深入理解java虚拟机(三):String.intern()-字符串常量池深入理解j ...

  2. Java虚拟机内存划分

    Java虚拟机在执行Java程序时,会把它管理的内存划分为若干个不同的数据区.这些区域有不同的特性,起不同的作用.它们有各自的创建时间,销毁时间.有的区域随着进程的启动而创建,随着进程结束而销毁,有的 ...

  3. 重读《深入理解Java虚拟机》一、Java虚拟机内存区域的划分

    一.Java虚拟机内存区域如何划分 1.Java虚拟机内存区域的划分 区域名称 作用(用途) 类型 特点 虚拟机规定异常情况 内存分配与回收 其他说明 1 程序计数器 指示当前正在执行的字节码指令地址 ...

  4. Java虚拟机内存管理机制

    自动内存管理机制 Java虚拟机(JVM)在执行Java程序过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区 ...

  5. Java虚拟机-内存tips

    java虚拟机内存可以分为独占区和共享区. 独占区:虚拟内存栈.本地方法栈.程序计数器. 共享区:方法区.Java堆(用来存放对象实例). 程序计数器 比较小的内存空间,当前线程所执行的字节码的行号指 ...

  6. JVM基础系列第6讲:Java 虚拟机内存结构

    看到这里,我相信大家对于一个 Java 源文件是如何变成字节码文件,以及字节码文件的含义已经非常清楚了.那么接下来就是让 Java 虚拟机运行字节码文件,从而得出我们最终想要的结果了.在这个过程中,J ...

  7. 总结Java虚拟机内存区域模型

    本篇文章主要来总结一下Java虚拟机内存的各个区域,以及这些区域的作用.服务对象以及其中可能产生的问题,作为大家的面试宝典. 首先我们来看一下Java运行时的数据区域,Java虚拟机在执行Java程序 ...

  8. 一文解析总结Java虚拟机内存区域模型

    最近抽空看了一点<深入理解Java虚拟机>,本篇文章主要来总结一下Java虚拟机内存的各个区域,以及这些区域的作用.服务对象以及其中可能产生的问题,作为大家的面试宝典. 首先我们来看一下J ...

  9. Java虚拟机内存模型及垃圾回收监控调优

    Java虚拟机内存模型及垃圾回收监控调优 如果你想理解Java垃圾回收如果工作,那么理解JVM的内存模型就显的非常重要.今天我们就来看看JVM内存的各不同部分及如果监控和实现垃圾回收调优. JVM内存 ...

随机推荐

  1. ubuntu 14.04 rabbitmq集群部署

    1.准备机器,我这边准备的是三台ubuntu14.04 机器主机名不能相同,不然节点冲突 2.安装rabbitmq 3.修改hosts文件 root@abc-web-04:~# vim /etc/ho ...

  2. 面试题:int和Integer的区别

    java底层源码:  -128  127之间

  3. Linux用命令启动程序(eclipse、IDEA等)

    打开根目录用管理员权限打开HOME 找到下图截图中的框选出的文件 用文本编辑器打开后 在文件末尾添加所需要打开的应用文件所在的目录 这里以本人的IDEA和eclipse为例:

  4. vue常用属性

    关键词:filters  | 自定义过滤器 (首字母大写) <p>{{ msg | capitalize }}</p> filters: { capitalize: funct ...

  5. 2.9 linux学习(1)

    2019-2-9 16:07:44 学一下Linux,多学一点东西 新认识个老师,超哥 很牛逼感觉! https://www.cnblogs.com/pyyu/p/9276851.html 这是入门参 ...

  6. promise的理解

    为什么会有promise,他的作用是什么? promise主要是为了解决js中多个异步回调难以维护和控制的问题. 什么是promise? 从图中,我们可以看出,Promise是一个函数,这个函数上有在 ...

  7. python从入门到实践-11章测试模块(测试函数出问题)

    #!/user/bin/env python# -*- coding:utf-8 -*- # 用python中unittes中工具来测试代码 # 1.测试函数import unittestfrom n ...

  8. 浅谈开发中python通过os模块存储数据

    #其实本人很烦发博客,但为了面试还是发一下好,证明一下自己的能力 前言 首先说一下适用环境,在开发中我们有一些经常用到的数据(数据量大)需要存储起来. 存sql嘛又不合适,要知道在开发中每条sql语句 ...

  9. 操作系统中 heap 和 stack 的区别

    操作系统中 heap 和 stack 的区别heap 和 stack是什么堆栈是两种数据结构.堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除.==在单片机 ...

  10. Java线程池核心原理剖析

    在系统开发时,我们经常会遇到“池”的概念.使用池一种以空间换时间的做法,通常在内存中事先保存一系列整装待命的对象,以供后期供其他对象随时调用.常见的池有:数据库连接池,socket连接池,线程池等.今 ...