目录
0 引言
针对JVM常见问题的总结,主要就是分为JVM内存模型、类加载系统、垃圾回收几个部分,主要参考周志明老师的《深入理解Java虚拟机》一书
1 JVM内存结构
JVM内存结构指的是JVM运行时数据区的内存分布,共分为五个部分,分别是虚拟机栈、本地方法栈、程序计数器、堆和方法区,其中虚拟机栈,本地方法栈和程序计数器是线程私有的,堆和方法区是线程共享的。对这些分区的接受如下:
堆
堆区是用来存放Java对象的地方,我们的Java对象基本都是存放在堆中,同时堆区也是JVM垃圾回收最频繁的地方,可能出现OOM
方法区
方法区主要存放了类型数据,存在垃圾回收,但是概率十分小,因为Java中要回收一个类的条件是十分苛刻的,需要注意的是在JDK7以后方法区挪到本地的直接内存中(这样可以看到垃圾回收的概率更小了),在JDK8中方法区也正式更名为元空间
程序计数器
程序计数器是线程私有的,记录了当前正在执行的字节码指令的地址。注意对于本地方法执行时,程序计数器的值为null
本地方法栈
执行本地方法,也就是所谓的native方法的时候是在本地方法栈中进行
虚拟机栈
虚拟机栈是线程私有的,描述的是Java方法执行的线程内存模型,每个方法被执行的时候,JVM都会创建一个栈帧,每一个方法被调用和返回的过程就对应这个一个栈帧的入栈和出栈的过程,栈帧中还有五个基本结构,如下:
-
局部变量表
是一个数组,编译期就确定了大小,数组的每一个存储空间我们称之为1个槽,一个槽能存储32位的数据,我们的对象引用和除long,double外的基本数据类型都只占一个槽,long和double数据占用两个槽,需要强调的一点是,对于非静态方法,其局部变量表中有一个this指针,静态方法中是没有this指针了,这也解释了为什么静态方法中只能使用静态属性
-
操作数栈
我们数据的计算都是在操作数栈中进行的,操作数栈主要用户存储计算的中间结果和临时存放操作数
-
方法返回地址
方法执行完后需要返回该方法被调用处,方法返回地址保存了原方法中该方法执行完后下一条指令的地址
-
动态链接
动态链接是一个指向方法区的引用,指向了该方法的元数据信息,即通过动态链接可以知道该方法是哪个类的方法
-
附加信息
除以上信息外的其他信息
2 Java对象的创建过程
java对象的创建过程主要分为6个步骤,具体过程如下
-
首先查看要创建的对象所对应的类是否已经被加载了,如果没有则需要先加载该对象对应的类(加载的过程也是先加载父类再加载子类)
-
在堆区中给对象实例分配内存空间,这里有两种内存空间的分配方法:
- 内存规整的情况下采用指针碰撞法,就是直接移动指针
- 内存不规整的情况下采用空闲列表法,内存不规整也就是说存在内存碎片,将空闲的内存维护在一个空闲列表中,然后根据对象大小在空闲列表中选择合适的空闲内存进行内存分配
-
处理一些并发安全问题
这是给对象分配内存空间的过程中需要考虑的问题,因为对象的创建非常频繁,若在对象A创建的过程中(指针还未来得及修改)又创建对象B就会产生并发安全的问题,为解决这个问题,虚拟机采用的CAS+失败重试;同时我们还可以给线程分配独立的TLAB,这样线程要创建对象时优先在TLAB中进行内存分配,TLAB不够的时候再在共享的堆内存中进行分配
-
字段初始化,这时候需要对实例数据进行默认初始化,默认初始化的目的是为了使java程序中的字段在未赋值的情况下能够直接使用
-
设置对象头
-
调用方法进行对象字段初始化,代码块初始化以及构造器初始化
3 Java中对象的内存布局
对象的内存布局如下:
-
对象头
-
markword
存储对象的运行时状态数据,比如哈希码,GC分代年龄,锁状态标志,偏向线程ID等信息
-
k
-