• JVM内存模型剖析与优化


    目录

    JDK体系结构

     Java语言的跨平台特性

    JVM整体结构及内存模型

     JVM具体结构

    方法区 

    本地方法栈

    堆 

    JVM内存参数设置 

    其他 


    JDK体系结构

     Java语言的跨平台特性

    JVM整体结构及内存模型

     JVM具体结构

    老师主要认为这个栈就是线程栈,因为一个线程进行执行就会分配对应内存区域,就是栈

    每当创建一个方法会在对应栈中创建一个栈帧,用来分配内存空间,可以这么理解,不同栈帧将方法的局部变量隔离出来

    如果栈里面存放了很多局部变量,局部变量是对象类型的话,那它的值就是这些对象在堆里面的内存地址

    为什么用栈来当存放栈帧内存空间 

    当我们先调用main方法,会先给main方法分配内存空间,再调用compute方法,然后会给compute方法对应分配内存空间,这个compute方法后调用会先执行完,执行完就将这部分内存空间直接释放掉,跟栈的FIFO特性相吻合,后分配的内存空间反而被先释放掉 

    局部变量

    可以理解为槽或者数组,就是用来存放局部变量的

    操作数栈

    就是要操作的数据中转存放的临时空间

    动态链接

    compute相当于一个符号,代码执行遇到这个符号之后需要进行解析,程序加载的时候会对静态资源进行解析,compute这种方法只有在程序运行的时候进行解析,就是要找到运行这行的代码,代码指令加载到方法区里面去,对应有内存的位置,动态链接就是在程序的运行过程中,把这些符号引用转换为方法对应代码的内存地址

    方法出口

    根据方法出口信息知道在外层方法具体哪个位置继续执行

    执行示例:

    方法

     

    将int类型常量1压入操作数栈中

    将int类型值存入局部变量1中,这个局部变量1就是a

    将int类型常量2压入操作数栈中

    将int类型值存入局部变量2中,这个局部变量2就是b

    从局部变量1中装载int类型的值,就是把1放到操作数栈中

    从局部变量2中装载int类型的值,就是把2放到操作数栈中

    执行int类型的加法,会把操作数栈的数据拿出来,计算完之后把值重新压回操作数栈中,在cpu内部进行操作

    将一个8位带符号整数压入栈中,就是把10放到操作数栈中

    执行int类型的乘法

    将int类型值存入局部变量3中,这个局部变量3就是

    从局部变量3中装载int类型的值,就是把30放到操作数栈中

    把值返回到主线程里面去

    局部变量0,局部变量1,这个可以理解为索引,局部变量0用来存放this 

    程序计数器 

    每个线程独有的内存空间,当前线程马上运行代码的位置 

    为什么设计程序计数器

    每次运行代码,字节码执行引擎都会修改程序计数器里面的这个值,因为多线程,当前线程执行的时候,时间片被其他线程抢占,当前线程会挂起等待下次抢到cpu执行权,等到下次可以继续执行的时候,可以通过程序计数器得知下次代码执行的开始位置 

    方法区 

    如果方法区有很多静态变量,恰好很多静态变量类型是对象,对象是存放在堆里面的,那么这些静态变量存放的是这些对象在堆里面的内存地址 

    在jdk1.8之前叫做永久代,1.8之后包括1.8叫元空间,用的是直接内存就是物理内存

    假设我们元空间设置的21M,我们进行回收之后只剩下1M,下一次就会把设置值自动调到15M,假设回收之后剩下20M,下一次会自动设置到30M,一般来说我们设置设置初始和最大大小一样,这样就不需要进行扩容或者缩容

    本地方法栈

    本地方法也是需要有内存空间进行存放,如果线程在运行过程中,调用了本地方法,本地方法也需要分配内存,就是分配在本地方法栈里面的,也是每个线程独有的 

    堆 

    minor GC是在Eden满的时候进行的垃圾回收,触发垃圾线程进行垃圾收集

    GC过程在方法栈找GC root, 比如局部变量和静态变量就可以作为gc root,就是堆上被外部变量所引用,从栈上找局部变量,从方法区里面找静态变量,找到所有引用对象,只要从gc root根节点出发在链条上面的所有对象都认为是非垃圾对象,会将非垃圾对象放到survivor区里面,一个对象如果经历过一次gc之后,没有被回收掉分代年龄会+1,如果下一次eden区又被放满了,不但回收eden区,而且会对survivor非空区也进行gc,把存活的对象放到空的survivor区中,分代年龄+1,当分代年龄到15还没有被回收掉,那么这个对象会被放到老年代

    缓存对象,静态变量,spring容器对象最后都会放到老年代

    heapTests相当于gc root,我们不断地new对象放到heapTests里面,heapTests相当于一直是有用的,eden区不断的产生新对象,因为一直被引用着,即使被放满了也不会进行回收,就会出现oom,老年代如果放满了会触发full gc,full gc是对整个堆和方法区进行回收

    gc的过程中会出现stw,stop the world,实际上就是停止掉用户线程,我们用户在电商网站上下单,后端就有对应的执行线程,当我们gc过程中,会停止对用户发起的所有线程,如果用户正在下单,开始gc,如果时间比较长,用户会感觉卡顿一下,对体验和性能有一定影响,我们调优就是减少gc,主要就是full gc,因为full gc收集的时间比较长,减少full gc次数或者full gc回收时间,minor gc也要减少执行次数,它的stw时间特别的短

    为什么要STW

    我们在gc过程中就是找出非垃圾对象,如果发生了full gc,我们顺着其中一个gc root开始找非垃圾对象,这个链条找完之后,如果我们没有STW,这个线程还在继续执行,假设这个线程执行结束,所有的变量都已经不用了,我们之前找出的非垃圾对象,现在变为垃圾对象,相当于gc白做了

    JVM内存参数设置 

    Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里): 

    java -Xms2048M -Xmx2048M -Xmn1024M -Xss512K -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar microservice-eureka-server.jar

    -Xss:每个线程的栈大小

    -Xms:设置堆的初始可用大小,默认物理内存的1/64

    -Xmx:设置堆的最大可用大小,默认物理内存的1/4

    -Xmn:新生代大小

    -XX:NewRatio:默认2表示新生代占年老代的1/2,占整个堆内存的1/3。

    -XX:SurvivorRatio:默认8表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。

    关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N 

    -XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。

    -XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。这个跟早期jdk版本的-XX:PermSize参数意思不一样,-XX:PermSize代表永久代的初始容量。

    由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。 

    StackOverflowError示例:

    1. // JVM设置  -Xss128k(默认1M)
    2. public class StackOverflowTest {
    3. static int count = 0;
    4. static void redo() {
    5. count++;
    6. redo();
    7. }
    8. public static void main(String[] args) {
    9. try {
    10. redo();
    11. } catch (Throwable t) {
    12. t.printStackTrace();
    13. System.out.println(count);
    14. }
    15. }
    16. }

    运行结果: java.lang.StackOverflowError at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:12) at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:13) at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:13)

    结论:-Xss设置越小count值越小,说明一个线程栈里能分配的栈帧就越少,但是对JVM整体来说能开启的线程数会更多 

    其他 

    老师理解full gc和major gc一样,有的人认为major gc只回收老年代的对象 

  • 相关阅读:
    TorchVision Transforms API 大升级,支持目标检测、实例/语义分割及视频类任务
    蓝桥杯练习题——dp
    linux实战项目经验得到的常用linux命令(2) 磁盘分区和内容查找
    [附源码]计算机毕业设计JAVAjsp校园活动募集平台
    SpringBoot进阶-日志等级配置与操作
    布隆过滤器
    web框架与Django
    Knowledge Graph Prompting for Multi-Document Question Answering
    仅2299元,Nreal Air国内发布进一步刺激BB市场
    python-flask笔记
  • 原文地址:https://blog.csdn.net/jiayoubaobei2/article/details/128168075