Jvm对象创建和类加载过程

1.对象创建流程是怎样的?有哪些步骤,分别有什么作用?

对象创建流程示意图

        jvm创建对象主要经过类加载检查、分配内存、初始化、设置对象头、执行初始化方法这几个阶段,下面将逐步解析每一步的含义。

类加载检查

        首先第一步是类加载检查,当虚拟机遇到new指令时,首先检查这个指令的参数能否在常量池中定位一个类的符号引用,并检查这个类是否已经加载、解析、初始化过,如果没有,就执行类加载流程

分配内存

        实际上为对象分配内存就是从堆中分割一块空间给当前对象。

        在第二步分配内存时,我们要关心两个问题:

        1.内存分配的方式是怎样的?

        2.并发场景下,jvm怎么保证内存分配的原子性?

      jvm内存分配方式有如下两种:

        1.指针碰撞(Bump the Pointer):假如java堆中的内存都是规整的,已使用的内存在一边,未使用的内存在另一边,中间用指针进行分割,当要给对象分配内存时,只需要将指针按对象大小移动到未使用内存那边,那么内存就分配完毕。

        2.空闲列表(Free List):假如java堆中的内存,已使用的和未使用的内存交织在一起,那么虚拟机就要维护一个可用内存列表,当要分配内存时,就查询这个列表上的可用内存进行分配。

        解决并发分配内存:

        1.虚拟机使用了CAS(compare and swap)配上失败重试的方式来保证更新操作的原子性

        2.本地线程分配缓冲(Thread Local Allocation Buffer,TLAB):将内存分配的操作按线程划分在不同的空间中进行,即每个线程都预先从堆中分配到一块独有的内存空间,当本地线程的缓冲空间使用完,才会使用CAS去Eden区竞争内存。虚拟机是否使用TLAB,可以通过-XX:+UseTLAB这个参数来配置。

初始化

        这里就是给对象的字段进行初始化零值,如果使用了TLAB,也可以提前到TLAB中进行。

设置对象头

        对象在jvm中是有三部分组成,分别是对象头(Object Header)、实例数据(Instance Data)、对齐填充(Padding)。

        在这一步主要是设置对象运行时的数据,比如哈希码、GC分代年龄、锁状态标志位、线程持有的锁、偏向线程ID、偏向时间戳等。对象头还有一部分是类型指针(klass point),指向的是类的元数据信息,也就是当前对象是哪个类的实例,如果是数组还需要设置数组的长度。

执行<init>()方法

        在这一步是按照我们开发人员指定的值给属性进行初始化,执行完这个方法后,对象才是真正的创建完成。

2.类加载流程是怎样的?

        我们编写好的代码,通常需要编译成class文件,然后加载到内存中才能被虚拟机识别和使用。将一个class文件加载到内存中,可以从两方面进行了解,首先就是类加载的过程,其次就是jvm的类加载器,那么下面将从这两方面进行详细的介绍。

    2.1 类加载的流程

类加载流程示意图

        类加载主要有加载、验证、准备、解析、初始化、使用、卸载这七个步骤。

        加载

        在类加载阶段会完成三件事:

        1.通过类的全限定名,来获取这个类的二进制字节流

        2.将读取到的字节流存放到运行时数据区的方法区

        3.在堆中生成一个对应这个类的对象,用于访问方法区这个类的入口通俗点说,就是将class文件存放到方法区中,然后在堆里面生成一个该类的java.lang.Class对象,用来访问类的信息

        验证

        这个阶段主要验证字节码的格式是否符合规范

        准备

        这个阶段主要是给静态变量分配内存、设置初始零值(int类型赋值0)。但是这里有个特殊的地方,就是在给final修饰的字段进行赋值时,是赋予指定的值。

基本类型的默认零值:

基本类型初始零值

// final修饰的静态字段在准备阶段会赋指定的值

