【JVM】5.运行时数据区(堆、方法区、直接内存)

4.JVM的运行时数据区

4.4 Java虚拟机堆

一般Java程序中堆内存是空间最大的一块内存区域,创建的对象(成员变量)都位于堆上。

虚拟机栈帧的局部变量表中,可以存放堆上对象的引用。静态变量也可以存放堆对象的引用,通过静态变量就可以实现对象在线程之间共享(静态变量存放在方法区之中,这个之后再说)。

在这里插入图片描述

4.5 虚拟机堆内存溢出

写一个死循环,然后不断new出新对象放入一个列表中,最后会发现抛出异常OutOfMemory。这就说明堆的内存大小是有上限的,不够用就会出现堆内存溢出。

堆有三个需要关注的值:used、total、max。

used指的是当前已经使用的堆内存,total是Java虚拟机已经分配的可用堆内存,max是Java虚拟机可以分配的最大堆内存。随着程序运行,当used使用的内存已经达到total的大小时,JVM会扩大total的大小。但是不是total到了max才会出现内存溢出,事实上,total还没有达到max就会出现内存溢出。

在这里插入图片描述

通过arthas可以看到堆内存的实时情况。可以使用dashboard命令看到。当然,也可以使用memory这个命令只看内存的情况。

如何对堆的total和max进行设置呢?可以通过两个参数对堆的total和max进行设置。

在这里插入图片描述

在这里插入图片描述

Java服务端程序开发时,建议将-Xmx-Xms设置成相同的值,这样在程序启动之后,total就是max,不会出现内存达到total然后向JVM请求扩张total的情况,减少了时间开销。-Xmx与具体运行环境有关,之后会讲到。

4.6 Java虚拟机方法区

方法区是存放基础信息的位置,线程共享,主要包括三部分内容:类的元信息(保存了所有类的基本信息),运行时常量池(保存了字节码文件中的常量池内容),字符串常量池(保存了字符串常量)。

  • 类的元信息,一般称为InstanceKlass对象。这个对象在类的加载阶段创建完成。
  • 运行时常量池。字节码文件中,一般都是通过编号查表的方式找到常量,所以在字节码文件中的常量池称为静态常量池。当静态常量池被类加载器加载到内存之中后,可以通过内存地址快速定位到常量池之中的内容。这种常量池称为运行时常量池。
  • 字符串常量池,存储在代码中定义的常量字符串内容。

在这里插入图片描述

方法区在jdk8之前,是在堆中的永久代区域,这样设计不是很合理,因为耦合了。所以在jdk8及以后,方法区被设计放在直接内存的元空间区域。

在这里插入图片描述

运行时常量池和字符串常量池之间有什么关系?要明确方法区是一个虚拟概念,并不是一块内存区域,所以方法区中的不同部分可能有些分布在堆中(字符串常量池,静态变量),有些分布在直接内存中(类元信息、运行时常量池)。

在这里插入图片描述

要注意的是:JDK7及以后的版本中,静态变量存放在堆中的Class对象中,已经脱离了永久代。

总结一下:jdk8之后,堆中存放的内容有字符串常量池、静态变量、对象。

来做两道字符串常量池的题目:

在这里插入图片描述

在这里插入图片描述

下面这道题非常神奇。原因是,jdk6之中,intern()方法直接将对象中的字符串复制一份,放入字符串常量池之中,而jdk8,为了节省内存,intern()方法只是将对象中的字符串的引用放入字符串常量池中。所以,存放着think123的对象s1使用了intern()方法后,其实是将s1的引用放入了字符串常量池,这样s1.intern()==s1自然就是true了。那为何存放着java的s2与s2.intern()不相同呢?因为java在程序启动,核心类加载的时候就已经被放入字符串常量池之中了。s2.intern()发现字符串常量池之中已经有java,自然返回字符串常量池中java的引用,而不会将s2的引用放入字符串常量池之中。所以jdk8的结果是true和false。

在这里插入图片描述

4.7 虚拟机方法区内存溢出

如果类的信息太多,方法区内存也会出现溢出。写一个死循环,然后用框架去生成不同类的字节码,加载到内存中。经过测试,会发现在jdk1.7中,类的数量11万左右,会抛出异常OutOfMemoryError:PermGen Space,抛出的异常跟堆内存溢出的异常一样,这也可以侧面说明jdk1.7确实将方法区存放在堆之中。在jdk1.8之中,类的数量达到数百万个,都没有出现内存溢出。这也说明了jdk1.8及之后,与jdk1.7及之前,确实对方法区采用了不同的设计。

在这里插入图片描述

4.8 直接内存

直接内存并不在《Java虚拟机规范》中被定义,所以并不属于Java虚拟机运行时内存。

直接内存的出现主要是为了解决以下问题:

  • Java堆中的对象如果不再使用,需要使用垃圾回收器(之后会提到)进行回收,但是垃圾回收器回收堆中的对象,会影响性能。
  • IO操作比如读文件需要先把文件读入直接内存,然后再把数据复制到Java堆中,现在直接放入直接内存即可,同时Java堆上维护直接内存的引用,减少数据复制的开销。写文件也是一样,没有必要从Java堆中将数据复制到直接内存,而是从直接内存中将数据写入文件中即可。

总结一下就是:为了减少NIO使用过程中对用户的影响以及提升读写文件的效率。当然,从上面也可以看到,它在jdk8之后可以保存方法区中的数据。

在这里插入图片描述

在这里插入图片描述

4.9 运行时数据区域总结

程序计数器、虚拟机栈、本地方法栈是线程不共享的。每一个线程都有自己的程序计数器、虚拟机栈以及本地方法栈。

堆、方法区是线程共享的。不同线程共享堆和方法区。因为静态变量是在堆中的,所以静态变量是线程共享的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值