jvm(三)--java内存区域之线程共享区

前言

  本笔记作为jvm学习系列的第三篇,上一篇讲完了java内存区域中的线程独占区,这一篇来讲线程共享区的内容,也即堆与方法区,但是堆的内容有意思的东西实在太多了,像对象的创建,gc的回收算法,本地线程分配缓冲等等,所以,堆的内容我打算“大事化小”,拆分成多个针对性强一点的jvm笔记。

线程共享区

​ 线程共享区即在运行过程中,每个线程共享的内存空间。其中包含 方法区

  java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。

jvm(三)--java内存区域之线程共享区_第1张图片

  留个伏笔,既然堆空间是线程共享的区域,那么在创建对象的过程会有线程安全问题吗?

  此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。为什么说是 几乎所有的对象,而不是绝对性的说是 所有的对象 呢?

  因为对象空间是可以分配在栈上的,也即 栈上分配

jvm(三)--java内存区域之线程共享区_第2张图片

  早期的 java 版本总是会被其他语言所诟-- java 太慢了。这是因为 java 为了方便程序员,不需要让程序员自己去管理内存,而是让对象创建在堆中,然后让垃圾回收器(gc)不定期的去收集垃圾对象。而在方便了程序员的背后是多开一个任务去计算并回收对象,以及垃圾对象对服务器内存占用。所以若对象在栈上分配空间,当栈帧弹栈之后,jvm 就会将这部分内存空间直接回收了,也就意味着对象不需要麻烦 gc 来回收了

  这一点在 java 虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但是随着 jit 编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。(关于栈上分配后续会单独出一篇讲)

  java 堆可以是物理上不连续的空间,只要逻辑上连续即可,主流的虚拟机都是按照可扩展的方式来实现的。如果当前堆中没有内存完成对象实例的创建,并且不能在进行内存扩展,则会抛出 OutOfMemory 异常

  当对象在堆中创建,则当方法调用结束后,要依靠垃圾回收器去收集“垃圾”对象。那么,如何判断一个对象是“垃圾”对象,而“垃圾”对象又要怎么回收,这些都要根据垃圾回收器的算法,即,堆的设计是由垃圾回收器决定的,准确的说是由所用的垃圾收集算法决定的

  现代 jvm 的垃圾收集器基本都是采用的分代收集算法,简单的说,就是采用分而治之的思想,将对象进行分代,也就是将堆的内存区域进行分代:新生代与老年代

jvm(三)--java内存区域之线程共享区_第3张图片

  如图所示,堆内存分代成 新生代老年代 ,新生代中有分成 Eden(伊甸园)占比80% 与两块 Surivivor(存活区)各占10%

方法区

  在 java 虚拟机规范中,将方法区作为堆的一个逻辑部分来对待,但事实上,方法区并不是堆(Non-Heap)。

jvm(三)--java内存区域之线程共享区_第4张图片

  java 的 类与对象关系,就像这 章盖与印迹 一样,那么在 java 中,这个"印迹"是存在堆中,那”章盖“是存在哪呢?

  方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。所以这”章盖“就存在方法区中。

  方法区在物理上也不需要是连续的,可以选择固定大小或可扩展大小,并且方法区比堆还多了一个限制:可以选择是否执行垃圾收集。一般的,方法区上执行的垃圾收集是很少的,这也是方法区被称为 永久代 的原因之一(HotSpot),但这也不代表着在方法区上完全没有垃圾收集,其上的垃圾收集主要是针对常量池的内存回收和对已加载类的卸载。

  早期的 HotSpot Java 虚拟机的实现方式中,将分代收集的思想扩展到了方法区,并将方法区设计成了永久代。不过,除HotSpot之外的多数虚拟机,并不将方法区当做永久代,HotSpot本身,也计划取消永久代。

  在方法区上进行垃圾收集,条件苛刻而且相当困难,效果也不令人满意,所以一般不做太多考虑,可以留作以后进一步深入研究时使用。

  在方法区上定义了 OutOfMemoryError:PermGen space 异常,在内存不足时抛出。

  运行时常量池(Runtime Constant Pool)是方法区的一部分(jdk7 以后,常量池被放到了堆中 ),用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译);运行时常量池除了存储编译期常量外,也可以存储在运行时间产生的常量(比如 String 类的 intern() 方法,作用是 String 维护了一个常量池,如果调用的字符 “abc” 已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址。关于 intern() 内容,有兴趣可以看我的另一篇文章:关于java字符串比较例子引发的学习以及intern方法)。

你可能感兴趣的:(java)