简单理解JVM对象创建过程、内存分配机制、内存分配方式、对象头信息、执行init方法

一、JVM对象创建

在Java程序中,对象的创建是通过类加载器来实现的。JVM在创建对象之前,会先加载该对象所属的类,然后再创建对象。

1. 类加载器

Java中的类加载器是用来加载类的,它将类的字节码文件加载到内存中,并生成对应的Class对象。Java中的类加载器有三种:

1)Bootstrap ClassLoader:它是JVM的内置类加载器,用来加载Java的核心类库,如java.lang和java.util等。

2)Extension ClassLoader:它用来加载Java的扩展类库,如javax等。

3)System ClassLoader:它用来加载应用程序的类,如自定义的类等。

2. 类加载过程

类加载的过程可以分为三个步骤:加载、链接和初始化。

1)加载:类加载器首先会从文件系统、网络或其他来源中加载类的字节码文件。在加载过程中,类加载器会生成对应的Class对象,并将其存放在JVM的方法区中。

2)链接:链接分为三个阶段:验证、准备和解析。

  • 验证:验证阶段用来确保类的字节码文件符合JVM规范,并且不包含安全漏洞等问题。

  • 准备:准备阶段用来为类的静态变量分配内存,并设置默认值。

  • 解析:解析阶段用来将类的符号引用转换为直接引用。

3)初始化:初始化阶段用来执行类的静态初始化代码块,并初始化静态变量。在初始化阶段,JVM会按照静态变量的定义顺序执行静态初始化代码块。

3. JVM对象创建时类加载的过程

在JVM对象创建时,类加载器会先加载该对象所属的类,然后再创建对象。具体的过程如下:

1)检查类是否已经被加载,如果没有,就先加载该类。

2)在堆内存中分配一块空间来存放对象。

3)对对象进行初始化,包括设置对象的成员变量和执行构造函数。

4)返回对象引用。

二、内存分配机制

1. JVM内存划分如下图

程序计数器

程序计数器是JVM中的一块较小的内存区域,它用于记录当前线程所执行的字节码的位置。由于Java是一种解释性语言,每条指令都需要翻译成机器码后才能执行,因此程序计数器就是记录正在执行的字节码指令的位置的指针。

Java虚拟机栈

Java虚拟机栈是线程私有的,用于存储方法调用过程中的局部变量、方法参数和返回值等数据。每个方法在执行的时候都会在栈上创建一个栈帧,栈帧中包含了该方法的局部变量表、操作数栈、方法返回地址等信息。随着方法的执行结束,栈帧也会随之出栈销毁。

本地方法栈

本地方法栈与Java虚拟机栈类似,不同的是本地方法栈是为虚拟机使用到的本地(Native)方法服务的。

Java堆

Java堆是Java虚拟机管理的最大一块内存区域,被所有线程共享。Java堆是用来存储对象实例和数组对象的内存区域,由于垃圾回收器会在Java堆中进行垃圾回收,因此Java堆被划分为年轻代和老年代两部分。

年轻代

年轻代是Java堆中的一部分,它又被分为三个部分:Eden空间、Survivor空间From和Survivor空间To。每个对象在创建的时候都会被分配到Eden空间,当Eden空间满了之后,虚拟机会对Eden空间中的存活对象进行一次垃圾回收,将存活的对象复制到Survivor空间From中,并将Eden空间中的所有对象清空。当Survivor空间From也满了之后,虚拟机会对Survivor空间From和Survivor空间To中的存活对象进行一次垃圾回收,将存活的对象复制到Survivor空间To中,并将Survivor空间From中的所有对象清空。当对象在Survivor空间中存活了一定的次数之后,虚拟机会将其晋升到老年代中。

老年代

老年代是Java堆中的一部分,用于存储长时间存活的对象。在进行垃圾回收时,虚拟机会对老年代中的存活对象进行标记-清除、标记-整理等垃圾回收算法。

方法区

方法区也是被所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在JDK8之前,方法区被称为永久代(PermGen),但是永久代容易出现内存溢出的问题,因此在JDK8之后被移除,被元空间(Metaspace)取代。

2. JVM内存分配

在JVM中,对象的内存分配是由Java堆中的内存分配器负责的。Java堆中的内存分配器主要分为两种:Serial收集器和并行收集器。

Serial收集器

Serial收集器是一种单线程的垃圾回收器,它会暂停所有Java线程来进行垃圾回收。Serial收集器主要适用于单核CPU和小内存的环境,它的优点是简单高效,但是不适合大规模的应用场景。

并行收集器

并行收集器是一种多线程的垃圾回收器,它可以利用多核CPU来并发地执行垃圾回收操作,从而减少垃圾回收的暂停时间。并行收集器适用于大规模应用和高并发环境,但是它也有一些缺点,例如垃圾回收期间会消耗大量的CPU资源,可能会导致应用的响应时间变长。

3. 对象的内存分配过程

