java程序运行时的运行模型
在jdk1.8之前的元空间,称为永久代并将元空间挪到了堆直接使用本地内存,不再占用堆空间
jvm内存结构划分
堆(方法区)和元空间是线程共有的,其他部分是线程私有的
每创建一个线程都会创建一个虚拟机栈
程序计数器 的特点及作用
1、程序计数器是一块较小的内存空间,几乎可以忽略;
2、是当前线程所执行的字节码的行号指示器
3、Java 多线程执行时,每条线程都有一个独立的程序计数器,各条线程之间计数器互不影响
4、该区域是“线程私有”的内存,每个线程独立存储
5、该区域不存在 OutOfMemoryError;
6、无GC 回收;
线程执行结束随之销毁
虚拟机栈(也称作线程栈,方法栈)的特点及作用
虚拟机栈包含
1.局部变量表(基本类型、引用类型)
2.操作数栈(压栈和出栈)
3.动态链接(调用其他方法时其他方法的底村地址)
4.返回地址(方法执行的return的结果)
虚拟机栈特点
1、线程私有;
2、方法执行会创建栈帧,存储局部变量表等信息(先进后出)
3、方法执行入虚拟机栈,方法执行完出虚拟机栈;
4、栈深度大于虚拟机所允许 StackOverflowError(比如死递归时);
5、栈需扩展而无法申请空间 OutOfMemoryError (比较少见) ; hotspot 虚拟机没有
6、栈里面运行方法,存放方法的局部变量名,变量名所指向的值 (常量值、对象值等) 都存放到堆上的
7、栈一般都不设置大小,栈所占的空间其实很小,可以通过-Xss1M
进行设置,如果不设置默认为 1M;(如果没有递归或者递归层次很小时也可以考虑设置小一点,这样每个线程占用的栈就会少一些,可以启动更多的线程)
8、随线程而生,随线程而销毁
9、不被GC回收
本地方法栈的特点及作用
1、与虚拟机栈基本类似
2、区别在于本地方法栈为 Native 方法服务
3、HotSpot 虚拟机将虚拟机栈和本地方法栈合并:
4、有 StackOverflowError和 OutOfMemoryError (比较少见)
5、随线程而生,随线程而灭
6、GC 不会回收该区域:
程序计数器、虚拟机栈、本地方法栈 3 个区域随线程而生,随线程而灭、都是不会垃圾回收
JVM 运行时数据区 Java 堆的特点及作用
1、线程共享的一块区域
2、虚拟机启动时创建
3、虚拟机所管理的内存中最大的一块区域:
4、存放所有实例对象或数组
5、GC 垃圾收集器的主要管理区域:
6、可分为新生代和老年代(默认情况下新生代占整个堆的1/3,老年代占2/3)
7、新生代更细化可分为 Eden、From Survivor(S0区)、To Survivor(S1区),Eden:Survivor = 8:1:1
8、可通过-Xmx、-Xms
调节堆大小;
9、无法再扩展java.lang.OutofMemoryError: Java heap space
10、如果从分配内存的角度看,所有线程共享的 Java 堆中可以划分出多个线程私有的分配缓冲区 (Thread Local Allocation Buffer,TLAB,为了解决多线程并发创建对象时内存抢占问题,所以每个线程都有一块私有的TLAB区,TLAB区放满再放入共享区域) ,以提升对象分配时的效率
JVM 中对象如何在堆内存分配
1、指针碰撞 (Bump The Pointer) : 内存规整的情况下通过指针顺序往后移动的方式实现内存划分;
2、空闲列表 (Free List) : 内存不规整的情况下;
选择哪种分配方式由Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有空间压缩整理 (Compact) 的能力决定因此,当使用 Serial、ParNew
等带压缩整理过程的收集器时,系统采用的分配算法是指针碰撞,既简单又高效;
因此,当使用 Serial、ParNew 等带压缩整理过程的收集器时,系统采用的分配算法是指针碰撞,既简单又高效,而当使用 CMS 这种基于清除(Sweep)算法的收集器时,理论上就只能采用较为复杂的空闲
列表来分配内存;
3、本地线程分配缓冲 (Thread Local Allocation Buffer,TLAB) (虚拟机默认情况下是可用的Enable): 对象创建在虚拟机中频繁发生,即使仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象 A分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况;
那么解决方案有两种:
(1) 同步锁定,JVM 是采用 CAS 配上失败重试的方式保证更新操作的原子性
(2)线程隔离,把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定,虚拟机是否使用TLAB,可以通过-XX: +/-UseTLABI
(+表示使用,-表示不使用)参数来设定,-XX:TIABSize=512k
设置缓冲大小,设置为0时自动设置大
小
JVM 堆内存中的对象布局(对象的组成)
在 HotSpot 虚拟机中,一个对象的存储结构分为 3 块区域:
对象头(Header)、:实例数据(Instance Data) 和 对齐填充(Padding):
对象头(Header): 包含两部分,
第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,32 位虚拟机占 32 bit64 位虚拟机占 64 bit,官方称为 “Mark Word’
第二部分是类型指针,即对象指向它的类的元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例,另外,如果是 Java 数组,对象头中还必须有一块用于记录数组长度的数据因为普通对象可以通过 Java 对象元数据确定大小,而数组对象不可以
实例数据(lnstance Data): 程序代码中所定义的各种成员变量类型的字段内容(包含父类继承下来的和子类中定义的)
对齐填充(Padding): 不是必然需要,主要是占位,保证对象大小是某个字节的整数倍HotSpot 虚拟机,任何对象的大小都是 8 字节的整数倍