就算你躺在沙发上三天不起,拉不开窗帘,因为决定不了穿哪双袜子哭个没完,我也不会停止爱你.
面试的时候 必问JVM
,淦!咱们准备好好复习吧,加油!奇怪的知识又增加了呐.
根据JVM规范,JVM 内存共分为虚拟机栈,堆,方法区,程序计数器,本地方法栈五个部分
程序计数器(线程私有):
是当前线程锁执行字节码的行号治时期,每条线程都有一个独立的程序计数器,这类内存也称为“线程私有”的内存。正在执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是Natice方法,则为空。
java 虚拟机栈
也是 线程私有的
。
每个方法在执行的时候也会创建一个栈帧,存储了 局部变量,操作数,动态链接,方法返回地址
。
每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。
通常所说的栈,一般是指在虚拟机栈中的局部变量部分。
局部变量所需内存在编译期间完成分配,
如果线程请求的栈深度大于虚拟机所允许的深度,则StackOverflowError。
如果虚拟机栈可以动态扩展,扩展到无法申请足够的内存,则OutOfMemoryError。
本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其
区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则
是为 虚拟机使用到的Native 方法服务
。虚拟机规范中对本地方法栈中的方法使用的语
言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至
有的虚拟机(譬如Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。
与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError
异常
Java堆(线程共享)
被所有线程共享的一块内存区域,在虚拟机启动的时候创建,用于存放对象实例。
对可以按照可扩展来实现(通过-Xmx 和-Xms 来控制)
当队中没有内存可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。
方法区(线程共享)
被所有方法线程共享的一块内存区域。
用于存储已经被虚拟机加载的类信息,常量,静态变量等。
这个区域的内存回收目标主要针对常量池的回收和堆类型的卸载。
JDK8 HotSpot JVM
将移除永久区,使用本地内存来存储类元数据信息并称之为: 元空间(Metaspace)
。这意味着不会再有java.lang.OutOfMemoryError: PermGen问题,也不再需要你进行调优及监控内存空间的使用。
二、空间分配担保(就像贷款一样) -XX:HandlerPromotionFailure
Minor GC之前检查
老年代最大可用连续空间是否 > 新生代所有对象总空间。
大于----> Minor GC
不大于—>老年代最大可用连续空间是否>历次晋升到老年代对象的平均大小
大于-----> Minor GC
不大于 -----> Full GC
-XX:HandlerPromotionFailure
注:Minor GC 是针对新生代的垃圾回收,MajorGC是老年代的,Full GC 是Minor GC 加上 MajorGC
新生代
所有new的对象都在堆里面。对象优先分配到Eden区,可以通过参数-XX:SurviviorRatio=8来指定Eden区和Survivior的比例,这样划分的依据和好处是根据gc的回收算法来定的。做到了内存利用率高。
经过几次(可以通过参数设置)minor gc还存活的对象进入S0,再经过几次minor gc还存活进入S1,达到minor gc的阈值(-XX:MaxTenuringThreshold=?)进入老年代。
还有就是动态对象年龄判定,相同年龄所有对象的大小总和 > survivor空间的一半,直接晋级老年代,不需要进行年龄阈值的判断。
这个区域的理想是98%以上的对象能够被回收。
老年代
大对象(-XX:PretenureSizeThreadshold=1024)直接进入老年代,在新生代里面长期存活的对象进入老年代。老年代对应的是major gc。
永久代
包含元数据信息,如class,method的detail信息
在Java1.8时,删除了方法区,增加了元空间,如下图:
PermGen空间状况
:这部分内存空间将全部移除。JVM的参数:PermSize 和 MaxPermSize 会被忽略并给出警告(如果在启用时设置了这两个参数)。
Metaspace 容量
: 默认情况下,类元数据只受可用的本地内存限制
(容量取决于是32位或是64位操作系统的可用虚拟内存大小)。 新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小
。如果没有指定这个参数,元空间会在运行时根据需要动态调整。
另外,对于僵死的类及类加载器的垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。适时地 监控和调整元空间
对于减小垃圾回收频率和减少延时是很有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器导致的内存泄漏或是大小设置不合适。
在JDK8之前的HotSpot JVM,存放这些”永久的”的区域叫做“永久代(permanent generation)”。永久代是一片连续的堆空间,在JVM启动之前通过在命令行设置参数 -XX:MaxPermSize
来设定永久代最大可分配的内存空间,默认大小是 64M(64位JVM由于指针膨胀,默认是85M)
。永久代的垃圾收集是和老年代(old generation) 捆绑在一起
的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。不过,一个明显的问题是,当JVM加载的类信息容量超过了参数-XX:MaxPermSize设定的值时,应用将 会报OOM的错误
(对于这句话,译者的理解是:32位的JVM默认MaxPermSize是64M,而JDK8里的Metaspace,也可以通过参数-XX:MetaspaceSize 和-XX:MaxMetaspaceSize设定大小,但如果不指定MaxMetaspaceSize的话,Metaspace的大小仅 受限于native memory的剩余大小
。也就是说永久代的最大空间一定得有个指定值,而如果MaxPermSize指定不当,就会OOM)。
从 JDK7开始
永久代的移除工作,贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap。但永久代仍然存在于JDK7,并没有完全的移除:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
在JDK7 update 4即随后的版本中,提供了完整的支持对于 Garbage-First(G1)
垃圾收集器,以取代在 JDK5中发布的CMS收集器
。使用G1,PermGen仅仅在 FullGC(stop-the-word,STW)
时才会被收集。G1仅仅在PermGen满了或者应用分配内存的速度比G1并发垃圾收集速度快的时候才触发FullGC。
而对于CMS收集器,通过开启布尔参数-XX:+CMSClassUnloadingEnabled来并发对PermGen进行收集。对于G1没有类似的选项,G1只能通过FullGC,stop the world,来对PermGen进行收集。
永久代在JDK8中被完全的移除了。所以永久代的参数-XX:PermSize和-XX:MaxPermSize也被移除了。
在JDK8中,classe metadata(the virtual machines internal presentation of Java class),被存储在叫做Metaspace的native memory。一些新的flags被加入:
1、 -XX:MetaspaceSize
,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值。
2、 -XX:MaxMetaspaceSize
,可以为class metadata分配的最大空间。默认是没有限制的。
3、 -XX:MinMetaspaceFreeRatio
,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集
4、 -XX:MaxMetaspaceFreeRatio
,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集
5、默认情况下, class metadata
的分配仅受限于可用的native memory总量。可以使用MaxMetaspaceSize来限制可为class metadata分配的最大内存。当class metadata的使用的内存达到MetaspaceSize(32位clientVM默认12Mbytes,32位ServerVM默认是16Mbytes)时就会对死亡的类加载器和类进行垃圾收集。设置MetaspaceSize为一个较高的值可以推迟垃圾收集的发生。
这里科普下,在Windows下称为虚拟内存(virtual memory),在Linux下称为交换空间(swap space),用于当系统需要更多的内存资源而物理内存已经满了的情况下,将物理内存中不活跃的页转移到磁盘上的交换空间中。
在JDK8,Native Memory,包括Metaspace和C-Heap。
由于类的元数据可以在本地内存(native memory)之外分配,所以其最大可利用空间是整个系统内存的可用空间。这样,你将不再会遇到OOM错误,溢出的内存会涌入到交换空间。最终用户可以为类元数据指定最大可利用的本地内存空间,JVM也可以增加本地内存空间来满足类元数据信息的存储。
注:永久代的移除并不意味者类加载器泄露的问题就没有了。因此,你仍然需要监控你的消费和计划,因为内存泄露会耗尽整个本地内存,导致内存交换(swapping),这样只会变得更糟。
创作不易,如果本篇文章能帮助到你,请给予支持,赠人玫瑰,手有余香,虫虫蟹蟹观众姥爷了