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示例:

// JVM设置  -Xss128k(默认1M)
public class StackOverflowTest {
    
    static int count = 0;
    
    static void redo() {
        count++;
        redo();
    }

    public static void main(String[] args) {
        try {
            redo();
        } catch (Throwable t) {
            t.printStackTrace();
            System.out.println(count);
        }
    }
}

运行结果: 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只回收老年代的对象 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值