【Notes3】jvm内存模型


1.逃逸分析:堆+方法区

方法区存储一些元数据信息,如下方法区在jdk8后从永久代转移到了元空间(metaspace),使用操作系统本地内存,而不使用jvm的堆内存,存静态方法或静态变量等等全局信息。
在这里插入图片描述
存储当前方法运行过程中一些局部变量,比如基本数据类型存在栈帧上,对象引用即对象地址也是存在这里。

本地方法不是由java编写的方法。java不能与操系底层打交道,需要C/C++本地方法与os底层api打交道。java可间接通过本地方法调用底层功能。本地方法栈就是给本地方法提供的内存空间

程序计数器是记住下一条jvm执行地址。

new的对象一般在上创建,特例:特殊逃逸分析:jvm在分析代码后发现一个对象在声明后只有在当前运行的函数中调用,那么就会将这个对象在上申请空间而不是在上,这就是jdk6出的逃逸分析。因为在栈上申请的对象,函数执行完后会直接清理,这样大大减轻了GC的压力。
在这里插入图片描述
在这里插入图片描述
Boolean、Byte的所有对象,都是预先创建好的(类加载的时候)。
Character、Short、Integer、Long是-128~127的对象是预先创建好的(Character没有负数)。
现象:如果new Integer(1)则是从创建好的缓存中,直接拿出,因而是同一个(,是Integer a1 = 1; Integer a2=1;…自动装箱的才是,new的是新的对象)。
原因:为了节省内存,这些数字使用概率很高,早就创建好,之后都用同一个,是提高效率的做法。
在这里插入图片描述
如上如果开启逃逸分析即将最后参数删除,几次GC后停止了。如果不是只在当前函数范围用到的对象不行。
在这里插入图片描述
下面以一个例子看下函数func1运行过程中内存如何调用func1(如下绿色)运行结束后即碰到它的最后大括号,先删除栈顶b再删除a。如下正确打印20,11。原因是func1运行拿到了a参数(int占4字节),因为是main函数调用的func1,func1前面紧跟着main函数的栈(如下黄色)。最后打印a时,func1已经运行完,打印的是main函数的栈区a=10(a在main中是实参,传入func1会变形参,会重新创建存取区)。
在这里插入图片描述
上面是值类型(复制一份值一样的)相关的函数运行过程和内存调用,下面看对象类型。func1结束后栈上清空了,但是堆上怎么清空呢?引出GC。除了栈和堆,还要注意方法区,main函数静态。
在这里插入图片描述
如下引用类型传地址,和上面形参a不同。
在这里插入图片描述
DOS窗口是windows下的黑窗口, 也叫作控制台, 也叫作命令提示符。查看当前目录(文件夹)下所有的内容: dir,清除控制台:cls。java virtual machine:java程序的运行环境(java二进制字节码的运行环境)。好处:1.一次编写,到处运行。2.自动内存管理,垃圾回收功能。3.数组下标越界检查。4.多态。如下VRD
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.GC垃圾收集器:标记/复制

对象是不是应该被删除?删除的标准是GCRoot。GCRoot下三个中直接或间接引用的对象不能被删除,
在这里插入图片描述
下面是实际GC过程,S0,S1,E比例:1:1:8。幸存到S0后,需要将E和S1全部删除,等下一次E快满了,将S0和E全部对象进行打标,幸存下来全部复制到S1。E+S1复制到S0,E+S0复制到S1,E+S1复制到S0…如此往复,比上面一分为二方法利用率高。大对象如1000万大小的int数组直接存到old区,如上的1和2用于FullGC,如上3用于young GC。

年龄15是最大值,7是初始值。晋升的岁数是个动态变化的值,在每次gc的时候都会重新计算,这和s区存活率(默认50%)等其他参数有关,可以通过-XX:+PrintTenuringDistribution来查看每次gc的时候确定下来的新的晋升年龄阈值。
在这里插入图片描述

3.g1垃圾收集器:CMS

在这里插入图片描述
serial串行垃圾收集器是单线程,会stop the world。ParNew是serial多线程版本,其他设定和serial一样,也是用复制算法,也会stop the world。

吞吐率指一段时间内,我能持续提供服务的时间,因而不关注单次GC的时间,单次GC时间可以长一点,但是保证很长一段时间内总的GC时间比较短就好。一般关注吞吐率客户端多,在win上直接启动java程序,win系统可能认为是客户端,所以会使用PS垃圾收集器。

