【JVM】熟悉而又陌生的JVM (贰) - 内存模型详细讲解

无论是面试中还是工作中,处处离不开JVM的知识,前段时间了解了一些JVM内存模型相关的知识,本来没想卖弄,但是知识在于分享,取长补短,也并没有研究的多么透彻,就是简单的理解内存模型中有哪些区块以及作用。

开局一张图,内容全靠编

JVM内存分配

程序计数器

程序计数器是线程私有,每个线程有一个,互不干扰,相互独立。
JVM就是通过读取计数器的值来决定下一步进循环还是异常等。
计数器是存的字节码地址。
高速切换,运行速度极快。
内存很小,没有定义溢出,因为它始终是一个指针。
程序计数器是保存在处理器内部的,只需要知道是哪个线程的计数器,和这个线程当前运行的位置。
当它运行到java字节码的时候,会记录当前运行字节码的地址,当运行到本地方法的时候,则存的是一个空,与其说是空,确切的说是一个undefinded。
在同一时间,只能有一个线程在CPU上运行,那么就要保存线程当前运行到什么地方,程序计数器就是来保存运行到哪里的一个指针。
CPU是有一个时间片的,一个时间片只能有一个线程,一个时间片执行完了,所有的线程要抢下一个时间片。

PS:调用本地方法的时候,记录undefined,因为本地方法是C语言,JVM没有识别C语言,所以不记录。
NB:当调用本地方法的时候,JVM会给当前线程重新启动一个线程,这个新线程的计数器记录的是undefined,不影响老线程,因为老线程阻塞,直到新线程执行本地方法完事,唤醒老线程,老线程里面的计数器依然存着字节码的地址,故本地方法调用完了,依然知道从哪运行。

线程:线程私有
单位:帧,一个方法一个帧,帧里面存着方法的各种数据
动作:栈的操作只有帧的出栈和入栈。
步骤:**线程启动,分配栈,各种帧入栈出栈,当前方法执行完之后就要出栈,那么栈的最顶端就是这个方法的上一个方法,相当于把返回值带给了上一个方法。**
帧:保存[**局部变量表、操作数栈、动态链接、返回地址**]。

局部变量表
	单位:slot。
	存放基本数据类型,对象引用,returnAddress。
	其中基本数据类型有参数和局部变量,this隐藏参数,catch的异常对象等。
	每个成员都有自己的内存大小,方法的代码写完之后是固定的,多少个变量多少个引用不可能再修改,那么这局部变量表的大小在编译的时候就分配了。
	long和double占用2个slot,其余的类型占用1个slot。因为一个slot只能存放32位以内的数据,long和double是64位的。
	在局部变量表的第一位是当前引用,是一个默认的参数,就是this,所以在方法中使用this,其实this是一个隐藏的参数,这个参数也是在局部变量表存着。

操作数栈
	特点:后进先出
	作用:参数传递、临时数据存放,运算。
	解释:Java没有寄存器,所以使用操作数栈进行参数传递。临时数据表示临时把数据放进来进行操作。
	例子:100+98求和,参数为两个int,return两个int的和。
	100和98原本是存在局部变量表的,而操作数栈中暂时还没有这两个。
	1、依次把100和98入栈到操作数栈。
	2、100和98出栈,同时把和的结果198入栈。
	3、收到return指令,198在操作数栈中出栈,此时操作数栈没有东西了。
	4、198在操作数栈出站之后,保存到局部变量表。
	5、局部变量表收到198进行下面操作。

动态链接
	作用:存储运行时常量池该方法的引用。
	解释:常量池中存着大量的符号引用,有了一个自己方法的引用可以支持动态链接。动态链接的作用就是在指定的时间将常量池的符号引用变成直接引用拿来用。

