JVM 内存模型JMM(Java Memory Model)
Java 代码:
public class MainTest {
private int compute() {
int a = 1;
int b = 2;
int c = a * b + 10;
return c;
}
public static void main(String[] args) {
MainTest mainTest = new MainTest();
int ret = mainTest.compute();
System.out.println(ret);
}
}
idea可以安装jclasslib插件,来查看字节码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NdyiUYXx-1650626855780)(//upload-images.jianshu.io/upload_images/1510669-47b05a50f6339a14.png?imageMogr2/auto-orient/strip|imageView2/2/w/773/format/webp)]
JVM 运行时数据区内存模型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyOwnoSh-1650626855784)(//upload-images.jianshu.io/upload_images/1510669-4e55ccc93c96d2bb.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1123/format/webp)]
“绿色区域”:栈、本地方法栈、程序计数器为线程运行时独有的数据区域
“黄色区域”:堆、方法区为所有内存之间共享的内存区域。
线程独有的内存区域
1、程序计数器(Program counter register)
程序计数器内存区域很小,它是当前线程所执行的字节码的行号指示器(LineNumber),字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。
只有Java的方法这个计数器才有值,如果执行的是一个Native方法,那这个计数器是空的。
JVM的多线程实现:JVM的多线程是通过CPU时间片轮转(即线程轮流切换并分配处理器执行时间)算法来实现的。当一个正在执行的线程因为时间片耗尽被挂起,另一个线程获取到时间片开始执行。
当被挂起的线程重新获取到时间片的时候,它要想从被挂起的地方继续执行,就必须知道它上次执行到哪个位置了,这时候LineNumer就起作用了,在JVM中,通过程序计数器记录某个线程的字节码执行位置
。
程序计数器是具备线程隔离的特性,每个线程工作时都有属于自己的独立计数器。
Java 虚拟机栈(Java Virtual Machine Stacks)
Java 虚拟机栈是线程私有的,生命周期随着线程启动而产生,线程结束而消亡。
Java虚拟机描述的是Java 方法执行的内存模型,用于存储栈帧。线程启动时会创建虚拟机栈,每个方法在执行时会在虚拟机栈中创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法返回地址、附加信息等。
每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中的入栈到出栈的过程。
内存不需要保证连续的,可以通过-Xss 设置成固定大小,也可以动态扩容和缩容。
局部变量表的容量以变量(Variable Slot)为最小单位,存放8种类型 byte、short、int、float、char、boolean、reference; 注意一下,局部变量表的引用存储的都是局部变量的对象引用,而不是成员变量(getter setter属性)的对象引用。成员变量的对象引用是存储在JAVA 堆(Heap)中),虚拟机栈工作流程如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AB1dGdZv-1650626855785)(//upload-images.jianshu.io/upload_images/1510669-d034429a798405fd.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/409/format/webp)]
本地方法栈
本地方法栈(Native Method Stacks)与Java 虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机为虚拟机执行Java方法(字节码)服务,本地方法栈为Native方法服务
Native 方法则是虚拟机通过JNI直接调用本地C/C++库。在此我向大家推荐一个架构学习交流圈。交流学习伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-83pOq2ae-1650626855785)(//upload-images.jianshu.io/upload_images/1510669-49e0d2e97fc3a8d0.png?imageMogr2/auto-orient/strip|imageView2/2/w/852/format/webp)]
线程共享区域
堆
Java 堆是垃圾收集器管理的主要区域,堆内存分为新生代(Young)和老年代(Old),新生代(Yong)分为Eden、From Survivor、To Survivor,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4L1Yo5HF-1650626855800)(//upload-images.jianshu.io/upload_images/1510669-e4c7747ec582f9a3.png?imageMogr2/auto-orient/strip|imageView2/2/w/1020/format/webp)]
假如一个电商网站,每日点击上亿次,每个用户平均点击二三十次,那么日活用户=1亿/20=500万,假如付费转化率10%,500万10%=50万订单,正常情况下50订单是在三四小时产生的,平均下来也就几十单,压力是能抗住的。但是在大促的时候,50万订单都是在前几分钟产生,假设每秒1000多单,有三台订单服务器,分配到每台机器大概是300单/每秒,每个订单对象1KB,所以又每秒300KB订单对象生成,下单还涉及到其他对象,比如库存、优惠券、积分等,放大20倍,300KB20/秒对象生成,可能同时还有其他操作,如订单查询等,再放大10倍,那就是300KB2010/秒,每秒产生60MB对象,1秒后都变成垃圾对象。
java -Xms3072M -Xms3072M -Xss1M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M -jar order.jar
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YyI7tnDi-1650626855801)(//upload-images.jianshu.io/upload_images/1510669-ba7dc6d1f2d4a489.png?imageMogr2/auto-orient/strip|imageView2/2/w/1030/format/webp)]
当分配的内存的时候 eden区内存已经被分配完了,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC,GC期间虚拟机又发现回收对象无法存入Survior空间,所以只好把新生代的对象提前转移到老年代,老年代上的空间足够存放回收对象时,就不会出现Full GC ,执行Minor GC 后,后面分配的对象如果能够存在Eden区的话,还会在Eden区分配内存
可以用如下代码验证
public class GCMainTest{
public static void main(String[] args) throws InterruptedException{
byte[] allocation1,allocation2,allocation3,
allocation4,allocation5,allocation6;
allocation1=new byte[60000*1024];
allocation2=new byte[8000*1024];
allocation3=new byte[1000*1024];
allocation4=new byte[1000*1024];
allocation5=new byte[1000*1024];
allocation6=new byte[1000*1024];
}
}
java -Xms3072M -Xms3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M -jar order.jar
加上 -Xmn2048M
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lupDpcPz-1650626855803)(//upload-images.jianshu.io/upload_images/1510669-101775a24879c298.png?imageMogr2/auto-orient/strip|imageView2/2/w/1026/format/webp)]