乐趣区

每日五分钟玩转JVM线程共享区

前言

上一篇中,我们了解了 JVM 中的线程独占区,这节课我们就来了解一下 JVM 中的线程共享区,JVM 中的线程共享区是跟随 JVM 启动时一起创建的,包括堆(Heap)和方法区()两部分,而线程独占区的程序计数器,虚拟机栈,本地方法栈的生命周期都是跟随线程的,随线程的创建而诞生,随线程的销毁而销毁。

堆(Heap)

堆内存作为 JVM 管理的内存中最大的一块,用于存放我们的 对象实例 ,我们经常会把 JVM 的内存简单的分为堆内存和栈内存,这样说虽然有些片面,但是也有这么说的道理,这两块儿一个作为执行程序的,一个作为存放对象的,是 JVM 中最为重要的两块儿内存。所以,我们的垃圾收集一般是针对的用于存放对象的 堆内存,所以堆内存有时候也会被称为 GC 堆。

从内存分配的角度上来说,堆内存中包含了新生代内存和老年代内存,而年轻代又分为 Eden 和 Survivor 区。Survivor 区由 From Survivor 和 To Survivor 组成。Eden 区占大容量,Survivor 两个区占小容量,默认比例是 8:1:1,而且 JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。

这样设计的原因是为了更方便的进行垃圾收集,我们会在后面垃圾收集的章节中去详细的讲解。

TLAB

TLAB 的全称是 Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个处于 堆内存中线程私有 的内存分配区域,默认情况下这个区域就是开启的,当然我们也可以在启动时配置 XX:+UseTLAB 去开启该区域,这个区域所占空间非常的小,默认情况下只占 Eden 区域的 1%,我们也可以通过也 XX:TLABWasteTargetPercent 设置 TLAB 空间所占用 Eden 空间的百分比大小。

方法区

方法区存储虚拟机加载的 类信息 常量 静态变量 ,即时编译器 编译后的代码 等数据,在 Java 虚拟机的规范中,把方法区认为是堆内存的逻辑部分,但是实际上他们是完全隔离的。

在 JDK 8 之前,方法区被称为 (或者可以说是被实现为) 持久代,永久代(Perman Gen),而在 JDK 8 之后,取消了永久代的概念,取而代之的实现是元空间(MetaSpace),原本位于永久代中的运行时常量池和静态变量都存储到了堆中,而其余的内容则是移到了元空间。

元空间的本质和永久代类似,都是对 JVM 规范中 方法区的实现 ,它们之间最大的区别在于: 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:-XX:MetaspaceSize-XX:MaxMetaspaceSize

所以我们前几年 JDK7 盛行的时候 OOM 错误消息是这样的:

java.lang.OutOfMemoryError:PermGen space

而在近几年 JDK8 的使用中遇到的 OOM 是这样的:

java.lang.OutOfMemoryError:Metaspace

运行时常量池

运行时常量池位于元空间中,用于存储编译期生成的各种字面量和符号引用,而这里需要注意一点:字符串常量池 从 JDK 7 之后就移到了堆内存中去管理,但是运行时常量是仍然位于方法区基于 JDK 8 的新实现——元空间中。

网上有部分声音说运行时常量池在 JDK8 移到了堆内存中,其实这种说法是错误的,真正移到堆内存的是字符串常量池,并且是在 JDK7 的更新中就已经移到了堆中。

更详细的关于常量池的信息我们会在类结构中去学习。

后话

每天五分钟,跟 Vi 玩转 JVM!

下篇解密一个对象的诞生!

公众号

本文首发于公众号,扫码关注即可获取最新文章

退出移动版