深入理解JAVA虚拟机学习笔记(一)JVM内存模型
上周末搬家后,家里的宽带一直没弄好,跟电信客服反映了N遍了终于约了个师傅明天早上来迁移宽带,可以结束一个多星期没网的痛苦日子了。这段时间也是各种忙,都一个星期没更新博客了,再不写之前那种状态和激情都要慢慢褪去了,总觉得心里慌的一逼,这怎么行呢?!趁明天周末,在公司电脑上记录下这周的一些学习内容。近期在看一本很经典的java书籍:《深入理解java虚拟机 第二版》,几年前也翻过,但那时候功力不够,不太能看懂就没看了。现在回过头来看,发现确实写的很好,很多知识点都能理解了,而且讲的也很有深度,收获颇多。后期计划按照这本书的内容写出一系列博客,来深入学习和复习下java虚拟机相关的知识。
一、JVM内存模型概述
JVM内存模型其实也挺简单的,这里先提2个知识点:
1、组成:java堆,java栈(即虚拟机栈),本地方法栈,方法区和程序计数器。
2、是否共享:其中方法区和堆区是线程共享的,虚拟机栈,本地方法栈和程序计数器是线程私有的,也称线程隔离的,每个区域存储不同的内容。这2个知识点必须牢记,是掌握JVM内存模型的基础。
二、程序计数器
JVM中的程序计数器是一块很小的内存区域,但是这块内存区域挺有意思的。主要特性有3个:
1、存储内容:对于java普通方法(即没用native关键字修饰的方法),存储的是执行过程中当前指令的地址,而对于native方法,这里是空的(undefined),为啥呢?因为调用本地方法的时候可能已经超出了JVM虚拟机的内存地址了。
2、线程私有的:为什么程序计数器是线程私有的?根据存储内容也好理解,假如是线程共享的,那多个线程执行的时候,都不知道自己当前线程执行的地址是哪个了,有的线程快,有的线程慢,快的执行完就进入下一步,等慢的线程执行完回来发现自己的地址都变了,岂不乱套?
3、是JVM中唯一不会报内存溢出(OutOfMemoryError)的区域。
三、虚拟机栈
虚拟机栈主要存储的是一个个栈帧,每个栈帧中存储的是局部变量表,操作数栈,动态链接和方法出口信息等。其中局部变量表中存储的是方法中定义的一些局部变量,对象的引用,参数,和方法的返回地址等。局部变量表所占用的空间大小在编译期就能确定,在方法运行的时候,并不会改变局部变量表的空间大小,这结合局部变量表存储的内容就很好理解。操作数栈可以理解为对当前操作的数据入出栈,对于64位长度的long和double类型,每个操作数占用2个字宽(slot),其他类型的操作数占用一个字宽(slot)。每个方法调用时都会创建一个栈帧,执行的过程对应的就是一个栈帧在虚拟机栈中从入栈到出栈的过程。有关栈帧的内容可以参考一个网友写的一篇博客:https://blog.csdn.net/xtayfjpk/article/details/41924283,讲的很好很详细。这里放个栈帧的图,看了一目了然。
关于虚拟机栈内存溢出有2种情况:
1、线程请求的栈深度 超过了虚拟机允许的深度,会抛出StackOverflowError,所以当我们在代码中看到这个异常时,就应该想到可能是虚拟机栈出了问题。
2、如果虚拟机栈可以动态扩展(当前大部分JVM都可以动态扩展,不过JVM也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时,会抛出OutOfMemoryError异常。
四、本地方法栈
这块知识点比较简单,本地方法栈和虚拟机栈的功能类似,只不过是为JVM调用native方法时服务的,而且JVM对本地方法使用的语言(比如Java调用C语言实现的功能,就需要定义native方法来实现)、使用方式和数据结构都没有强制规定,因此不同的虚拟机可以自由实现。而且HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一。与虚拟机栈类似,本地方法栈也会抛出StackOverflowError和OutOfMemoryError。
五、方法区
方法区是一个比较重要的区域,java虚拟机规范中把方法区描述为堆的一个逻辑部分,但是为了和Heap(堆区)对应,也称Non-Heap(非堆区)。主要存储的是静态变量,常量(包括运行时常量),类的加载信息和java编译后的代码。这部分空间不需要连续,可以选择固定大小和可扩展,通常在这部分是没有GC的,因为GC回收的都是些静态变量,常量和类的加载信息,这些对象回收效果通常不尽人意,因此可以选择不实现垃圾回收。这块区域也称为持久代,当这块内存不足时,也会报OutOfMemoryError异常。
六、堆区
Java堆区是JVM内存中最胖的一块区域,因为这里存储的都是对象的实例和数组对象。这块区域是线程共享的,在JVM启动时就会创建,想想如果这么大的空间是线程私有的,那内存不得爆掉吗?按照java虚拟机规范,堆区的内容可以物理上不连续,只要逻辑上连续即可,在实现时可以是固定大小的,也可以是可扩展的,而且通常都是可扩展的,我们常用的内存参数-Xms和-Xmx就是用来调节堆大小的。java堆区按生命周期不同,分为新生代和老年代。新生代又可以细分为Eden和Survivor区,而Survivor又可以细分为Survivor1和Survivor2,这两者通常只使用其中一块,另一块用来GC时保留存活的对象。大部分的new出来的对象都是存放在Eden区,如果是大对象,比如一个很大的数组或者List对象,可以通过JVM参数-XX:PretenureSizeThreshold将超过指定大小的对象直接存入到老年代,需要注意的是,写程序时应该尽量避免朝生夕死的大对象进入老年代,因为相比年轻代的GC,老年代GC的成本更大。Eden和Survivor的默认大小比值的8:1:1,新生代默认的GC算法是复制算法。老年代的默认GC算法是标记整理法。关于这2种GC算法,会在下篇博客讲解。
当堆中没有足够内存时,会抛出OutOfMemoryError异常。关于堆区的内存模型,可以参考下面的图片:

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
从基础到实现:集成学习综合教程(附Python代码)
介绍 当你想购买一辆新车时,你会走到第一家汽车商店就根据经销商的建议购买一辆车吗?这是不太可能的。 你可能会浏览一些人们发布评论并比较不同车型的门户网站,检查其功能和价格。你也可能会问你的朋友和同事们的意见。总之,你不会直接得出结论,还会参考其他人的意见做出决定。 机器学习中的集成模型也是类似的思路。它们结合了多个模型的决策来提高整体性能。这可以通过各种方式实现,本文将会带你一起探索。 本文的目的是介绍集成学习的概念并理解使用这种技术的算法。为了巩固你对这个多元化主题的理解,我们将用真实问题的动手案例,配合Python来解释其中的高级算法。 注意:本文假定你对机器学习算法有基本的了解。我建议阅读这篇文章以熟悉这些概念。 目录 集成学习介绍基础集成技术最大投票(Max Voting)法平均(Averaging)法加权平均(Weighted Ave
- 下一篇
Java 程序执行过程的内存分析
内存分析先知 栈: 1、每个线程私有,不能实现线程间的共享! 2、局部变量放置于栈中。 3、栈是由系统自动分配,速度快!栈是一个连续的内存空间! 堆: 1、放置new出来的对象! 2、堆是一个不连续的内存空间,分配灵活,速度慢! 方法区: 1、被所有线程共享! 2、用来存放程序中永远不变或唯一的内容。(类代码信息、静态变量、字符串常量) 注:常量池具有共享的机制,不同类的常量池之间共享数据。比如:Student 类常量池下有个 "老叶"字符串常量,那么当 Test 类中用到"老叶"这个字符串常量时就会去方法区的其他类的常量池中找,刚好 Student 类有,所以它直接就引用Student 类的"老叶" 开始分析Student 类 public class Student { //静态的数据 String name; int id; //学号 int age; String gender; int weight; //动态的行为 public void study(){ System.out.println(name+"在学习"); } public void sayHello(Stri...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8编译安装MySQL8.0.19
- CentOS关闭SELinux安全模块
- Red5直播服务器,属于Java语言的直播服务器
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池