深入JVM内存区域管理,值得你收藏 (2)

这个区域也是会发生GC的,垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行,适时地监控和调整元空间对于减小垃圾回收频率和减少延时是很有必要的。如果的元空间持续的发生GC说明可能存在类、类加载器导致的内存泄漏或是大小设置不合适,如果这个空间使用达到了MaxMetaspaceSize,但GC无法回收(所有的类信息都是有用的,所以无法回收),也会发生OOM错误。

String常量池已经从方法区(jdk8以前的叫法)中的运行时常量池分离到堆中了,不在元数据中。

Metaspace由两部分组成:Klass MetaSpace 和 NoKlass MetaSpace,Klass代表的是
class文件在jvm中运行时的数据结构,NoKlass专门用来存储Klass相关的其它数据,比如Method和ConstantPool。

回答刚开始的问题

深入JVM内存区域管理,值得你收藏

用一段代码分析JVM内存的存储 new Thread(new Runnable() { @Override public void run() { test(); } public void test(){ Object obj = new Object(); } }).start();

上面这段代码很简单,启动了一个线程,线程的run方法中调用了test方法,test方法中创建一个Objet对象,一起来看一下这段代码涉及的JVM内存哪些区域,分别存储了什么。

首先创建了一个线程,那么这个线程对应的私有的虚拟机栈内存肯定被分配,这个线程的代码执行中对应的程序计数器内存肯定被分配,因为没有涉及到本地方法,所有本地栈内存不会分配,而且虚拟机栈内存是在编译器就确定的。

Test方法执行时,创建一个Object对象,我们知道obj是一个引用(reference)类型,所以obj保存在Java栈的本地变量表中,而在Java堆中会保存该引用的实例化对象,Java堆中还必须包含能查找到此对象类型数据的地址信息(如对象类型、父类、实现的接口,方法等)这些类型数据则保存在元数据区域中。一般对象引用到对象实例和对象类型指向有两种方法,一种是句柄池方式,一种是直接指针方式。这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference中存放的是稳定的句柄地址,在对象的移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式的最大好处是速度快,它节省了一次指针定位的时间开销。目前Java默认使用的HotSpot虚拟机采用的便是是第二种方式进行对象访问的,下面用两张图来表述一下这两种方式。

这张图是句柄池方式

深入JVM内存区域管理,值得你收藏

这张图是直接指针方式

深入JVM内存区域管理,值得你收藏

关于基本数据类型和引用类型的分配

基本数据类型包括 int short long bolean等,引用类型就是我们常见的对象,那么这两种数据类型内存中是怎么分配的呢?这个得区别看待,我们根据下面代码来分析

class Dog { private int age; } class Test{ public void test(){ Dog dog = new Dog(); dog.age = 2; int age = 1; Integer age = new Integer(3); } }

在Test类中的test方法中,我们创建了一个Dog对象,这个对象实例是分配在堆上的,dog这个引用是在栈上的,dog中的age在哪里呢?因为Dog对象实例是在堆上的,所有他的成员变量也是在堆上的。 int age这个变量是栈上的,因为它是局部变量,并且是基本数据类型,Integer age实例是在堆上的,引用是在栈上的,根据这个例子,可以总结下面两条基本黄金法则

引用类型总是被分配到“堆”上。

值类型总是分配到它声明的地方:    a. 作为引用类型的成员变量分配到“堆”上    b. 作为方法的局部变量时分配到“栈”上

总结

本文详细介绍了JVM内存区域的各个情况,也就是JVM内存模型,也解答了一些常见的面试题和内存分配相关的一些问题,希望能够帮助到读者更好的了解到JVM,可能会有人有些疑问,为什么不说堆内存的分代(年轻代,年老代)问题呢?我认为这个属于JVM垃圾回收的方位,分代思想只是解决垃圾收回问题的一种方法,同理,Java8中G1的region也是一样,都是为了解决垃圾回收效率和性能问题,会放在JVM垃圾回收一文来说。

深入JVM内存区域管理,值得你收藏

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzdpyy.html