JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看一 jvm 的内存模型 2 1jvm 内存模型概览 jvm 内存

大家好,欢迎来到IT知识分享网。

一 jvm的内存模型

2.1 jvm内存模型概览

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.2 pc计数器

它是一块很小的内存空间,集合可以忽略不记,也是运行速度最快的存储区域。不会随着程序的运行需要更大的空间。

在jvm规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程生命周期保持一致。

它是唯一一个在java虚拟机规范中没有规定任务outofmemoryError 情况的区域。

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.3 线程栈

2.3.1 概述特点

Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。

如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError异常。

如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出—个OutOfMemoryError异常。

栈中不存在GC,但存在OOM

2.3.2 栈帧包含的信息

栈帧包含信息:

1)局部变量: 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据线程安全问题。

当一个实例方法被调用的时候,它的方法参数和方法体内部定义的局部变量,将会按照顺序被复制到局部变量表中的每一个slot上。

2)操作数据栈

操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。

如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令。

3)动态链接

指向运行期中常量池该方法的引用,常量池的作用,就是为了提供一些符号和常量,便于指令的识别。

4)方法出口等信息

无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。

2.3.3 发生栈溢出的情况*

1.局部数据过大,当函数内部的数组过大时,有坑导致堆栈溢出。

2.递归调用层次太多,递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。

2.3.4 设置栈内存大小*

#设置栈内存大小:-Xss size (即: -XX:ThreadStackSize)

2.3.5 方法和栈帧之间的关系

在线程上正在执行的每个方法都各自对一个栈帧。栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.4 本地方法栈

java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。

2.5 java堆

2.5.1.jdk7的结构

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.5.2.jdk8的结构

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.5.3  java中栈与堆关系

1.代码

public class SimpleHeap { private int id; public SimpleHeap(int id) { this.id = id; } public void show() { System.out.println("My ID is " + id); } public static void main(String[] args) { SimpleHeap sl = new SimpleHeap(1); SimpleHeap s2 = new SimpleHeap(2); } }

2.关系

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.5.4 如何设置堆大小

Java堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了,大家可以通过选项”-Xmx”和”-Xms”来进行设置。

“-Xms”用于表示堆区的起始内存,等价于-XX:InitialHeapSize

“-Xmx”则用于表示堆区的最大内存,等价于-XX:MaxHeapSize

1)通常会将-Xms 和-Xmx两个参数配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能

2)heap默认最大值计算方式:如果物理内存少于192M,那么heap最大值为物理内存的一半。如果物理内存大于等于1G,那么heap的最大值为物理内存的1/4。

3)heap默认最小值计算方式:最少不得少于8M,如果物理内存大于等于1G,那么默认值为物理内存的1/64,即1024/64=16M。最小堆内存在jvm启动的时候就会被初始化。

2.5.5  堆中年轻代与老年代

存储在JVM中的Java对象可以被划分为两类:

一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速

另外一类对象的生命周期却非常长,在某些极端的情况下还能够与JVM的生命周期保持一致。Java堆区进一步细分的话,可以划分为年轻代(YoungGen)和老年代(OldGen)。

几乎所有的Java对象都是在Eden区被new出来的。绝大部分的Java对象的销毁都在年轻代进行了。

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.5.6 对象的GC过程

关于垃圾回收总流程:

1)频繁在新生区收集

2)很少在养老区收集

3)几乎不在永久区/元空间收集

几乎所有的Java对象都是在Eden区被new出来的。绝大部分的Java对象的销毁都在年轻代进行

在新生代中,使用“停止-复制”算法进行清理,将新生代内存分为2部分,1部分 Eden区较大,1部分Survivor比较小,并被划分为两个等量的部分,survivorFrom和survivorTo;

当年轻代的Eden区装满时就会触发Minor GC(Survivor满不会引发GC)每次清理时

首先,将Eden区和survivorFrom还存活的对象复制到survivorTo中,

如果survivoerTo中没有空间了,则直接复制到老年代;

如果survivorTo有空间,同时把这些对象的年龄+1如果有对象的年龄以及达到了老年的标准,一般是15则复制到老年代。

然后清理Eden区和刚才survivorFrom中的gc对象

最后,根据谁空谁做to区Survivor To和Survivor From互换(原来的survivor From 现在为空,变为survivor To;原来的survivor To现在非空,变为survivor From),按此流程以此类推。 

2.5.7 TLAB

从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.6 方法区

2.6.1 作用

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域

方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。

