继上一篇 JVM学习之路1-内存模型 介绍完JVM内存模型以后,这篇准备聊聊对象的内存布局以及逃逸分析。咱们知道对象通常是分配在堆上的,可是你知道对象在堆上是怎么存放的吗?咱们平时程序中在使用的时候是怎么找到对象的?
知识点
一、内存对象布局
二、逃逸分析html
先说一下咱们平时是怎么建立对象的A a = new A();
如上所示,一个对象A就被建立出来了。看似简单的一行语句,其实虚拟机为咱们作了不少事情。
首先虚拟机去常量池中查找是否有类A的符号引用,并检查该符号引用的类A是否已经被虚拟机所加载过,若是没有则先进行加载(具体的类加载机制咱们会在后面的系列文章中介绍),若是已经被加载过则能够肯定为该类对象所分配的内存大小并进行内存分配,大概流程以下:
这里涉及到两种分配方式:指针碰撞和空闲列表java
简单来讲,基于内存规整的前提下将内存分为两部分,用过的内存放到左侧,没用过的内存放到右侧,中间放一个指针来分界,当为对象分配内存时,向空闲区移动一块该对象大小的空间:算法
上面是基于内存规整的状况下进行指针碰撞,若是内存不规整的话,怎么处理?那就要说到空闲列表了,简单来讲就是已分配内存和未分配内存是交错的,虚拟机维护一份列表来知道哪些内存是可用的,在进行对象分配的时候会从列表中取一块足够空间的内存,分配结束后更新列表:
上面两种分配方式哪一种更好呢?这个没有必定哪一个好或者哪一个很差,取决于java的内存是否规整,而java内存是否规整又取决于所用的垃圾收集器是否带有压缩整理法决定,在后面介绍垃圾收集器的时候再具体聊。segmentfault
分配对象的对象的时候不是线程安全的,可能在分配对象A的时候,指针都没来得及修改对象B又拿到原来的指针进行分配,如何避免呢?虚拟机在这方面有两种解决方案:
一、分配对象更新指针的时候使用CAS来保证操做的原子性
二、使用TLAB机制,也就是对于每一个线程都先分配一个缓存进行内存的预分配,每次线程须要分配申请内存的时候都先在该缓存中进行,只有缓存不够的时候再加锁分配新的TLAB(-XX:+/-UseTLAB)。缓存
这里我画了个图,看这个图基本上你们就比较清晰了,我仍是大概解释一下。安全
存放两部分数据:
一、运行时数据,好比哈希码、gc分代年龄、锁状态等。
二、类型指针,用来查找该对应指向的类元数据在哪,经过该类元数据就知道对象属于哪一个类的对象。jvm
就是存放对象的字段内容,举个栗子:
存的就是age和name信息。函数
没啥意义,起到占位符的做用,由于虚拟机分配对象大小是8字节的倍数,为了可以补齐。布局
对象内存分配和对象结构都讲完了,那咱们在方法中是怎么访问到对象的呢?上一篇文章中(jvm学习之路1-内存模型),咱们在介绍jvm内存模型的时候说到了虚拟机栈,在方法调用的时候会产生一个栈帧,栈帧中存放了局部标量信息,就是这个局部变量表里对于对象会有一个reference,该内容存的就是对象的引用,咱们就是经过它来找到对象数据的。而引用又分为两种:
一、对象句柄池,优势是稳,对象地址变了,不影响栈中引用;
二、对象指针,优势是快,数据直接找到,少一步中间商(hotspot方式);
这两个概念比较简单,咱们参照书里说的,直接给两幅图:性能
对象内存布局基本讲完了,回顾一下,咱们上面讲的对象都是在堆上分配的,那是否是全部对象都是在堆上分配的呢?答案固然是否认的,这里就是咱们这部分想要说的逃逸分析技术,他其实就是jvm的一种优化。
正常对象在堆上分配,由gc销毁,可是这样是比较耗性能的,你们都知道对于一个程序来讲,gc次数越少会好,为了尽可能减小gc消耗,逃逸分析技术就诞生了。
网上有位博友这么形容逃逸,用了一段简单直接的代码,我以为挺直截了当的,能够供参考:
stringBuilder是在方法的内部变量,而此时它被直接返回,这样stringBuilder就有可能被其余地方的方法或参数所改变,这样它的做用域就不仅是demo1了,虽然它是一个局部变量,但其发生了“逃逸”。
那么,我能够改一下代码:
如此,就没有返回StringBuilder,而是toString(),那么StringBuilder没有从方法中直接脱离,就没有发生逃逸。因此咱们写代码的时候也要注意利用好这点优化。
逃逸分析,是一种能够有效减小Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。经过逃逸分析,Java Hotspot编译器可以分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
通常状况下,不会逃逸的对象所占空间比较大,若是能使用栈上的空间,那么大量的对象将随方法的结束而销毁,减轻了GC压力
若是你定义的类的方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行。
Java虚拟机中的原始数据类型(int,long等数值类型以及reference类型等)都不能再进一步分解,它们能够称为标量。相对的,若是一个数据能够继续分解,那它称为聚合量,Java中最典型的聚合量是对象。若是逃逸分析证实一个对象不会被外部访问,而且这个对象是可分解的,那程序真正执行的时候将可能不建立这个对象,而改成直接建立它的若干个被这个方法使用到的成员变量来代替。拆散后的变量即可以被单独分析与优化,能够各自分别在栈帧或寄存器上分配空间,本来的对象就无需总体分配空间了。
jdk1.8以上是默认开启的。
开启:-XX:+DoEscapeAnalysis
关闭:-XX:-DoEscapeAnalysis
这篇文章主要介绍了两部份内容:
一、jvm中对象的内存布局,包括内存分配、对象结构组成、如何访问对象。
二、jvm的优化技术-逃逸分析,什么是逃逸分析、逃逸分析的好处、如何使用逃逸分析。
参考资料:
周志明《深刻理解java虚拟机》
http://www.javashuo.com/article/p-tktsyeei-gv.html