Java虚拟机内存模型

Java虚拟机内存模型

内存结构篇

一、内存结构简介

image

  • 程序计数器java

    • 当前线程所执行的字节码行号指示器;
    • 分支、循环、跳转等控制;
    • 当执行的是java方法时是正在执行的虚拟机字节码指令的地址;
    • 当执行的是Native(JNI)方法时该指针为空;
    • 没有(out of memory error)OOM。
  • 数据库

    • 生命周期与线程相同;
    • 每一个方法执行时都会建立一个栈帧(stack frame);
    • 用于存储局部变量表、操做数栈、动态连接、方法出口等信息;
    • 每一个方法从调用到执行成的过程对应栈帧在栈中入栈到出栈的过程;
    • 栈深过大会StackOverFlowError;内存不足会OOM;
  • 本地方法栈数组

    • Native method stack,跟栈同样,栈服务于java方法,本地方法栈服务于native 方法(JNI),部分虚拟机是合并的。
  • 缓存

    • 因此线程共享;
    • 存放对象实例;
    • 垃圾回收的主要区域;
    • 物理内存上能够不连续;
    • 会有OOM问题;
  • 方法区安全

    • 线程共享;
    • 用于存储已经被虚拟机加载的类信息,常量、静态变量等;
    • 类的加载、卸载、常量池回收均发生在此;
    • 内存不足会有OOM ;
    • 运行时常量池:方法区一部分,存放编译时期生成的各类字面量和符号引用,具备动态特性,内存不足也会OOM;**运行时常量池时放在方法区(1.8更名放在元空间)
  • 直接内存多线程

    • 与JVM定义内存区域无关,不归JVM管理;
    • 内存不足也会OOM;
    • native库直接分配的堆外内存;不会回收。例如Netty缓冲区,不须要回收,能够反复用。

二、栈和栈帧

栈帧:一个方法对应一个栈帧区域,先进后出FILO,压栈再出栈。运维

操做数栈:是各类运算发生的场所,各类数据在进行运算时都会弹入操做数栈,而后结果会弹出操做数栈。jvm

方法出口:存储的是当前方法执行完毕以后应该返回到上一级方法的位置。布局

image

三、堆内存逻辑分区

image

四、元空间

方法区又称永久代在1.8称为元空间。性能

元空间替换永久代缘由

​ 一、Java7及之前版本的Hotspot中方法区位于永久代中。同时,永久代和堆是相互隔离的,但它们使用的物理内存是连续的。永久代的垃圾收集是和老年代捆绑在一块儿的,所以不管谁满了,都会触发永久代和老年代的垃圾收集。

​ 二、元空间存在于本地内存,意味着只要本地内存足够,它不会出现像永久代中“java.lang.OutOfMemoryError: PermGen space”这种错误。看上图中的方法区,是否是“膨胀”了。默认状况下元空间是能够无限使用本地内存的,但为了避免让它如此膨胀,JVM一样提供了参数来限制它使用的使用。

​ 表面上看是为了不OOM异常。由于一般使用PermSize和MaxPermSize设置永久代的大小就决定了永久代的上限,可是不是总能知道应该设置为多大合适, 若是使用默认值很容易遇到OOM错误。当使用元空间时,能够加载多少类的元数据就再也不由MaxPermSize控制, 而由系统的实际可用空间来控制。

​ 更深层的缘由仍是要合并HotSpot和JRockit的代码,JRockit历来没有所谓的永久代,也不须要开发运维人员设置永久代的大小,可是运行良好。同时也不用担忧运行性能问题了,在覆盖到的测试中, 程序启动和运行速度下降不超过1%,可是这点性能损失换来了更大的安全保障。

总结:

  • 永久代:GC不会再程序运行期间对永久代进行垃圾回收,这会致使OOM。
  • 元数据空间:不存在虚拟机中,而是使用本地内存,大小由系统实际可用内存控制。
元空间配置参数
-XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:若是释放了大量的空间,就适当的下降该值;若是释放了不多的空间,那么在不超过MaxMetaspaceSize(若是设置了的话),适当的提升该值。

-XX:MaxMetaspaceSize,能够为metadata分配的最大空间,默认是没有限制的。

-XX:MinMetaspaceFreeRatio,在GC以后,最小的Metaspace剩余空间容量的百分比。

-XX:MaxMetaspaceFreeRatio,在GC以后,最大的Metaspace剩余空间容量的百分比。

五、常量池

常量池

​ Class能够理解为Class文件的资源仓库.class文件中除了包含类的版本、字段、方法、接口等描述信息,还有一项就是常量池,常量池中用于存放编译期间生成的各类字面变量和符号引用。

​ 八中基本类型中byte、short、integer、long、char等在值小于等于127使用对象池,即不负责建立和管理大于127的对象。

字符串常量池
  1. 字符串的分配和其余的对象分配同样,耗费高昂的时间和空间代价,做为基础数据,大量建立字符串影响程序性能。
  2. JVM为了提升性能和减小开销,在实例化字符串常量时进行了优化。
  • 为字符串开辟一个字符串常量池,相似缓存区。
  • 建立字符串常量是,先检查字符串常量池是否存在该字符串。
  • 存在则返回该字符串的引用,不存在时则实例化该字符串并放入池中(String s=new String("abc");这种方式会新建出字符串,与常量池的不一样)。