方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutOfMemoryError: PermGen space或者java.lang.OutOfMemoryError:Metaspace

总结: 它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。加载类的信息都在方法区。

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.6.2 方法区存储内容

1)类信息:

JVM必须在方法区中存储以下类型信息: 

① 这个类型的完整有效名称(全名=包名.类名)

② 这个类型直接父类的完整有效名(对于interface或是java.lang.Object,都没有父类) 

③ 这个类型的修饰符(public,abstract, final的某个子集) 

④ 这个类型直接接口的一个有序列表 

2)域信息:

JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。

域的相关信息包括: 域名称、域类型、域修饰符(public, private, protected, static, final, volatile, transient的某个子集)

方法信息:

JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序 :

3)方法名称 

方法的返回类型(或void)

方法参数的数量和类型(按顺序)

方法的修饰符(public, private, protected, static, final, synchronized, native, abstract的一个子集)

方法的字节码(bytecodes)、操作数栈、局部变量表及大小 (abstract和native方法除外)

异常表(abstract和native方法除外)

每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引

4)类变量:

静态变量和类关联在一起,随着类的加载而加载,它们成为类数据在逻辑上的一部分。

类变量被类的所有实例共享,即使没有类实例时你也可以访问它。

5)全局常量:static final,每个全局常量在编译的时候就会被分配了。

 2.6.3 方法区大小设置

jdk7及以前:

通过-XX:PermSize来设置永久代初始分配空间。默认值是20.75M

-XX:MaxPermSize来设定永久代最大可分配空间。32位机器默认是64M,64位机器模式是82M

当JVM加载的类信息容量超过了这个值,会报异常OutOfMemoryError:PermGen space 。

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

jdk8及以后:

元数据区大小可以使用参数-XX:MetaspaceSize和-XX:MaxMetaspaceSize指定,替代上述原有的两个参数。

默认值依赖于平台。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize 的值是-1,即没有限制。

与永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存。如果元数据区发生溢出,虚拟机一样会抛出异常OutOfMemoryError: Metaspace

 2.6.4 方法区存储内容

jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

jdk1.8及之后:无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池仍在堆。

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看 

 2.7 运行时常量池

2.7.1 常量池作用

运行时常量池(Runtime Constant Pool)是方法区的一部分。

常量池表(Constant Pool Table)是Class文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池。

常量池,可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。

包含:

数量值

字符串值

类引用

字段引用

方法引用

2.7.2 方法区与永久代

元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。

2.7.3 String常量池*

1.通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。

2.字符串常量池中是不会存储相同内容的字符串的。

String s1 = “abc”;//字面量的定义方式

3.String常量池的位置:

Java 6及以前,字符串常量池存放在永久代。

Java 7 中 Oracle 的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内。

Java 8 中,字符串常量仍然在堆。

4.String的实例化方式:

方式一:通过字面量定义的方式

方式二:通过new + 构造器的方式

5.面试题:String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?

 两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:”abc”

6.<clinit> 是类初始化时执行的方法, 而<init> 是对象初始化时执行的方法。

2.8 直接内存

直接内存是在Java堆外的、直接向系统申请的内存区间。

通常,访问直接内存的速度会优于Java堆。即读写性能高。

Java的NIO库允许Java程序使用直接内存,用于数据缓冲区

由于直接内存在Java堆外,因此它的大小不会直接受限于-Xmx指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存。

如果不指定MaxDirectMemorySize,则默认与java堆最大值(-Xmx指定)一致。

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

二  实操案例

2.1 堆默认值以及设置规则

2.1.1 默认值

默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3

可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5

在HotSpot中,Eden空间和另外两个Survivor空间缺省所占的比例是8:1:1

当然开发人员可以通过选项“-XX:SurvivorRatio”调整这个空间比例。比如-XX:SurvivorRatio=8

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.1.2 设置命令以及查看

案例1:堆空间大小的设置: 

-Xms:初始内存 (默认为物理内存的1/64);

-Xmx:最大内存(默认为物理内存的1/4);

具体查看某个参数的指令:

jps:查看当前运行中的进程

jinfo -flag SurvivorRatio 进程id

2.1.3 设置api

常见API设置:

-Xmn

设置年轻代的大小。(初始值及最大值)通常默认即可。

-XX:NewRatio

配置新生代与老年代在堆结构的占比。赋的值即为老年代的占比,剩下的1给新生代

默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3

-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5

-XX:SurvivorRatio

在HotSpot中,Eden空间和另外两个Survivor空间缺省所占的比例是8:1

