概念:
jmap -heap:堆大小=年轻代大小 + 年老代大小 + 持久代大小。 ---JVM内存
堆内存=新生代(Eden、From Survivor、To Survivor)+老年代(Tenured Generation)
常见配置:
jdk1.8以下:-Xms4096m -Xmx4096m -XX:MaxPermSize=512m
jdk1.8及以上:-Xms4096m -Xmx4096m -XX:MaxMetaspaceSize=512m
线程共享: Java 堆、方法区;线程私有:虚拟机栈、本地方法栈,程序计数器一小部分内存。
1、JVM堆:
堆内存(Heap memory)
1 )堆是所有线程共享的,主要是存放类 /对象实例和数组。处于物理上不连续的内存空间,只要逻辑连续即可
2)Java垃圾回收机制只作用于堆区,对非堆区没有作用。
3)包括: 新生代(Eden、From Survivor、To Survivor)、老年代(Tenured Generation)
(1)年轻代(New):年轻代用来存放JVM刚分配的Java对象
Eden:Eden用来存放JVM刚分配的对象
Survivor1/Survivro2:两个Survivor空间一样大,当Eden中的对象经过垃圾回收没有被回收掉时,会在两个Survivor之间来回Copy,当满足某个条件,比如Copy次数,就会被Copy到Tenured。显然,Survivor只是增加了对象在年轻代中的逗留时间,增加了被垃圾回收的可能性。
(2)年老代(Tenured):年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代
(3)永久代(Perm):永久代存放Class、Method元信息,其大小跟项目的规模、类、方法的量有关,一般设置为128M就足够,设置原则是预留30%的空间。
新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 )
默认的,Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
4)参数设置:
(1)配置堆区的参数:-Xms、-Xmx、-XX:newSize、-XX:MaxnewSize、-Xmn
(2)配置非堆区的参数:-XX:PermSize、-XX:MaxPermSize
-Xms3072M:设置JVM初始内存为3072M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。(一般为操作系统可用内存的1/64大小)。
-Xmx3072M:设置JVM最大可用内存为3072M。(一般为操作系统可用内存的1/4大小)
-Xss1M:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-XX:NewSize设置新生代最小空间大小。(注意:该值需要小于-Xms的值)。可以缩写-Xns
-XX:MaxNewSize设置新生代最大空间大小。(注意:该值需要小于-Xmx的值)。可以缩写 -Xmn
-Xmn2048M:设置年轻代大小为2G。此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。增大年轻代后,将会减小年老代大小。不过此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。这个参数很简洁,相当于一次性设定NewSize和MaxNewSIze,而且两者相等。(注意:JDK1.4之后才有该参数)。
-XX:NewRatio 年轻代(包括Eden和两个Survivor区)与年老代的比值
-XX:MinHeapFreeRatio XX:MaxHeapFreeRatio GC后,如果发现空闲堆内存占到整个预估堆内存的40%,则放大堆内存的预估最大值,但不超过固定最大值。预估堆内存是堆大小动态调控的重要选项之一。前者会根据使用情况动态调大或缩小,以提高GC回收的效率,默认70%
-XX:MaxTenuringThreshold 垃圾最大年龄,设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率
-XX:InitialTenuringThreshold 可以设定老年代阀值的初始值
-XX:+PrintTenuringDistribution 查看每次minor GC后新的存活周期的阈值 Note
2、方法区(永久代)Perm / Metaspace
1)属于共享内存区域,存储已被虚拟机加载的类信息( 编译后的class 文件)、常量、静态变量、即时编译器编译后的代码等数据。 常量池: 方法区的一部分,用于存放编译期生成的各种字面量和符号引用。
2)在JDK8之前的HotSpot JVM,存放这些”永久的”的区域叫做“永久代(permanent generation)”。永久代是一片连续的堆空间,在JVM启动之前通过在命令行设置参数-XX:MaxPermSize来设定永久代最大可分配的内存空间,默认大小是64M(64位JVM默认是85M)。
随着JDK8的到来,JVM不再有 永久代(PermGen)。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)。
两者区别:
无论-XX:MetaspaceSize和-XX:MaxMetaspaceSize两个参数如何设置,都会从20.8M开始,随着类加载越来越多不断扩容调整,上限是-XX:MaxMetaspaceSize,默认是几乎无穷大。而Perm的话,我们通过配置-XX:PermSize以及-XX:MaxPermSize来控制这块内存的大小,jvm在启动的时候会根据-XX:PermSize初始化分配一块连续的内存块,这样的话,如果-XX:PermSize设置过大,就是一种赤果果的浪费。很明显,Metapsace比Perm好多了^^;
建议:
MetaspaceSize和MaxMetaspaceSize设置一样大;具体设置多大,建议稳定运行一段时间后通过jstat -gc pid确认且这个值大一些,对于大部分项目256m即可。
3)下图中的Perm代表的是永久代,但是注意永久代并不属于堆内存中的一部分,同时jdk1.8之后永久代已经被移除。
4)方法区或永生代相关设置
-XX:PermSize=64MB 最小尺寸,初始分配。
-XX:MaxPermSize=256MB 最大允许分配尺寸,按需分配。Perm(俗称方法区)占整个堆内存的最大值
XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled 设置垃圾不回收
默认大小
-server选项下默认MaxPermSize为64m
-client选项下默认MaxPermSize为32m
3、虚拟机栈(JVM Stack)
Java中一个线程就会相应有一个线程栈与之对应,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈,因此栈存储的信息都是跟当前线程(或程序)相关信息的,包括局部变量表、程序运行状态、方法返回值、方法出口 、操作数栈、动态连接等等。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
java虚拟机栈是线程私有,生命周期与线程相同。创建线程的时候就会创建一个java虚拟机栈。
虚拟机执行java程序的时候,每个方法都会创建一个栈帧,栈帧存放在java虚拟机栈中,通过压栈出栈的方式进行方法调用。
平时我们所说的变量存在栈中,这句话说的不太严谨,应该说局部变量存放在java虚拟机栈的局部变量表中。
java的8中基本类型的局部变量的值存放在虚拟机栈的局部变量表中,如果是引用型的变量,则只存储对象的引用地址。
4、本地方法栈(Native Stack)
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
5、程序计数器(PC Register)
程序计数器就是记录当前线程执行程序的位置,改变计数器的值来确定执行的下一条指令,比如循环、分支、方法跳转、异常处理,线程恢复都是依赖程序计数器来完成。
6、问题
Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线程切换能恢复到正确的位置,每条线程都需要一个独立的程序计数器,所以它是线程私有的。
那为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗?
从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据,分工明确,处理逻辑更为清晰体现了“分而治之”以及“隔离”的思想。
堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这样共享的方式有很多收益:提供了一种有效的数据交互方式(如:共享内存);堆中的共享常量和缓存可以被所有栈访问,节省了空间。
栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。
堆和栈的结合完美体现了面向对象的设计。当我们将对象拆开,你会发现,对象的属性即是数据,存放在堆中;而对象的行为(方法)即是运行逻辑,放在栈中。因此编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。
7、典型JVM参数配置参考:
-Xmx3550m-Xms3550m-Xmn2g-Xss128k
-XX:ParallelGCThreads=20
-XX:+UseConcMarkSweepGC-XX:+UseParNewGC
https://blog.csdn.net/weixin_39957068/article/details/112620109