在JVM中,当需要创建一个新的对象时,虚拟机会首先在Eden空间中查找是否有足够的空间来存放该对象。如果Eden空间中的剩余空间不够,虚拟机会触发一次Minor GC来对Eden空间进行垃圾回收,从而为该对象腾出空间。如果Eden空间中的剩余空间足够,虚拟机会在Eden空间中为该对象分配内存。

当对象在Eden空间中存活了一定的时间后,虚拟机会将其移动到Survivor空间中。当Survivor空间也满了之后,虚拟机会触发一次Minor GC来对Survivor空间进行垃圾回收,从而为对象腾出空间。如果Survivor空间也不足以存放该对象,虚拟机会将该对象直接分配到老年代中。

在老年代中分配内存时,虚拟机会检查老年代中是否有足够的连续空间来存放该对象。如果老年代中的空间足够,虚拟机会为该对象分配内存。如果老年代中的空间不足,虚拟机会触发一次Full GC来对整个堆空间进行垃圾回收,从而为该对象腾出空间。

三、内存分配方式

JVM的内存分配方式有两种:栈内存和堆内存。栈内存是用来存放基本数据类型和对象引用的,而堆内存是用来存放对象的。具体的内存分配方式如下:

1)栈内存:栈内存是一种后进先出的数据结构,用来存放基本数据类型和对象引用。当程序调用一个方法时,JVM会为该方法创建一个栈帧,用来存放方法的参数、局部变量和返回值等信息。当方法执行完毕时,JVM会将该栈帧出栈,释放栈内存。

2)堆内存:堆内存是用来存放对象的。当程序创建对象时,JVM会在堆内存中分配一块空间来存放对象。堆内存的大小可以通过JVM参数进行调整。

对于堆内存还存在两种内存分配方式:指针碰撞和空闲列表

1. 指针碰撞

指针碰撞是一种内存分配方式,它适用于堆内存中的连续空间。在这种情况下,JVM会将堆内存划分为两个区域:一部分用来存放已经分配的对象,另一部分则是未分配的空间。JVM会使用一个指针来记录已经分配的空间的末尾位置,当程序需要创建一个对象时,JVM会检查未分配的空间是否有足够的空间来存放该对象,如果有,JVM会将指针移动到未分配空间的起始位置,并将该对象存放在指针所指向的位置。如果没有足够的空间,JVM会触发一次垃圾回收操作,释放一些已经不再使用的空间,然后再次尝试分配空间。

2. 空闲列表

空闲列表是一种内存分配方式,它适用于堆内存中的非连续空间。在这种情况下,JVM会将堆内存划分为多个大小不同的块,每个块都有一个头部信息,用来记录该块是否已经被分配和块的大小等信息。JVM会使用一个链表来记录所有未被分配的块,当程序需要创建一个对象时,JVM会遍历空闲列表,查找是否有足够大小的块来存放该对象,如果有,JVM会将该块分配给该对象,并将该块从空闲列表中移除。如果没有足够的空间,JVM会触发一次垃圾回收操作,释放一些已经不再使用的空间,然后再次尝试分配空间。

栈内存用来存放基本数据类型和对象引用,堆内存用来存放对象。
指针碰撞适用于堆内存中的连续空间,空闲列表适用于堆内存中的非连续空间。

四、对象头

在JVM中,每个对象都有一个对象头,它是对象在内存中的元数据信息,包括对象的类型、锁状态、GC信息等。


1. JVM对象头的结构

JVM对象头的结构包含两部分:Mark Word和Class Pointer。其中,Mark Word是对象头的核心部分,它占用了8个字节,Class Pointer占用了4个字节。下面分别介绍这两部分的作用和结构。

  1. Mark Word

Mark Word是对象头的核心部分,它包含了对象的类型、锁状态、GC信息等。Mark Word的结构如下:

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
hashcode age biased_lock lock_state identity_hashcode unused CMS_mark CMS_remark CMS_bits reserved thread epoch unused unused forward

其中,各字段的含义如下:

  • hashcode:对象的哈希码,用于快速定位对象在哈希表中的位置。

  • age:对象的年龄,用于标记对象是否经过了一次Minor GC。

  • biased_lock:偏向锁标记,用于标记对象是否处于偏向锁状态。

  • lock_state:锁状态,用于标记对象的锁状态,包括无锁、轻量级锁、重量级锁等。

  • identity_hashcode:对象的标识哈希码,用于在JVM中识别对象。

  • unused:未使用字段。

  • CMS_mark:CMS标记位,用于标记对象是否被CMS GC标记。

  • CMS_remark:CMS重新标记位,用于标记对象是否被CMS GC重新标记。

  • CMS_bits:CMS位图,用于标记对象在CMS GC中的状态。

  • reserved:保留字段。

  • thread:线程ID,用于标记当前持有锁的线程ID。

  • epoch:时间戳,用于标记偏向锁的时间戳。

  • forward:对象是否被移动的标记位。

Mark Word的结构可以根据JVM的版本和配置不同而有所不同,但是大致的结构是相似的。

2. Class Pointer

