作为运维,你需要了解的jvm知识点
Posted 龙叔运维
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了作为运维,你需要了解的jvm知识点相关的知识,希望对你有一定的参考价值。
作为运维,处理最多的可能就是系统的一些内存报错了,所以整理一下jvm方面的知识点,希望各位读完本篇文章,对jvm能有一个基本的了解
推荐公众号,分享运维知识:龙叔18岁
1·概括
堆是堆(heap),栈是栈(stack),堆栈是栈(这个经常会让新手误会)
1.1·内存结构图
下面几个图是我在网上看到的很棒的jvm内存的概括图
1.2·知识图谱
下面这个是我自己总结的jvm内存结构知识点图谱
2·堆
堆是各个线程之间共享的一块内存区域,存放的主要是对象实例和数组,几乎所有的对象实例都在这里分配内存。
分为年轻代和老年代,他们之间的大小比例通过参数 -XX:NewRatio
来指定 ,一般情况下,不允许-XX:Newratio值小于1,即Old要比Yong大。
2.1·分类
为什么要把对进行分代,这样做是为例优化GC的性能
2.1.1·年轻代
分为Eden、survivor(from和to,也叫s0和s1),他们之间的大小比例由参数–XX:SurvivorRatio 来设定
大多数情况下,对象在先新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Young GC
-XX:Newsize : 设置Yong Generation的初始值大小
-XX:Maxnewsize:设置Yong Generation的最大值大小
-XX:Surviorratio : 设置Eden和一个Suivior的比例,比如值为5,即Eden是To(S2)的比例是5,(From和To是一样大的),此时Eden占据Yong Generation的5/7
2.1.2·老年代
大于年龄阈值(参数-XX:MaxTenuringThreshold,默认 15)的对象,将晋升到老年代中。(对象每经历一次垃圾回收,且没被回收掉,它的年龄就增加 1)
2.1.3·永久代
永久代是对方法区的具体实现,1.8之后就没有永久代了,取而代之的是元空间
只有 HotSpot 才有永久代(PermGen space)
2.2·GC回收
分为young gc(minor gc),old gc,full gc,当有人说major gc的时候,一定要问清楚说的式old gc 还是 full gc。
我在一篇文章中看到了一段对 java对象 一生的描述,觉的写的很好:
我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。
2.2.1·young gc(minor gc)
针对年轻代的垃圾回收:
YoungGC 就是在新生代的Eden区域满了之后,就会触发,采用复利算法来回收新生代的垃圾
发生YoungGC之前往往会先检查一下老年代的空间,如果说明本次YoungGC后可能升入老年代对象的大小,可能超过了老年代当前可用内存空间,此时必须先触发一次OldGC给老年代腾出更多的空间,然后再执行YoungGC
young gc时,程序会挂起,但是很短暂
Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
调优:
想要减少young gc的消耗时间,就较小年轻代的大小
想要减少young gc的频率,就增大年轻代的大小
2.2.2·old gc
针对老年代的垃圾回收,只收集old gen的GC。只有CMS的concurrent collection是这个模式
2.2.3·full gc
针对年轻代,老年代,永久代的回收
触发条件:
【1】当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC
【2】如果有perm gen的话,要在perm gen分配空间但已经没有足够空间时,也要触发一次full GC
【3】或者System.gc()、heap dump带GC,默认也是触发full GC。
2.3·堆的异常报错
2.3.1·java.lang.OutOfMemoryError: Java heap space
下面代码加上JVM参数-verbose:gc -Xms10M -Xmx10M -XX:+PrintGCDetails -XX:SurvivorRatio=8,就能很快报出OOM异常(内存溢出异常):
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space(因为不断创建对象实例,二堆内存只分配了10M的空间,很快就撑爆了)
如果加上-XX:+HeapDumpOnOutOfMemoryError,就可以才出现OOM的时候打dump文件
public class HeapOOM
static class OOMObject
/**
* @param args
*/
public static void main(String[] args)
List list = new ArrayList();
while (true)
list.add(new OOMObject());
2.3.2·java.lang.OutOfMemoryError: GC overhead limit exceeded
JDK6新增错误类型,当GC为释放很小空间占用大量时间时抛出;,一般是因为堆太小,导致异常的原因,没有足够的内存。
2.3.3·java.lang.OutOfMemoryError: PermGen space
永久代大小不够,可以调整-XX:PermSize和-XX:MaxPermSize参数
3·栈
这里注意栈分为java虚拟机栈和本地方法栈,java虚拟机栈服务于普通java方法,本地方法栈服务于其他语言实现的native方法,除了服务对象不一样,其他基本一致(native方法多由C和C++语言实现,譬如java.lang.Object类中的hashCode()方法就是native方法,其底层是通过C++实现的。)(Sun HotSpot虚拟机就直接将本地方法栈和Java虚拟机栈合二为一了)
每个线程包含一个栈区,栈中只保存方法中(不包括对象的成员变量)的基础数据类型和自定义对象的引用(不是对象),对象都存放在堆区中
Java虚拟机栈和线程同时创建,用于存储栈帧。每个方法在执行时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直到执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
栈的一些简要说明:
·栈是后入先出了(LIFO)
·函数的调用时用栈实现的,函数调用时,函数的入口出会创建一个栈帧,出口出会释放这个栈帧(为什么会用栈区实现函数的调用呢,因为栈式最适合实现嵌套关系的,LIFO)
·每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
·栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
函数的调用
int main()
a();
return 0;
void a()
b();
void b()
c();
void c()
过程如下
方法调用时,创建栈帧,并压入虚拟机栈;方法执行完毕,栈帧出栈并被销毁,如下图所示:
3.1·栈帧
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。
一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态。对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作
3.2·栈的异常报错
3.2.1·java.lang.StackOverflowError
线程请求的栈深度大于虚拟机所允许的深度(-Xss设定的)
一般是因为函数调用太深,出现死循环或者递归调用太深
解决办法:
优化程序设计,减少方法调用层次;调整-Xss参数增加线程栈大小。
3.2.2·java.lang.OutOfMemoryError: unable to create new native thread
不同于StackOverflowError,OutOfMemoryError指的是当整个虚拟机栈内存耗尽,并且无法再申请到新的内存时抛出的异常。
栈的最大内存大致上等于“JVM进程能占用的最大内存(依赖于具体操作系统) - 最大堆内存 - 最大方法区内存 - 程序计数器内存(可以忽略不计) - JVM进程本身消耗内存”
解决方法:
【1】通过 -Xss启动参数减少单个线程栈大小,这样便能开更多线程(当然不能太小,太小会出现StackOverflowError)
【2】通过-Xms -Xmx 两参数减少Heap大小,将内存让给Stack(前提是保证Heap空间够用)。
4·堆和栈比较
4.1·关系
4.2·区别
堆是线程共享的,栈式线程私有的
系统都会自动去回收它,但是对于堆内存一般开发人员会自动回收它
栈的空间大小远远小于堆的
栈存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
不论对象什么时候创建,他都会存储在堆内存中,栈内存包含它的引用。栈内存只包含原始值变量好和堆中对象变量的引用。
5·方法区
主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与 堆
进行区分,通常又叫 非堆
。
5.1·不同实现方法
1.8版本将方法区的时候从永久代变为元空间
5.1.1·永久代
PermGen
, 就是 PermGen space
,全称是 Permanent Generation space
,是指内存的永久保存区域。这块内存主要是被JVM存放Class和Meta信息的, Class 在被 Loader 时就会被放到 PermGen space
中。
5.1.2·元空间
元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用 本地内存。默认情况下,元空间的大小仅受 本地内存 限制,但可以通过以下参数来指定元空间的大小:
-XX:MetaspaceSize
,初始空间大小:达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。-XX:MaxMetaspaceSize
,最大空间:默认是没有限制的。除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio
,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集;-XX:MaxMetaspaceFreeRatio
,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集;
5.1.3·为什么1.8要改成元空间
1)字符串存在永久代中,容易出现性能问题和内存溢出。
2)类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
3)永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
4)Oracle 可能会将HotSpot 与 JRockit 合二为一
5.2·方法区的异常报错
5.2.1·【永久代】java.lang.OutOfMemoryError: PermGen space
由于方法区
主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。
6·程序计数器
PC寄存器( PC register ):每个线程启动的时候,都会创建一个PC(Program Counter,程序计数器)寄存器。PC寄存器里保存有当前正在执行的JVM指令的地址。 每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。保存下一条将要执行的指令地址的寄存器是 :PC寄存器。PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
推荐公众号,分享运维知识:龙叔18岁
以上是关于作为运维,你需要了解的jvm知识点的主要内容,如果未能解决你的问题,请参考以下文章