Java虚拟机内存空间是由哪些部分组成的?
ps:这张图在掘金上看到的,感觉很不错,来源: juejin.cn/post/684490…
ps:JDK1.8同JDK1.7相比:元空间(Metaspace)取代了永久代。元空间本质和永久代类似,都是对JVM规范中方法区的实现,不过元空间与永久代最大的区别是,元空间不存在于虚拟机中,而是使用了本地内存。
堆:
堆是什么?
堆是用来存放Java对象的地方,几乎所有的对象都存在于堆中。
堆的特点
- 线程共享的地方,每个线程都有权利访问堆区。
- 垃圾回收主要存在于堆区,因为java程序运行时也就是生成大部分java对象,从而垃圾回收也就是要回收不用的java的对象。
- 堆可以分为新生代和老年代,可以针对不同的区域使用不同的垃圾回收算法。(新生代:Eden,Survivor(from,to),老年代:old)。
- 堆的大小可以固定也可以扩展,但是堆已满的时候,内存无法扩展的时候,就会抛出OutOfMemoryError异常。
新生代与老年代
- 新生代是由 Eden + Survivor(S0) + Survivor(S1)构成的,默认比例 8:1:1。
- 老年代生命周期比年轻代生命周期长,也就是老年代对象存活时间长。
- 新生代与老年代默认比例,1:2 XX:NewRatio=2,新生代占堆空间的1/3。
- 所有的对象几乎都是在Eden区被new出来的,Eden放不了的大对象直接进入老年代。
对象分配的过程?
- 对象首先被new在Eden区,但是要看Eden区能否有足够的空间。
- 如果Eden空间不足,那么首先进行一次minorGC,进行一次年轻代回收,将年轻代不用的对象进行销毁,在把新的对象放入Eden区。
- 剩余的对象放入幸存者Survivor-S0区。
- 如果在进行垃圾回收的话,Survivor-S0区的对象如果没被回收掉的话,会被复制到S1区。
- 如果在进行垃圾回收的话,Survivor-S1区的对象如果没被回收掉的话,会被复制到S0区。(总之保证S0和S1只有一个区有对象)
- 默认是15次的循环,超过15次,则会将幸存者区存下来的对象转入老年代。
FullGC和MajorGC的触发条件?
- Full GC,完全GC,一般发生在老年代和方法区空间不够的情况下才会触发Full GC,同时对新老年代进行回收,STW-stop the-word停顿时间最长。
- Major GC,大型GC,一般是发生在老年代,但是老年代回收一般都要引起minor GC,年轻代GC。
- 参考文档:www.bookstack.cn/read/gc-han…
TLAB
- TLAB是Thread Local Allocation Buffer,即线程本地分配缓存区,是属于Eden区的,这是一个线程专用的内存分配区域,线程私有,默认开启的。
- 堆是全局共享的,在同一时间,可能会有多个线程在堆上申请空间,每次对象的分配都需要同步的进行,虚拟机通过CAS的方式保证更新操作的原子性。
- 通过TLAB来避免多线程冲突,在给对象分配内存的时候,每个线程使用自己的TLAB,这样可以使得线程同步,提高了对象分配的效率。
- -XX:+UseTLAB,-XX:+TLABSize设置TLAB的大小。
对象引用方式?
- 强引用:
Squirrel s = new Squirrel()
,此时的s就是Squirrel的引用变量,普通new出来的对象都是强引用,有引用变量指向的时候,jvm不会对其(forever)进行回收。 - 软引用:一个对象具有软引用,但是内存空间足够的时候,就不会去回收他,如果内存空间不够了,那么有可能会对其进行回收。Soft
- 弱引用:JVM进行垃圾回收的时候,都会对其进行回收 WeakReference
- 虚引用:任何时候都还会被垃圾回收器进行回收。
方法区:
方法区是什么?
- 方法区是堆的一个逻辑部分,但是他不存在于堆中,而是存在于本地内存中。
方法区中存放了什么?
- 被虚拟机加载的类信息
- 常量
- 静态变量
- 编译器编译后的代码
方法区的特点?
- 线程共享,和堆一样都是线程共享的。
- 方法区中的信息一般需要长期存在。
- 内存回收效率低,方法区中的信息一般需要长期存在,回收一遍之后可能只有少量信息无效,主要回收目标,常量池的回收,类型的卸载。
运行时常量池?
- 方法区中存放:类信息、常量、静态变量、即时编译器编译后的代码。常量就存放在运行时常量池中。
- 当类被虚拟机加载以后,.class文件中的常量就存放在方法区的运行时常量池中,而且在运行时期,可以向常量池中添加新的常量,如string的intern()可以在运行期向常量池添加字符串常量。
栈
ps:来源:camo.githubusercontent.com/109d4eff5b6…
栈是什么?
栈是描述Java方法运行过程的内存模型。Java虚拟机会为每一个即将运行的Java方法创建一块叫做”栈针”的区域,用来存放该方法运行过程中的一些信息。局部变量表、操作数栈、动态链接、方法出口信息。
压栈出栈过程?
- 当方法运行过程中需要创建局部变量时,就将局部变量的值存入栈帧中的局部变量中。
- 虚拟机栈顶的栈帧是当前正在执行的活动栈,也就是当前正在执行的方法。PC寄存器也会指向这个地址。如果方法中仍然有方法,那么将其对应新的栈帧压入栈顶,变为当前的活动栈,方法结束后,当前活动栈的返回值变成新的活动栈帧的一个操作数。
局部变量表?
定义为一个数字数组,主要用于存储方法参数、定义在方法体内部的局部变量,数据类型包括各类基本数据类型,对象引用,以及 return address 类型。
操作数栈?
- 栈顶缓存技术:由于操作数是存储在内存中,频繁的进行内存读写操作影响执行速度,将栈顶元素全部缓存到物理 CPU 的寄存器中,以此降低对内存的读写次数,提升执行引擎的执行效率。
- 每一个操作数栈会拥有一个明确的栈深度,用于存储数值,最大深度在编译期就定义好。32bit 类型占用一个栈单位深度,64bit 类型占用两个栈单位深度操作数栈。
- 并非采用访问索引方式进行数据访问,而是只能通过标准的入栈、出栈操作完成一次数据访问。
动态链接?
如果被调用的方法无法再编译期被确定下来,只能在运行期将调用的方法的符号引用转为直接引用,这种引用转换过程具备动态性,因此被称为动态链接。
本地方法栈
本地方法栈是什么?
本地方法栈结构和虚拟机方法栈是一样的,只不过其实用C来实现的,这里就不过多介绍了。
程序计数器
什么是程序计数器?
程序计数器顾名思义:程序计数器存储的是当前线程正在执行的那条字节码指令的地址,若当前线程正在执行的是一个本地方法,那么此时计数器为undefined。
程序计数器的作用?
- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制
- 在多线程情况下,程序计数器记录的是当前线程执行的位置,从而当线程切换回来的时候,就知道上次线程执行到哪了。
程序计数器的特点?
- 线程私有,每条线程都有自己的程序计数器。
- 生命周期,随着线程的创建而创建,随着线程的结束而销毁。
- 不会出现outOfMemoryError的内存区域。
本文转载自: 掘金