Class Pointer是对象头的另一部分,它占用了4个字节,用于指向对象的类元数据信息。在JVM中,每个类都有一个Class对象,它包含了类的基本信息,如类名、父类、接口、方法等。当对象被创建时,JVM会将Class对象的指针存储在对象头的Class Pointer中,以便在运行时快速访问对象的类信息。

JVM对象头的作用

JVM对象头是对象在内存中的元数据信息,它包含了对象的类型、锁状态、GC信息等。JVM通过对象头来管理对象的生命周期和状态,包括对象的创建、访问、锁定、垃圾回收等。下面分别介绍JVM对象头的几个重要作用。

  1. 类型标记

JVM对象头中的Mark Word包含了对象的类型信息,用于标记对象的类型。在JVM中,每个对象都有一个类型,它决定了对象的行为和属性。JVM通过对象头中的类型标记来判断对象的类型,以便在运行时调用对象的方法和属性。

  1. 锁状态标记

JVM对象头中的Mark Word包含了对象的锁状态信息,用于标记对象的锁状态。在JVM中,对象可以被多个线程同时访问,为了保证线程安全,JVM通过对象头中的锁状态标记来管理对象的锁状态。在JVM中,锁状态包括无锁、轻量级锁、重量级锁等,不同的锁状态对应不同的锁实现方式。

  1. GC标记

JVM对象头中的Mark Word包含了对象的GC信息,用于标记对象的GC状态。在JVM中,对象的内存分配和回收是由垃圾回收器来管理的,JVM通过对象头中的GC标记来管理对象的GC状态。在JVM中,GC标记包括标记位、位图等,用于标记对象是否需要被垃圾回收器回收。

  1. Class Pointer

JVM对象头中的Class Pointer用于指向对象的类元数据信息,以便在运行时快速访问对象的类信息。在JVM中,每个类都有一个Class对象,它包含了类的基本信息,如类名、父类、接口、方法等。JVM通过对象头中的Class Pointer来访问对象的类信息,以便在运行时调用对象的方法和属性。

JVM对象头是对象在内存中的元数据信息,它包含了对象的类型、锁状态、GC信息等。JVM通过对象头来管理对象的生命周期和状态,包括对象的创建、访问、锁定、垃圾回收等。

五、执行init方法

在Java中,每个类都有一个默认的构造函数,用于初始化对象的成员变量。但是,有时候我们需要在对象创建时执行一些特殊的操作,比如初始化静态变量、读取配置文件等。这时候就需要使用init方法来完成这些操作。

1. 什么是init方法

init方法是一种特殊的Java方法,它的作用是在对象创建后进行初始化工作。init方法的命名规则是以“<init>”开头,后面跟随着一些参数,用于初始化对象的状态。init方法的定义在Java代码中不可见,它是由Java编译器自动产生的。在Java中,每个类都可以定义一个init方法,它必须满足以下条件:

  • init方法必须是非静态方法。

  • init方法的名称可以是任意的,但是通常为init或者initialize。

  • init方法的返回值类型必须为void。

  • init方法不能被重载。

  • init方法的访问权限可以是public、protected或者默认访问权限,但是不能是private。

在Java中,init方法通常用于初始化静态变量、读取配置文件、建立数据库连接等。在Spring框架中,init方法还被用于执行Bean的初始化操作。

2. JVM执行init方法的过程

当JVM执行new关键字创建一个对象时,会分配一块内存空间用于存储对象的实例变量,并初始化对象头信息。在初始化完对象头信息后,JVM会自动调用该对象的init方法进行进一步的初始化工作。

init方法的调用过程是由Java虚拟机完成的,其具体过程如下:

  1. 在堆上分配对象的内存空间,包括对象头和实例变量。

  2. 对象头信息初始化完成后,JVM会将对象的引用作为参数传递给init方法。

  3. JVM会查找对象所属类的init方法。如果该类没有定义init方法,则会向上查找其父类的init方法,直到Object类为止。

  4. JVM在找到init方法后,会将对象引用作为参数传递给init方法,并调用该方法进行对象的进一步初始化工作。

  5. 当init方法执行完毕后,JVM将该对象的引用返回给程序,使得程序可以通过该引用来访问对象。

需要注意的是,JVM只会调用一个对象的init方法一次。如果一个对象被多次创建,它的init方法也只会被调用一次。另外,JVM会确保在调用init方法之前,对象的实例变量已经被正确初始化。

JVM在执行init方法时是单线程的,即同一时间只有一个线程在执行init方法。这是因为对象的初始化操作通常是非常耗时的,如果允许多个线程同时执行init方法,就会导致竞争条件和线程安全问题。

总结:

JVM的对象创建、内存分配机制、内存分配方式、设置对象头、执行init方法是Java程序中非常重要的一部分。了解这些机制可以帮助我们更好地理解Java程序的运行机制,从而更好地编写高质量的Java程序。在实际开发中,我们应该注意对象的创建和内存分配,避免对象的过多创建和内存泄漏等问题,从而提高程序的性能和稳定性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容