线程隔离:各个
线程间
的这部分内存空间互不影响,相互独立存储
线程共享:所有线程共享同一块内存
方法区
堆
当前线程所执行的字节码的行号指示器,虚拟机(字节码解释器)在工作时通过改变计数器的值来选取下一条需要执行的字节码指令。
Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的。在一个确定的时刻,一个处理器
都只会执行一条线程中的指令。为了保证线程切换后能够恢复到正确的执行位置,每条线程都需要有一个单独的程序计数器,各个线程的计数器之间互不影响,独立存储,我们称这类内存区域为“线程私有”的内存
该区域不会出现OOM(内存溢出)错误
如果该线程正在执行的是一个java方法,计数器记录的是正在执行的虚拟机字节码指令定制
如果是native方法,计数器值为空(undefined).
描述Java方法执行的线程内存模型,每个方法被执行时候,JVM都会同步创建一个栈帧——用于存储局部变量表、操作数栈、动态连接、方法出口等信息
。每个方法的被调用到执行执行结束都对应着一个栈帧从虚拟机栈中从入栈到出栈的过程。
与对象内存分配关系最密切的区域就是 “栈”和“堆”,其中的“栈”通常是指这里的虚拟机栈、更多的情况下指的是栈帧中用于存放变量的局部变量表
局部变量表存放了编译期可知的各种JVM基本数据类型:
除了8个基本数据类型外,还有对象引用(reference,不等于对象本身,也有可能是一个指向对象起始地址的引用指针
)、returnAddress类型(返回地址、指向一条字节码指令的地址)
和1.2java虚拟机栈类似,不同的是虚拟机栈只为java方法服务,本地方法栈为JNI(虚拟机使用的本地方法服务)。
简单地讲,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。
java使用起来非常方便,然而有些层次的任务用java实现起来不容易
java需要与一些底层系统如操作系统或某些硬件交换信息时的情况
。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解java应用之外的繁琐的细节。如果我们要使用一些java语言本身没有提供封装的操作系统的特性时,我们也需要使用本地方法。
java堆的目的是用于存放对象实例,所有的对象实例和数组都在堆上进行分配
垃圾回收发生在java堆中,由于目前大部分垃圾回收器都是基于分代收集理论设计
的,所以在对堆中的定义又经常会出现“新生代”“老年代”“永久代”“Eden空间”“From Survivor空间”“To Survivor空间”等名词。
Java堆是垃圾收集器管理的内存区域,将Java堆细分的目的只是为了更好地回收内存,或者更快地分配内存。
Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的,这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。
存储被虚拟机加载的类型信息
、常量、静态变量
、即时编译器编译后的代码缓存等数据
《Java虚拟机规范》对方法区的约束是非常宽松的,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域的确是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。
但在JDK8中“永久代”这个名称已经被废弃,而采用 元空间
的概念.
产生这种变化的原因很多,简单的来说就是以下几点:
垃圾收集器的分代设计扩展至方法区
,或者说使用永久代来实现方法区而已(也就是方法区中的内存其实是用堆的垃圾收集器去进行收集,虽然方法区和堆在逻辑上是不同的两个概念、但是却因为采用同一套设计导致耦合)Java7及以前版本的Hotspot中方法区位于永久代中。永久代的垃圾收集是和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。
JDK8以前的运行时数据区分布如下,虽然方法区和堆在逻辑上是隔离的,但实际上在物理空间上是连续的。
JDK8以后,采用元空间的概念,方法区存在于元空间内,同时元空间所在的内存,物理上不再与堆连续,而是存在于本地内存中。元空间存在于本地内存,意味着只要本地内存足够,默认情况下元空间是可以无限使用本地内存的,但为了不让它如此膨胀,JVM同样提供了参数来限制它使用的使用。
元空间存储的是类的元信息,堆里面存储了静态变量和常量。
答案:堆中
先说明一下:以前的永久代是用来保存类的信息的,然后Java8将其一分为二,一部分是元空间,另一部分放到堆了。
代码有两部分:一部分是声明好大好大的静态数组,分别是静态数组和常量数组,然后两种情况堆都溢出了,第二部分是是使用cglib生成大量类,元空间溢出。
所以:元空间存储的是类的元信息,堆里面存储了静态变量和常量。
参考 为什么永久代消失了
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(ConstantPool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
在 JDK 1.6 以及以前的版本中,字符串池是放在 Perm 区(Permanent Generation,永久代),JDK7后字符串池移到Java Heap
看一道比较常见的面试题,下面的代码创建了多少个 String 对象?
String s1 = new String("he") + new String("llo");
String s2 = s1.intern();
System.out.println(s1 == s2);
// 在 JDK 1.6 下输出是 false,创建了 6 个对象
// 在 JDK 1.7 之后的版本输出是 true,创建了 5 个对象
// 当然我们这里没有考虑GC,但这些对象确实存在或存在过
1234567
为什么输出会有这些变化呢?主要还是字符串池从永久代中脱离、移入堆区的原因, intern()
方法也相应发生了变化:
intern()
首先会在字符串池中寻找 equal()
相等的字符串,假如字符串存在就返回该字符串在字符串池中的引用;假如字符串不存在,虚拟机会重新在永久代上创建一个实例,将 StringTable 的一个表项指向这个新创建的实例。intern()
做了一些修改,更方便地利用堆中的对象。字符串存在时和 JDK 1.6一样,但是字符串不存在时不再需要重新创建实例,可以直接指向堆上的实例。(也就是S2直接指向S1)直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现,所以我们放到这里一起讲解。在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制,一般服务器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。
原文:https://www.cnblogs.com/LeeBoom/p/jvm-ji-chu-gai-nian.html