温州网站开发定制,网站建设首期款,山东省住房和城乡建设厅文件,龙泉驿区城乡建设局网站文章目录 JVM内存结构图1、运行时数据区域JDK 1.7JDK 1.81. 线程栈#xff08;虚拟机栈#xff09;2. 本地方法栈3. 程序计数器4. 方法区#xff08;元空间#xff09;5. 堆6、运行时常量池#xff08;Runtime Constant Pool#xff09;7、直接内存#xff08;Direct Me… 文章目录 JVM内存结构图1、运行时数据区域JDK 1.7JDK 1.81. 线程栈虚拟机栈2. 本地方法栈3. 程序计数器4. 方法区元空间5. 堆6、运行时常量池Runtime Constant Pool7、直接内存Direct Memory 2、JVM中对象及常量、局部变量、全局变量的存储位置1. 局部变量2. 全局变量3、JVM内存参数 4、堆和栈的区别5、JVM对象创建过程5.1、类加载检查5.2、分配内存5.2.1、内存分配方式5.2.2、内存分配并发问题 5.3、初始化零值5.4、设置对象头5.5 、执行初始化init方法 参考文章
JDK1.8 JVM运行时数据区域划分JVM内存结构
JVM内存结构图 1、运行时数据区域
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。
JDK 1.8 和之前的版本略有不同我们这里以 JDK 1.7 和 JDK 1.8 这两个版本为例介绍。
JDK 1.7 JDK 1.8 不同虚拟机的运行时数据区可能略微有所不同但都会遵从 Java 虚拟机规范 Java 虚拟机规范规定的区域分为以下 5 个部分其中线程私有程序计数器、虚拟机栈、本地方法栈线程共享堆、方法区、直接内存。
线程私有的
程序计数器虚拟机栈本地方法栈
线程共享的
堆方法区直接内存 (非运行时数据区的一部分)
Java 虚拟机规范对于运行时数据区域的规定是相当宽松的。以堆为例堆可以是连续空间也可以不连续。堆的大小可以固定也可以在运行时按需扩展 。虚拟机实现者可以使用任何垃圾回收算法管理堆甚至完全不进行垃圾收集也是可以的。
注意 jdk1.8里静态变量和字符串常量池放入堆中运行时常量池仍然在方法区元空间。
详情看这篇字符串常量池
1. 线程栈虚拟机栈 JVM的每一个线程对应一个线程栈一个线程的每个方法会分配一块栈帧内存空间。**栈帧中包含局部变量表、操作数栈、动态链接和方法出口**。局部变量表存储基本数据类型(int、float、byte等)如果是引用数据类型则存储的是其在堆中的内存地址也就是指向对象的一个指针。 操作数栈操作数运算时一块临时的空间来存放操作数。 动态链接将代码的符号引用转换为在方法区运行时常量池中的直接引用。 方法出口存储了栈帧中的方法执完之后回到上一层方法的位置。 Java 虚拟机栈会出现两种错误StackOverFlowError 和 OutOfMemoryError。 StackOverFlowError 若 Java 虚拟机栈的内存大小不允许动态扩展那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候就抛出 StackOverFlowError 错误。OutOfMemoryError 若 Java 虚拟机栈的内存大小允许动态扩展且当线程请求栈时内存用完了无法再动态扩展了此时抛出 OutOfMemoryError 错误。 2. 本地方法栈
运行本地方法的空间也就是native本地方法运行时的一块空间。 和虚拟机栈所发挥的作用非常相似区别是 虚拟机栈为虚拟机执行 Java 方法 也就是字节码服务而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
方法执行完毕后相应的栈帧也会出栈并释放内存空间也会出现 StackOverFlowError 和OutOfMemoryError 两种错误。
3. 程序计数器 程序计数器是用于存放下一条指令所在单元的地址的地方。当执行一条指令时首先需要根据PC中存放的指令地址将指令由内存取到指令寄存器中此过程称为“取指令”。与此同时PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令执行指令。完成第一条指令的执行而后根据PC取出第二条指令的地址如此循环执行每一条指令。 程序计数器是线程私有的属性其主要有两个作用
字节码解释器通过改变程序计数器来依次读取指令从而实现代码的流程控制如顺序执行、选择、循环、异常处理。在多线程的情况下程序计数器用于记录当前线程执行的位置从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了
4. 方法区元空间
线程共享的一块区域它用于存储已被虚拟机加载的类信息、常量、静态变量、运行时数据区等数据。
JDK 8 版本之后方法区HotSpot 的永久代被彻底移除了JDK1.7 就已经开始了取而代之是元空间元空间使用的是直接内存。 另外JDK1.8后字符串常量池和静态变量存储到了堆中类的元数据及运行时常量池存储到元空间中。 5. 堆 堆是运行时数据区所有类的实例和数组都是在堆上分配内存。它在 JVM 启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。Java 虚拟机所管理的内存中最大的一块线程共享此内存区域的唯一目的就是存放对象实例几乎所有的对象实例以及数组都在这里分配内存。Java 堆是垃圾收集器管理的主要区域因此也被称作GC 堆Garbage Collected Heap。
堆这里最容易出现的就是 OutOfMemoryError 错误并且出现这种错误之后的表现形式还会有几种比如
OutOfMemoryError: GC Overhead Limit Exceeded 当JVM花太多时间执行垃圾回收并且只能回收很少的堆空间时就会发生此错误。java.lang.OutOfMemoryError: Java heap space :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发java.lang.OutOfMemoryError: Java heap space 错误。(和本机物理内存无关和你配置的内存大小有关)
6、运行时常量池Runtime Constant Pool
用于存放编译期生成的各种字面量和符号引用当常量池无法再申请到内存时会抛出 OutOfMemoryError 错误。 JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代 JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代 。 JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
7、直接内存Direct Memory
直接内存并不是虚拟机运行时数据区的一部分也不是虚拟机规范中定义的内存区域但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。 JDK1.4 中新加入的 NIO(New Input/Output) 类引入了一种基于通道Channel 与缓存区Buffer 的 I/O 方式它可以直接使用 Native 函数库直接分配堆外内存然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能因为避免了在 Java 堆和 Native 堆之间来回复制数据。 本机直接内存的分配不会受到 Java 堆的限制但是既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
2、JVM中对象及常量、局部变量、全局变量的存储位置
1. 局部变量
基本数据类型变量名和变量值存储在方法栈中。引用数据类型变量值存储在方法栈中存储的是堆中对象的内存地址所指向的对象是存储在堆内存中如new出来的对象。
2. 全局变量
基本数据类型变量名和变量值存储在堆内存中。引用数据类型变量名存储的是所引用对象的内存地址变量名和变量值存储在堆内存中。
3、JVM内存参数
默认堆中年轻代(Young)占1/3老年代(Old)占2/3年轻代中包含Eden区和Survivor区Survivor区包含From(S0)区和To(区)默认新生代中Eden区、From区、To区的比例为8:1:1当Eden区内存不足时会触发Minor gc没有被回收的对象进入到Survivor区同时分代年龄1当再次触发Minor gc时From区中的对象会移动到To区Minor gc会回收Eden区和From区中的垃圾对象对象的分代年龄会一次次的增加当分代年龄增加到15以后对象会进入到老年代。
当老年代内存不足时会触发Full gc如果Full gc无法释放足够的空间会触发OOM内存溢出在进行Minor gc或Full gc时会触发STWStop The World即停止用户线程。
Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里)
java -Xms2048M -Xmx2048M -Xmn1024M -Xss512K -XX:MetaspaceSize256M -XX:MaxMetaspaceSize256M -jar microservice-eureka-server.jar-Xss每个线程的栈大小 -Xms设置堆的初始可用大小默认物理内存的1/64 -Xmx设置堆的最大可用大小默认物理内存的1/4 -Xmn新生代大小 -XX:NewRatio默认2表示新生代占年老代的1/2占整个堆内存的1/3。 -XX:SurvivorRatio默认8表示一个survivor区占用1/8的Eden内存即1/10的新生代内存。
关于元空间的JVM参数有两个-XX:MetaspaceSizeN和 -XX:MaxMetaspaceSizeN
-XXMaxMetaspaceSize 元空间最大值 默认-1 即不限制或者说只受限于本地内存大小。
-XXMetaspaceSize 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小) 以字节为单位默认是21M左右达到该值就会触发full gc进行类型卸载 同时收集器会对该值进行调整 如果释放了大量的空间 就适当降低该值 如果释放了很少的空间会适当提高该值 如果设置了-XXMaxMetaspaceSize不会超过其最大值 。这个跟早期jdk版本的-XX:PermSize参数意思不一样-XX:PermSize代表永久代的初始容量。
由于调整元空间的大小需要Full GC这是非常昂贵的操作如果应用在启动的时候发生大量Full GC通常都是由于永久代或元空间发生了大小调整基于这种情况一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值并设置得比初始值要大对于8G物理内存的机器来说这两个值可以都设置为256M。
4、堆和栈的区别
物理地址 堆的物理地址分配对对象是不连续的。因此性能慢些。栈使用的是数据结构中的栈先进后出的原则物理地址分配是连续的。所以性能快。 内存分配 堆因为是不连续的所以分配的内存是在运行期确认的因此大小不固定。一般堆大小远远大于栈。栈是连续的所以分配的内存大小要在编译期就确认大小是固定的。 存放的内容 堆存放的是对象的实例和数组。因此该区更关注的是数据的存储栈存放局部变量操作数栈返回结果。该区更关注的是程序方法的执行。 静态变量放在方法区静态的对象还是放在堆。 × 静态变量1.8以后也在堆中 程序的可见度 堆对于整个应用程序都是共享、可见的。栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
5、JVM对象创建过程
5.1、类加载检查
虚拟机遇到一条 new 指令时首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有那必须先执行相应的类加载过程。
5.2、分配内存
在类加载检查通过后接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。
5.2.1、内存分配方式
分配方式有 “指针碰撞” 和 “空闲列表” 两种选择那种分配方式由 Java 堆是否规整决定而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。 5.2.2、内存分配并发问题
对象的创建在虚拟机中是一个非常频繁的行为哪怕只是修改一个指针所指向的位置在并发情况下也是不安全的可能出现正在给对象 A 分配内存指针还没来得及修改对象 B 又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案
对分配内存空间的动作进行同步处理采用 CAS 失败重试来保障更新操作的原子性 把内存分配的动作按照线程划分在不同的空间之中进行即每个线程在 Java 堆中预先分配一小块内存称为本地线程分配缓冲Thread Local Allocation Buffer, TLAB。哪个线程要分配内存就在哪个线程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 时才需要同步锁。通过-XX:/-UserTLAB参数来设定虚拟机是否使用TLAB。
5.3、初始化零值
内存分配完成后虚拟机需要将分配到的内存空间都初始化为零值不包括对象头这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用程序能访问到这些字段的数据类型所对应的零值。
5.4、设置对象头
初始化零值完成之后虚拟机要对对象进行必要的设置例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外根据虚拟机当前运行状态的不同如是否启用偏向锁等对象头会有不同的设置方式。
5.5 、执行初始化init方法
在上面工作都完成之后从虚拟机的视角来看一个新的对象已经产生了但从 Java 程序的视角来看对象创建才刚开始在上面工作都完成之后从虚拟机的视角来看一个新的对象已经产生了但从 Java 程序的视角来看对象创建才刚开始 方法还没有执行所有的字段都还为零。所以一般来说执行 new 指令之后会接着执行方法把对象按照程序员的意愿进行初始化这样一个真正可用的对象才算完全产生出来。