开发人员可以通过选项“-XX:SurvivorRatio”调整这个空间比例。比如-XX:SurvivorRatio=8

-XX:MaxTenuringThreshold

设置新生代垃圾的最大年龄。超过此值,仍未被回收的话,则进入老年代。默认值为15

-XX:MaxTenuringThreshold=0:表示年轻代对象不经过Survivor区,直接进入老年代。对于老年代比较多的应用,可以提高效率。

如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概率。

-XX:+PrintGCDetails

输出详细的GC处理日志

-XX:HandlePromotionFailure

在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,

如果大于,则此次Minor GC是安全的。

如果小于,则虚拟机会查看-XX:HandlePromotionFailure设置值是否允许担保失败。

如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。

-XX:+PrintFlagsFinal

看所有的参数的最终值(可能会存在修改,不再是初始值)

2.2 设置和查看栈大小

1.代码

/ * 演示栈中的异常:StackOverflowError * * @author shkstart * @create 2020 下午 9:08 * * 设置栈的大小: -Xss (-XX:ThreadStackSize) * * -XX:+PrintFlagsFinal */ public class StackErrorTest { private static int count = 1; public static void main(String[] args) { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } try { count++; main(args); }catch (Throwable e){ System.out.println("递归的次数为:" + count); } } }

2.设置

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

3.查看: jinfo -flag    ThreadStackSize pid

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.3 查堆中老年代大小

1.代码

package com.ljf.jvm.optimise.staick; / * 1. 设置堆空间大小的参数 * -Xms 用来设置堆空间(年轻代+老年代)的初始内存大小 * -X 是jvm的运行参数 * ms 是memory start * -Xmx 用来设置堆空间(年轻代+老年代)的最大内存大小 * * 2. 默认堆空间的大小 * 初始内存大小:物理电脑内存大小 / 64 * 最大内存大小:物理电脑内存大小 / 4 * 3. 手动设置:-Xms600m -Xmx600m * 开发中建议将初始堆内存和最大的堆内存设置成相同的值。 * * 4. -XX:+PrintFlagsFinal * * @create 2021 20:15 */ public class HeapSpaceInitial { public static void main(String[] args) { //返回Java虚拟机中的堆内存总量 long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024; //返回Java虚拟机试图使用的最大堆内存量 long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024; System.out.println("-Xms : " + initialMemory + "M"); System.out.println("-Xmx : " + maxMemory + "M"); // System.out.println("系统内存大小为:" + initialMemory * 64.0 / 1024 + "G"); // System.out.println("系统内存大小为:" + maxMemory * 4.0 / 1024 + "G"); try { Thread.sleep(); } catch (InterruptedException e) { e.printStackTrace(); } } } 

2.查看

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.4 设置堆大小和日志输出

1.代码

/ 测试:大对象直接进入老年代 * -Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails * @create 2021 21:48 */ public class YoungOldAreaTest { public static void main(String[] args) { byte[] buffer = new byte[1024 * 1024 * 20];//20m } } 

2.进行设置:    -Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

3.输出日志

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.5 设置元空间的大小与日志输出

1.代码

package com.atguigu.methodarea; / * 结论: * 静态引用对应的对象实体始终都存在堆空间 * * jdk7: * -Xms300m -Xmx300m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:+PrintGCDetails * jdk 8: * -Xms300m -Xmx300m -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails * @author shkstart  * @create 2020 21:20 */ public class StaticFieldTest { private static byte[] arr = new byte[1024 * 1024 * 100];//100MB -> 200+MB public static void main(String[] args) { System.out.println(StaticFieldTest.arr); // try { // Thread.sleep(); // } catch (InterruptedException e) { // e.printStackTrace(); // } } } 

2.设置:

 -Xms300m -Xmx300m -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

3.查看

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

2.6 设置堆中年轻代和日志输出

1.代码

/ * -Xms600m -Xmx600m -XX:SurvivorRatio=8 -XX:+PrintGCDetails * @create 2020 17:51 */ public class HeapInstanceTest { byte[] buffer = new byte[new Random().nextInt(1024 * 200)]; public static void main(String[] args) { ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>(); while (true) { list.add(new HeapInstanceTest()); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }

2.设置:  -Xms600m -Xmx600m -XX:SurvivorRatio=8 -XX:+PrintGCDetails

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

3.日志输出

JVM 调优篇2 jvm的内存结构以及堆栈参数设置与查看

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/117874.html

(0)
上一篇 2025-11-17 15:26
下一篇 2025-11-17 15:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信