例子:

String s1 = "abc"; String s2 = "abc"; String s3 = new String("abc"); String s4 = new String("abc"); s1==s2    s2!=s3    s3!=s4

例题1:String str=new String("abc");建立了多少个对象?

答:建立过程以下:

  1. 在常量池查找"abc"对象,有则返回对应的引用实例,没有则在常量池建立对应实例对象。
  2. 在堆中new一个String("abc")对象。
  3. 将对象地址赋值给str,建立一个引用。

所以,常量池没有"abc"字面量则建立两个对象,不然建立一个对象以及建立一个对象的引用。

例题2:String str=new String("a"+"b");建立了多少个对象?

答:字符串常量池:a、b、ab。

​ 堆:new String("ab")。

​ 引用:str。

合计5个。

六、TLAB

​ TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区。

​ JVM分配对象是优先分配到线程栈上,栈上分配不了的(如对象较大)则直接分配在Old区;若是对象不大,优先分配在栈上的TLAB上。

​ TLAB是在Eden区的专门的内存空间,为了防止在Eden区分配空间的多线程竞争资源,JVM为每一个线程在Eden区上分配的专属内存空间即TLAB。

内存分配和对象布局篇

一、JOL对象内存布局

Java Object Layout 对象的内存布局:即对象在内存中如何分布的。

image

数组对象:markword(8) + classPointer(4) + 数组长度(4) + 实例数据 + 对齐

Ps:压缩指针和压缩普通对象指针:

使用java -XX:+PrintCommandLineFlags -version命令能够看到包含如下信息:

显示: -XX:+UseCompressedClassPointers -XX:+UseCompressedOops,

其中-XX:+UseCompressedClassPointers是使用压缩类指针,原先是8字节,因为8字节=8x8=64位,2^64位寻址空间太大,所以不必使用8字节,所以使用了压缩成4字节的压缩指针,4字节的寻址能力:48=32位,2^32=4G(2^10=1024=1KB 2^20=1M 2^30=1G) 又因为JVM是8字节一寻址,也就是每8字节做为一个单位,所以实际寻址能力4G8=32G,ZGC号称最大可以使用4T的内存,ZGC使用8字节做为ClassPointer 其中有42位为类指针,4位为颜色指针,2^42=4T。

其中-XX:+UseCompressedOops是表示使用压缩对象指针进行寻址,两个指令应该是一对的。

关闭压缩指针-XX:-UseCompressedClassPointers -XX:-UseCompressedOops指令。

二、为新对象分配内存的方式

  • 内存规整:直接移动指针到未被使用的区域(指针碰撞),须要带压缩的GC:Serial、ParNew等带compact的垃圾回收器。
  • 内存不规整:空闲列表维护可用空间,例如:CMS等基于mark-sweep的垃圾回收器。

三、内存分配的线程安全

​ CAS保证,每一个线程在jvm预先分配了内存,称为本地线程分配缓冲区TLAB,并同步锁定。

四、对象的访问定位

​ 栈上的reference数据操做具体堆上的对象有句柄和直接指针两种方式。

句柄方式:jvm堆上划份内存来做为句柄池,引用时引用存储对象的句柄地址,句柄中包含对象实例数据和类型数据的各自具体地址信息。

​ 优势:引用不用修改,比较稳定,对象移动如垃圾回收只要改变句柄值便可。

image

直接指针:引用直接引用对象地址,对象中包含类型数据指针,指向方法区class。

​ 优势:速度快。

image

五、对象的建立过程

  • new 指令,开辟空间(申请、初始化),这里的初始化是半初始化,仍是默认值,例如对象中有变量 int i=8; 此时初始化后i=0 即默认值,因此称为半初始化。
  • invokespecial ,构造方法,赋值(真正的初始化,i=8)。
  • astore, 对象创建关联,栈空间与堆空间创建关联。
  • 初次访问对象

    其中二、3两部会发生指令重排。

六、对象逃逸

JVM的3中运行模式:

解释模式:只使用解释器,执行一行JVM字节码就编译一行为机器码。这样能更快的看到程序的执行效果,可是执行的效果并不必定最快。适合执行一次的代码模块。

编译模式:只使用编译器,先将全部的JVM字节码一次编译为机器码,而后一次性执行全部机器码。这样启动的稍慢,但执行完很快,适合反复执行的代码模块;

混合模式:依然采用解释模式,可是对于一点热点代码采用编译模式,并把对应的字节码缓存起来。JVM通常采用混合模式。

对象逃逸分析:

public User t1(){
    User user = new User();
    user.setId(1);
    user.setName("sz");
    //写入数据库
    return user;
}

public User t2(){
    User user = new User();
    user.setId(1);
    user.setName("sz");
    //写入数据库
}

t1对象会被其余引用,做用范围不明显;t2对象不会被其余线程引用,直接分配到当前线程。

对象逃逸分析的JVM参数: -XX:+DoEscapeAnalysis(开启)-XX:DoEscapeAnalysis(关闭)。

JDK1.7以后默认开启,但若是栈空间不足会分配到堆空间。逃逸分析发生在编译期间。

相关文章
相关标签/搜索