nangongyanya 2020-07-26
????Java具有跨平台性,无疑是JVM底层翻译出来的汇编指令的不同,Unix和Window系统的汇编指令是不同的,Windows派系采用的是Intel汇编,Unix派系采用的是AT&T汇编。无论在哪个平台上编写的Java文件,编译后的class文件,放在哪个平台上都可以执行,只要下载平台相对应的JDK,都可以执行这个class文件,从而翻译出跟平台相对应的汇编指令
????class文件: 这个应该不用多说,就是java文件编译后的class文件
????class content: 首先来看一张类加载的图
????我们的class文件是存在在硬盘上的,需要将文件从硬盘读取到内存中,通过类加载将class文件内容加载到内存中,则会在类加载子系统中形成一块class content表示class 文件内容
????class对象:用过反射的网友应该清楚Class<T>这个对象,这就是class对象,类加载器通过解析class content内容则会在方法区(永久代)中形成Class<T>对象,像这个class对象的字段,方法的描述信息都会放在方法区中
????对象:对象则就是指我们new出来生成的对象
????1. 元空间使用的是直接内存,操作系统的内存。永久代会产生OOM
????2. 硬件的发展,之前的计算机硬件比较落后,没多少内存使用。如果还让JVM使用直接内存的话,会导致没有多少内存供其它程序使用。随着硬件的发展,现在的机器能够达到32G 64G这样的,有足够的内存使用。32位的机器最大的内存就是4G,64位的机器最大的内存可以达到2的48次方,256T。64bit = 16(保留位) + 48
????最小20.75M,最大256T,相当于无限大。元空间调优技巧:最小最大设置成一样大,防止忽大忽小,动态扩展。一般设置成物理内存的1 / 32。
????局部变量表:存放方法中声明的局部变量
????操作数栈:变量需要进行运算所要用到的操作数栈
????动态链接:指向方法在方法区的内存地址,也就是指向下图中的main方法对象或者add方法对象的内存地址
????返回地址:恢复现场。如果main方法调用了add方法,add方法执行完后,需要返回到main方法前面执行到的指令,则add方法中的返回地址则记录了main方法中程序计数器所执行到字节码指令
????1. 恢复局部变量表指针
????2. 恢复操作数栈的指针
????3. 恢复程序计数器
????4. 如果方法有返回地址,则需要返回
????5. 清理栈帧(程序计数器做的)
????右边的图中我们可以看到new Test()所生成的字节码指令,并不是只有一条指令,而是由四条指令组成的,所以它不是一个原子操作。
0 new 3 dup 4 invokespecial #调用构造函数,初始化 7 astore_1
????这就是为什么在单例模式的双检查锁DCL中,为什么要加volatile关键字了,防止这四个指令的重排序问题
每个非静态方法,局部变量表index=0的位置永远存放的都是this指针,有点类似于python里面每个方法的第一个参数是self的意思,this指针在调用init构造方法时完成赋值
????默认大小是物理内存的1 / 64,最大是1 / 4
虚拟机栈 -> 方法区的联系
????动态链接
堆 -> 方法区
????klass Pointer类型指针:对象执行类的Class对象的内存地址
方法区 -> 堆
????静态变量:例如public static Test test = new Test(), 静态属性在方法区,对象在堆中
????未开启指针压缩:内存地址占8字节。开启指针压缩:内存地址占4位
????要计算一个对象的大小,首先需要了解一下对象在JVM中的一个内存布局
????Mark Word:
????????32位机:4个字节
????????64位机:8个字节
????指针压缩:
????????开启:4个字节
????????未开启:8个字节
????对齐填充:按8个字节对齐填充
????首先看一个简单的对象,计算它的对象大小(引入jol-core包,可以查看对象大小)
????开启指针压缩的情况下,像这种没有普通属性的对象被称为空对象,来看看它的对象大小16B是怎么算出来的
????????首先是Mark Word = 8B,因为是64位的机器上
????????其次是类型指针,默认开启指针压缩,则类型指针 = 4B
????????数组长度 = 0B
????????实例数据 = 0B
????????对齐填充:8B + 4B不是8B对齐,需要填充4B,对齐填充= 4B
???? 最后,Test对象大小 = 8B + 4B + 0B + 0B + 4B = 16B
????关闭指针压缩的情况下:Test对象大小还是16B = 8B + 8B + 0B + 0B + 0B
????来看一个具有普通属性的对象
????我们可以看到该Test类开启了指针压缩,具有一个普通属性a,short占2B,最后得出来的对象大小为16B = 8B(Mark Word) + 4B(类型指针) + 0B(数组长度) + 2B(实例数据) + 2B(对齐填充)
????关闭指针压缩
????发现该Test对象大小为24B = 8B + 8B + 0B + 2B + 6B
????来看一下数组对象所占的内存大小
????我们可以看到开启指针压缩的情况下,arr数组对象占32B
????我们可以看到arr数组对象关闭指针压缩的情况下,对象大小占40B,并且数组对象会有2个padding(对齐填充).
????什么情况下对象的内存布局会产生2个padding
????????对象是数组对象并且关闭指针压缩的情况下,会产生2个padding
????1. 节省JVM空间
????2. 提升JVM运行效率
????假设有三个对象test1 = 16B、test2 = 32B、test3 = 24B。
????在内存中的地址分别为:test1 = 0x00000、test2 = 0x10000、test3 = 0x30000
????因为JVM采用8字节对齐1000,后面三位一定是000。所以可以将test1, test2, test3后面三位抹掉,那么在储存时就会变为test1= 0x00、test2 = 0x10、 test3 = 0x30,而在使用时,则将地址后面的三位000补充回来
????我们知道开启指针压缩的情况下,类型指针占4字节,也就是32位,上一小节说到JVM储存时会抹掉后面的3位,也就是可以保存32 + 3 = 35位,最大内存空间也就是2的35次方,32G
????前面说到JVM采用8字节对齐,会抹掉后面的3位,如果我们让它采用16字节对齐,那么是不是可以抹掉最后面的4位,oop(对象指针)所能表示的堆空间则为2的32 + 4次方
????如果采用16字节对齐的话,在使用时对齐填充浪费内存空间
????可以通过命令java -XX:+PrintFlagsFinal -version | grep ThreadStack栈大小为1024KB
????JVM对虚拟机栈做了最小的限制,限制为160KB
????我们可以看到通过-Xss参数设置虚拟机栈大小为100KB,发现控制台报错,最小为160KB