CMS是在g1出来前广泛使用的服务端垃收器,一般年轻代使用复制算法,年轻代会stop the world,但是由于年轻代GC(Garbage Collection)时间短,对整体性能影响不大,重要的是老年代:Serial Old串行执行时间长。Paralled Old关注吞吐率的,单次执行时间也长。CMS关注单次垃圾收集时间,最大限度降低了单次垃圾收集时间。
在这里插入图片描述
并发标记:最终连上GCRoot就不需要被清理。带并发两个字和用户线程一起运行,不会stop the world(STW),但造成一个对象一开始是不需要被清理的,但是后面用户线程继续运行,把这个对象不使用了,变成需要被清理的对象了,这时候这个对象还未标记上,所以还需要第三步,第四步才开始清理。

标记清理:产生内存碎片,但对标记的对象清理,其他不动,这样可以并发清理即用户线程你继续运行,你引用的对象还在它原来位置,不用stop the world,因而优点在整个GC时间短。
在这里插入图片描述
如上是经典的三组垃收组合,下面是g1垃圾收集器,如下内存结构是堆。堆结构region化:空的就是没有被分配。每个region大小:1M-32M,2000个左右(可以设置个数)。我们声明对象时超过了单个region大小,g1有如下1和2策略。
在这里插入图片描述
如下remember set,collection set。
在这里插入图片描述
如下g1年轻代和老年代都是使用g1垃圾收集器,young区采用YGC,old区采用MixGC,如下绿色就是E S(from)。
在这里插入图片描述
如下MixGC和CMS步骤像,第一步还会标记GCRoot所在对象的Region,第二步拿第一步RootRegion扫描整个old区,看old区所有Region的rset中是否含有RootRegion,有就标识为阴影,标识这三个阴影作为第三步的遍历,不需要再遍历整个old区(阴影指有对象存活的区域,需要被进一步扫描,而没被标记的部分,说明已经不存在外部的引用可以直接被删除)。

g1中采用复制清理和CMS不同,也会STW。绿色young区在mixgc中需全部被选,old蓝色区选垃圾较多的。绿色young变成深绿色survive区。mixgc每次清理大部分,不用完全清理干净也能保证系统正常运行。
在这里插入图片描述
在这里插入图片描述

4.栈和栈帧:线程安全,top

当调用第一个方法如栈帧1时,给第一个方法划分一段栈帧空间并压入栈内。当这个方法执行完了将这个方法对应的栈帧出栈也就是释放这个方法所占用的内存。

一个栈内有没有可能多个栈帧存在?有的,调用方法1,方法1间接调用方法2,为方法2调用一块内存入栈,方法2又调用了方法3(尾递归)。。。方法3调用结束就把栈帧3内存释放掉。。。
在这里插入图片描述
如下main调用method1,method1调用method2,活动栈帧就是栈顶部的正在执行的方法method2。
在这里插入图片描述
栈内存就是一次次方法调用产生的栈帧内存,栈帧内存在每次方法结束后被弹出栈自动回收掉,不需要垃圾回收管理栈内存,垃圾回收只回收堆内存中无用对象。

栈内存大小可通过运行代码时虚拟机参数指定,栈内存划的越大反而让线程数变少:因为物理内存大小一定的,比如一个线程使用了1M栈内存,总共物理内存有500M,理论上有500个同时运行。如果对每个线程的栈内存设置了2M,只能同时运行250个线程。

所以栈内存不是分配越大越好,划分大了只能更多次进行方法递归调用,不会增强运行效率反而影响到线程数目变少,一般采用系统默认栈内存大小
在这里插入图片描述
在这里插入图片描述
x变量是m1方法内的局部变量,我们说一个线程对应一个栈,线程内每次方法调用会产生一个新的栈帧,如下线程不会产生安全问题。
在这里插入图片描述
在这里插入图片描述
如下不加线程保护的话会产生安全问题。
在这里插入图片描述
如下只有第一个线程安全
在这里插入图片描述
内存溢出:栈帧过多:方法的递归调用没有设置正确的计数条件导致递归爆栈。栈帧过大(很少出现):栈帧里都是些局部变量,方法参数,占用的内存都很小(一个int变量4字节,默认栈大小1M左右)。
在这里插入图片描述
如下设置Xss栈大小256k,比默认小。如上代码执行减少到5000多次。
在这里插入图片描述
两个类循环引用导致将java对象解析为json字符串数据StackOverflowError。
在这里插入图片描述
如下是线程诊断
在这里插入图片描述
在这里插入图片描述
B站/知乎/微信公众号:码农编程录
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43435675/article/details/112354981