JVM 进阶知识

Java的跨平台性

    Java具有跨平台性,无疑是JVM底层翻译出来的汇编指令的不同,Unix和Window系统的汇编指令是不同的,Windows派系采用的是Intel汇编,Unix派系采用的是AT&T汇编。无论在哪个平台上编写的Java文件,编译后的class文件,放在哪个平台上都可以执行,只要下载平台相对应的JDK,都可以执行这个class文件,从而翻译出跟平台相对应的汇编指令


class文件、class content、class对象、对象四个基础概念

    class文件: 这个应该不用多说,就是java文件编译后的class文件

    class content: 首先来看一张类加载的图

    我们的class文件是存在在硬盘上的,需要将文件从硬盘读取到内存中,通过类加载将class文件内容加载到内存中,则会在类加载子系统中形成一块class content表示class 文件内容

    class对象:用过反射的网友应该清楚Class<T>这个对象,这就是class对象,类加载器通过解析class content内容则会在方法区(永久代)中形成Class<T>对象,像这个class对象的字段,方法的描述信息都会放在方法区中

    对象:对象则就是指我们new出来生成的对象


JDK1.8后元空间替代永久代

    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方法中程序计数器所执行到字节码指令


一个方法执行完JVM需要做的事情

    1. 恢复局部变量表指针

    2. 恢复操作数栈的指针

    3. 恢复程序计数器

    4. 如果方法有返回地址,则需要返回

    5. 清理栈帧(程序计数器做的)


new操作不是原子操作

    右边的图中我们可以看到new Test()所生成的字节码指令,并不是只有一条指令,而是由四条指令组成的,所以它不是一个原子操作。

0 new 
3 dup
4 invokespecial #调用构造函数,初始化
7 astore_1

    这就是为什么在单例模式的双检查锁DCL中,为什么要加volatile关键字了,防止这四个指令的重排序问题


每个非静态方法,局部变量表index=0的位置永远存放的都是this指针,有点类似于python里面每个方法的第一个参数是self的意思,this指针在执行dup指令时完成赋值,调用构造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运行效率


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补充回来


开启指针压缩的情况下,一个oop(对象指针)能表示最大堆空间是多少

    我们知道开启指针压缩的情况下,类型指针占4字节,也就是32位,上一小节说到JVM储存时会抹掉后面的3位,也就是可以保存32 + 3 = 35位,最大内存空间也就是2的35次方,32G


oop(对象指针)如何扩容

    前面说到JVM采用8字节对齐,会抹掉后面的3位,如果我们让它采用16字节对齐,那么是不是可以抹掉最后面的4位,oop(对象指针)所能表示的堆空间则为2的32 + 4次方


JVM为什么不是16字节对齐

    如果采用16字节对齐的话,在使用时对齐填充浪费内存空间


虚拟机栈默认大小

    可以通过命令java -XX:+PrintFlagsFinal -version | grep ThreadStack栈大小为1024KB


虚拟机栈最小为160KB

    JVM对虚拟机栈做了最小的限制,限制为160KB

    我们可以看到通过-Xss参数设置虚拟机栈大小为100KB,发现控制台报错,最小为160KB

posted @ 2020-07-26 20:36  半分、  阅读(393)  评论(4编辑  收藏  举报