JVM内存模型剖析与优化

一:JDK体系结构

 二:JVM整体结构及内存模型

public class Math {
    public static final int initData = 666;
    public int compute(){
        int a = 1;
        int b = 2;
        int c = (a+b)*10;
        return c;
    };
    public static void main(String[] args){
        Math math = new Math();
        math.compute();
    }
}

当我们执行main方法,jvm会在 线程栈中 分配一块空间。每一个线程独用 栈,程序计数器,本地方法栈,不会共享。

通过程序计数器,虚拟机会一行一行的执行如下命令。

eg:

0: iconst_1 ->将int类型常量1压入操作数栈

1: istore_1 -> 将int类型值存入局部变量1

javap -c Math.class -> Math.txt 对class文件反编译

参照jvm指令手册:http://note.youdao.com/s/MVsjIxcH 

直到进入一个新的方法入口,新的栈帧入栈,当前栈帧改变,又或者遇到返回指令或出现异常结束了方法,对应虚拟机出栈。

执行完毕 局部变量销毁 资源释放。

1: 堆,方法区是所有线程共享内存区域。

2: 栈,程序计数器,本地方法栈,线程私有,不会共享

3:方法区是JVM规范1.8版本用元空间取代了1.7版本及以前的永久代。元数据区和永久代本质上都是方法区的实现。

不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存(也就是说jvm可以使用外边的内存)。 因此,默认情况下,元空间的大小仅受本地内存限制。

元空间存储:已被虚拟机加载的类信息、常量、静态变量、JIT(just-in-time compilation)即时编译器编译后的代码等数据。

运行时常量池 是方法区的一部分 Class文件中除了有类的版本/字段/方法/接口等描述信息外,还有一项信息是Class常量池 (Constant Pool Table),用于存放编译期生成的各种字面量和符号引用(通过 javap -v 类名.class即可反编译查看) 这部分内容将类在加载后进入方法区的运行时常量池中存放。

运行时常量池相对于Class文件常量池(Constant pool:)的另外一个重要特征就是具备动态性当然并非Class文件常量池中 的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。 这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。

4: 本地方法栈:为虚拟机使用到的Native方法服务。

 栈:

用来存放基本数据类型:boolean、char、byte、short、int、long、float、double 引用类型变量时,栈(局部变量表)存储的是指向堆的内存地址(或者说对象引用),真正数据在堆内存中。

栈帧是什么?

栈帧(Stack Frame)是用于虚拟机执行时 方法调用和方法执行时的数据结构,它是虚拟栈(特性FILO)的基本元素。

栈帧包含的内容有:

(1)局部变量表:

在编译时即可确定。存放方法参数和局部变量;它的基本单位为变量槽(slot);它是有索引的,就像数组一样。 从0开始,到表的最大索引,要注意的是,方法参数的个数 + 局部变量的个数 ≠ Slot的数量。因为Slot的空间 是可以复用的,当pc计数器的值已经超出了某个变量的作用域时,下一个变量不必使用新的Slot空间,可以去覆盖 前面那个空间。 虚拟机是通过索引定位的方式使用局部变量表。

(2)操作数栈:

(1)做计算等操作 临时的中转存放 内存空间。

   2)栈桢刚创建时,里面的操作数栈是空的。

(3)提供指令来让操作数栈对一些数据进行入栈操作,比如可以把局部变量表里的数据、实例的字段等数据入栈。

public int compute();

Code:

0: iconst_1 ->将int类型常量1压入操作数栈

1: istore_1 -> 将int类型值存入局部变量1

2: iconst_2 3: istore_2

(3) 动态链接:是在程序运行期间完成的将符号引用(Class常量池中的)替换为直接引用。

(4) 方法返回地址:

当执行遇到返回指令,会将返回值传递给上层的方法调用者,这种退出的方式称为正常完成出口 当执行遇到异常,并且当前方法体内没有得到处理,就会导致方法退出,此时是没有返回值的,称为异常完成出口。返回地址要通过异常处理器表来确定。

5: 字符串常量池

JVM为了提高性能和减少内存开销,在实例化字符串常量的时候为字符串开辟一个字符串常量池。

字符串常量池位置:

Jdk1.6及之前: 有永久代, 运行时常量池在永久代,运行时常量池包含字符串常量池 Jdk1.7:有永久代,字符串常量池从永久代里的运行时常量池分离到堆里

Jdk1.8及之后: 无永久代,运行时常量池在元空间(方法区),字符串常量池里依然在堆里

创建字符串常量时,首先查询字符串常量池是否存在该字符串(可类比缓存池) 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中!

三:JVM内存参数设置

Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里): java ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K -XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐jar microservice‐eurek a‐server.jar

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

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

-XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M. 达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值