public static final int value = 111

        解析

        虚拟机将常量池中的符号引用替换为直接引用。

        符号引用:用一组符号来描述所引用的对象,通过符号可以定位指定的数据。

        直接引用:可以是直接指向对象的指针、内存相对偏移量。

        静态链接:在类加载过程将符号引用替换为直接引用,就叫静态链接。

        动态链接:在程序运行时,将符号引用天魂成直接引用,就叫动态链接。

      初始化

        在当前阶段,则会根据开发人员指定的值去给字段进行赋值,然后执行静态代码块。

        2.2 类加载器

          类加载器的主要作用:通过一个类的全限定名来获取该类的二进制字节流,实现这个过程的就是类加载器(Class Loader)。

          Jvm中有三种类加载,分别是启动类加载器(Bootstrap Class Loader)、扩展类加载器(Extension Class Loader)、应用程序类加载器(Application Class Loader),其中启动类加载器是C++实现,因此启动类加载器不能在java中获取。

        启动类加载器(Bootstrap Class Loader):负责加载\lib目录下的核心类库。

        扩展类加载器(Extension Class Loader):负责加载\lib\ext目录下的扩展类库。

        应用程序类加载器(Application Class Loader):负责加载用户类路径(classpath)下的所有类库。

        java应用程序都是由这三种类加载器相互配合来实现类的加载,它们直接的关系如下图所示:

JVM类加载器

      这种工作模式被称为类加载器双亲委派机制,它们的执行流程是:当某一个类要被加载时,当前类的加载器会先检查自己是否已经加载过这个类,如果没有加载过,就会委派给父类加载器(直到最顶级的类加载器),如果父类加载器加载失败,那么子加载器才会去加载该类。

    双亲委派的好处:

    1.沙箱安全机制,避免核心的类库被篡改,假如我们自己也定义一个String类,那么类加载器是不会加载的。

    2.避免重复加载相同的类。

    类和类加载器的关系:在java中,同一个类被不同的类加载器加载,那么这两个类也不相同。

3.对象内存分配

      对象内存分配主要流程如下图所示:

对象内存分配流程图

        对象栈上分配:

        大多数对象都是分配在堆上,但是如果某些对象经过“逃逸分析”,发现不会发生逃逸现象,该对象则会在栈上进行分配。如果栈上的连续空间不足以放下当前对象,那么jvm会使用“标量替换”,将对象进行拆分后存放栈内。对象分配在栈上,可以随着栈帧的出栈而销毁,减轻垃圾回收的压力。

        逃逸分析:该对象只作用在本方法内,无法被外部访问到,就代表没有逃逸,相反,如果可以被外部访问,则是逃逸。使用XX:+DoEscapeAnalysis(开启逃逸分析)-XX:- DoEscapeAnalysis(关闭逃逸分析)。

        标量替换:将一个聚合量(java中的对象)拆分成若干份,根据程序访问,将用到的成员变量恢复为原始类型,这就是标量替换。使用-XX:+EliminateAllocations(开启标量替换)-XX:-EliminateAllocations(关闭标量替换)。

// test1中user逃逸出test1方法

public User test1() {

User user = new User();

  user.setId(1);

  user.setName("test");

  return user;

}

// test2中user没有逃逸出去

public void test2() {

    User user = new User();

    user.setId(1);

    user.setName("test");

}

        对象Eden区分配:

        大多数情况下,对象都是在Eden区进行分配,如果Eden区内存不足,虚拟机会执行MinorGc。如上图所示,年轻代分为Eden区、Survivor0、Survivor1区,它们之间占比为8:1:1。

      大对象直接进入老年代:

        大对象指的是连续占用大量内存空间的java对象,比如数组。虚拟机提供了-XX:PretenureSizeThreshold参数,可以设置对象达到某个阈值后,直接进入老年代中,这样做的好处是避免了大对象在年轻代中来回的复制。这-XX:PretenureSizeThreshold参数只在Serial和ParNew两款年轻代收集器下有效。

      长期存活的对象进入老年代:

        虚拟机给每个对象的头部都设置了一个Gc分代年龄计数器,每经历过一次Gc,计数器就会+1,默认是达到15次后,就会晋升到老年代中。可以使用-XX:MaxTenuringThreshold=15参数来进行设置。

        对象动态年龄判断:

        一批对象的总大小大于当前Survivor区的50%,那么大于等于这批对象年龄的对象,直接进入老年代中。例如当前Survivor区有一批对象,年龄1+2+3+n大于当前Survivor区的50%,此时就会将大于等于n的对象,直接转移到老年代中。对象动态年龄判断一般在minor Gc执行后触发。

        空间分配担保:

        在执行minor Gc前,虚拟机会先检查老年代剩余可用空间是否大于新生代所有对象的大小,如果大于就执行minor Gc;否则,虚拟机将检查是否配置了担保参数“-XX:HandlePromotionFailure”,如果有担保参数,就会判断老年代剩余可用空间是否大于历次晋升到老年代对象的平均大小,如果不符合就执行full Gc,符合就执行minor Gc。

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

推荐阅读更多精彩内容