返回地址
	作用:返回到上一个栈帧,如果有返回结果则带上返回结果。
	解释:任何一个方法有两种情况完成,一种是正常,无论返回类型是void还是String,另一种是出现异常。
	正常返回:当前栈帧出栈,拿着返回值回到上一个栈帧,并把返回值给这个栈帧。
	异常返回:当前栈帧出栈,即使有返回值也不带了,不在回上一个栈帧,如果找不到匹配的异常处理器,方法结束,直接抛异常。

线程:线程共享。
作用:存放对象,暴露一个引用地址给外部。
成员:年轻代,年老代,永久代。
年轻代
	所有新生成的对象都在年轻代,目的就是那些周期短的对象可以更快的GC。
	分成三个区,一个Eden,两个Survivor。也是为了GC。
	两个Survivor必须有一个是空的,如果满了要复制一部分给年老代。
	Survivor可以设置多个,不少于2个,目的就是为了减少去年老代。
年老代
	经过N次在年轻代GC之后,还活着的对象,复制过来。
	N次可以设置。
永久代
	即方法区。

方法区

	线程:线程共享。
	作用:存放类加载的信息。[类名,类路径,类描述,修饰符,类的类型(接口还是普通类)]。
	GC:如果某个类不要了,会进行垃圾回收。
	成员:运行时常量池、域信息、方法信息、静态变量。
	运行时常量池
	存储类所使用到的所有的类型,方法,和符号引用。
	运行时常量池是一个有序的集合,根据索引来查找。
	域信息
	域信息表示一块区域,比如域的修饰符决定是这个类可以用在哪块域上,public还是private。
	方法信息
	保存类中所有方法的信息,包括方法名,方法的修饰,方法的返回,方法的参数。
	之前说过栈里面的操作数栈在编译的时候就确定了大小,给这个栈帧的操作数栈定多大的内存,就是这里存储的。
	静态变量
	就是类的变量,属于类的,类的变量是所有实例共享的,即使没有类的实例也可以访问,说白了就是不用new,也可以.到,直接类名.变量就可以了,因为是静态的。
	还有一些数据结构,比如方法表,存着实例方法的直接引用等等。

直接内存

	作用:在计算机内存申请空间。
	特点:速度快于堆内存,也受限制,也需要分配。
	场景:适合频繁的IO,适合大数据存储,适合生命周期长的。
	因为在直接内存中分配,避免了堆之间的来回复制,所以速度快点。
	在堆中有一个DirectByteBuffer对象是专门来存储这块内存的引用的。
	具体怎么存放,该怎么存还是怎么存,没变化,知道具体的引用即可。就是有这么个东西。

元空间

	1.8以后新引入,废弃永久代。
	元空间的作用和永久代一样,只不过不在JVM的内存中,而是在本地内存。
	元空间可以设置大小,不设置的话,那么元空间将无限占有本地内存。
	在1.7的时候,就开始做废弃工作,将一部分原本在方法区的数据转移到堆中了。
	1.8以后彻底移除永久代和永久代的参数,新加元空间和相应的参数。
	移除永久代新增元空间主要目的就是为了方便GC。因为永久代的GC很不理想,而且还存在丢失等问题。元空间的内存分配和堆算是一样的,GC的时候方便管理。
	有了元空间并不是说从此没有OOM了,如果本地内存满了,会更糟糕。
	一个叫做MaxMetaspaceSize的参数在控制元空间多大的时候进行GC。
	元空间特点
	每个加载器有专门的存储空间
	不会单独回收某个类
	对象的位置是固定的
	如果某个加载器没有工作了,那么会回收这个加载器的整个空间。
	因为有了元空间,有一些东西不能存在元空间,就要存在堆,所以有了元空间,堆的压力也大了,所以要给堆多分配点空间。
	理解元空间的内存分配、容量、回收机制、对堆的影响、监控即可。

以上是我根据《深入理解JVM虚拟机》一书以及在他人博客中查阅所得,如果遇到不足之处或者错误之处,还请各位多多指点,相互学习。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值