1.虚拟机内存结构
现代的java虚拟机会按照功能将内存分为几个部分,堆、方法区、本地栈、程序计数器。
常量池
常量池是方法区里最常接触到的部分,常量池分为类常量池和运行时常量池。
类常量池是类属性的静态描述,我们编译class后,会在class文件中定义constant_pool表,主要有三部分数据,类和接口的全限定名、字段的名称和描述符、方法的名称和描述符,通过这种方式指令集就可以通过简单的字节描述定位目标。
类常量池的数据需要在每次方法或字段的访问时调用,所以为了提高新能,加载类文件时,会把常量池加载到内存称为常量池缓存,也叫运行时常量池。
匿名变量会在编译时做处理后进入常量池,所以第一个是true。
String a = "a";
String b = "b";
String c = "a" + "b";
String d = "ab";
System.out.println(("a" + "b") == d); //true
System.out.println(a + b == d); // false
栈
栈的组成
栈是严格后进先出的数据结构,空间由jvm自动管理,栈空间相对较小,一般只有几M,所以当调用关系太长时可能会造成内存溢出。
栈是由栈帧组成,栈帧结构大概可以分为局部变量、返回指令集、指针、运行时常量池引用、操作数栈、monitor等几部分组成。
栈式指令集
Jazelle是ARM体系针对java字节码处理的硬件级解决方案,可通过DBX和RCT分别对java解释型和编译型提供支持,为java提供硬件级加速。
JVM执行指令集是基于栈的指令集。相比寄存器式指令集操作简单,只有入栈与出栈两种。可移植性好,但性能稍差。
堆
java的堆是我们主要能操作的内存空间,程序运行中的数据一般都会被存放在堆中,对象都会被放在堆中管理。
jvm会把堆划分成几部分eden、2个survivor、old,就是常说的分代,为什么分代呢?
首先,内存是一个连续的线性存储,大小有限,分配后还要回收,但每次分配的大小不可能完全一样,所以在多次分配后就会
产生碎片,为了回收内存并减少碎片,出现了几种算法。
标记清除,标记需要回收的内存再进行回收,不做其他处理,内存碎片很多。
复制算法,把需要保留的内存复制到一个新的空间,然后把旧空间整体释放,没有碎片,但需要更多的内存。
标记整理,标记需要回收的内存回收,并整理需要保留的内存,没有碎片。
这时现在jvm的几种主流垃圾回收算法,但每个算法都有各自的优点和问题,而结合java对象的生存周期来说,新分配的对象存活几率很小,所以适合复制算法,长期存活的对象适合标记整理算法,所以就有了分代策略。
新创建的对象要分配到eden区,在存活一定时间后会到old区。这章我们主要讲内存,jvm垃圾回收后面文章会重点讲,这里就不多说了。
2.对象内存的分配
上面说了我们自己创建的对象实例一般会分配在堆上,但这个绝对么?又如果分配?
栈上分配
jit,Just In Time Compiler是jvm运行时的编译器,java是解释型语言,有自己的虚拟机和指令集,但解释性语言性能差,为了提高jvm的性能,jvm会在运行时对热点代码进行编译,让热点代码可以直接由操作系统执行,jit就是编译热点代码的编译器,jit除了对代码进行编译外,还会做些额外优化,其中栈上分配就是和对象分配相关的优化。
站上分配,栈上分配顾名思义就是直接在栈上给对象的实例分配空间,栈的还出就是因为它遵循严格的后进先出策略,不会产生内存碎片,不需要垃圾回收去回收这个对象,但什么样的对象适合在栈上分配呢?
逃逸分析,栈是每个线程独有,所以如果创建的对象不会被其它线程访问到就是栈上基础了,逃逸分析就是jvm判断对象是否会有其它线程访问对象,会不会被引用到方法体外,数据会不会过大等。
标量替换,如果jit判断对象可以在栈上分配,就会把对象打散。把对象的成员变量直接分配在栈上,这里分两种情况,基本类型数据的引用和数据都在栈上分配,引用类型就只有引用放在栈上。
TLAB
当我们的对象不满足栈上分配的条件,又如何分配呢?
指针碰撞,指针碰撞是eden区分配对象的方法,jvm通过cas方式修改指针位置分配对象,但cas方法之前文章也说了在并发特别高的情况下会导致频繁失败并重试(https://segmentfault.com/a/11...,而分配对象就是一个特别频发的操作,如果直接使用指针碰撞分配就会造成大量的重试。
TLAB,Thread Local Allocation Buffer,线程本地分配缓存区,tlab是每个线程都有的一块空间,在eden区,每个线程只能写自己的tlab,但可以访问其它线程的tlab空间,但tlab分配满了以后,线程会向虚拟机再申请一块内存作为tlab,原来的tlab空间不需要做什么,后期垃圾回收会对这块空间记性回收的。当对象过大不能再tlab分配时,才会通过指针碰撞方式在eden区直接分配。
NUMA
numa之前文章介绍过(https://segmentfault.com/a/11...,与tlab类似,但并不冲突,jvm支持numa让线程只会操作本地内存,让对象分配更快,但支持numa的垃圾回收策略只有ParallelGC和zgc,常用的cms、g1(java14的g1,貌似支持了numa)并不支持numa。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。