Java 虚拟机内存模型
内存构造篇
1、内存构造简介
-
程序计数器:
- 以后线程所执行的字节码行号指示器;
- 分支、循环、跳转等管制;
- 当执行的是 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 缓冲区,不须要回收,能够重复用。
2、栈和栈帧
栈帧:一个办法对应一个栈帧区域,先进后出 FILO,压栈再出栈。
操作数栈: 是各种运算产生的场合, 各种数据在进行运算时都会弹入操作数栈,而后后果会弹出操作数栈。
办法进口: 存储的是以后办法执行结束之后应该返回到上一级办法的地位。
3、堆内存逻辑分区
4、元空间
办法区又称永恒代在 1.8 称为元空间。
元空间替换永恒代起因
1、Java7 及以前版本的 Hotspot 中办法区位于永恒代中。同时,永恒代和堆是互相隔离的,但它们应用的物理内存是间断的。永恒代的垃圾收集是和老年代捆绑在一起的,因而无论谁满了,都会触发永恒代和老年代的垃圾收集。
2、元空间存在于本地内存,意味着只有本地内存足够,它不会呈现像永恒代中“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 残余空间容量的百分比。
5、常量池
常量池
Class 能够了解为 Class 文件的资源仓库.class 文件中除了蕴含类的版本、字段、办法、接口等形容信息,还有一项就是常量池,常量池中用于寄存编译期间生成的各种字面变量和符号援用。
八中根本类型中 byte、short、integer、long、char 等在值小于等于 127 应用对象池,即不负责创立和治理大于 127 的对象。
字符串常量池
- 字符串的调配和其余的对象调配一样,消耗昂扬的工夫和空间代价,作为根底数据,大量创立字符串影响程序性能。
- 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”); 创立了多少个对象?
答:创立过程如下:
- 在常量池查找 ”abc” 对象,有则返回对应的援用实例,没有则在常量池创立对应实例对象。
- 在堆中 new 一个 String(“abc”)对象。
- 将对象地址赋值给 str,创立一个援用。
因而,常量池没有 ”abc” 字面量则创立两个对象,否则创立一个对象以及创立一个对象的援用。
例题 2:String str=new String(“a”+”b”); 创立了多少个对象?
答:字符串常量池:a、b、ab。
堆:new String(“ab”)。
援用:str。
共计 5 个。
6、TLAB
TLAB(Thread Local Allocation Buffer)线程本地调配缓冲区。
JVM 调配对象是优先调配到线程栈上,栈上调配不了的(如对象较大)则间接调配在 Old 区;如果对象不大,优先调配在栈上的 TLAB 上。
TLAB 是在 Eden 区的专门的内存空间,为了避免在 Eden 区调配空间的多线程竞争资源,JVM 为每个线程在 Eden 区上调配的专属内存空间即 TLAB。
内存调配和对象布局篇
1、JOL 对象内存布局
Java Object Layout 对象的内存布局:即对象在内存中如何散布的。
数组对象:markword(8) + classPointer(4) + 数组长度(4) + 实例数据 + 对齐
Ps:压缩指针和压缩一般对象指针:
应用 java -XX:+PrintCommandLineFlags -version 命令能够看到蕴含以下信息:
显示:-XX:+UseCompressedClassPointers -XX:+UseCompressedOops,
其中 -XX:+UseCompressedClassPointers 是应用压缩类指针,原先是 8 字节,因为 8 字节 =8×8=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 指令。
2、为新对象分配内存的形式
- 内存规整:间接挪动指针到未被应用的区域(指针碰撞),须要带压缩的 GC:Serial、ParNew 等带 compact 的垃圾回收器。
- 内存不规整:闲暇列表保护可用空间,例如:CMS 等基于 mark-sweep 的垃圾回收器。
3、内存调配的线程平安
CAS 保障,每个线程在 jvm 事后调配了内存,称为本地线程调配缓冲区 TLAB,并同步锁定。
4、对象的拜访定位
栈上的 reference 数据操作具体堆上的对象有 句柄和间接指针 两种形式。
句柄形式:jvm 堆上划分内存来作为句柄池,援用时援用存储对象的句柄地址,句柄中蕴含对象实例数据和类型数据的各自具体地址信息。
长处:援用不必批改,比较稳定,对象挪动如垃圾回收只有扭转句柄值即可。
间接指针:援用间接援用对象地址,对象中蕴含类型数据指针,指向办法区 class。
长处:速度快。
5、对象的创立过程
- new 指令,开拓空间(申请、初始化),这里的初始化是半初始化,还是默认值,例如对象中有变量 int i=8; 此时初始化后 i =0 即默认值,所以称为半初始化。
- invokespecial , 构造方法,赋值(真正的初始化,i=8)。
- astore, 对象建设关联,栈空间与堆空间建设关联。
- 首次拜访对象。
其中 2、3 两部会产生指令重排。
6、对象逃逸
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 之后默认开启,但如果栈空间有余会调配到堆空间。逃逸剖析产生在编译期间。