jvm相关知识点学习
Posted wcgstudy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm相关知识点学习相关的知识,希望对你有一定的参考价值。
1、Class loader类加载器:
负责加载class文件,class文件在文件开头有特定的文件标识,并且classLoader只负责class文件的加载,至于它是否可以运行,则由Execution English 决定
2、Native Interfice
本地接口的作用是融合不同的编程语言为java 所用,它的初衷是融合 c/c++程序。它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies.
目前该方法使用的越来越少,除非是与硬件有关的应用。
3、method Area方法区
方法区是被所有的线程共享,所有的字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此进行定义,简单说,所有定义的方法的信息都保存在该区域,此区属于共享区间。
静态变量+常量+类信息+运行时常量池存在方法区中+实例变量存在堆内存中
4、PC Register 程序计数器
每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
5、Native Method Stack 本地方法栈
它的具体做法是Native Method Stack中登记的native方法,在Execution Engine执行时加载native libraise.
6、Stack栈是什么
栈也叫栈内存,主管java 程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就会释放,对于栈来说不存在垃圾回收的问题,只要线程一结束该栈就over,生命周期和线程一致,是线程私有的。基本类型的变量和对象的引用变量都是在函数的栈内存中分配。
6.1、栈存储什么
栈帧中主要保存3类数据:
本地变量(local variables):输入参数和输出参数以及方法内的变量
栈操作(Operand Stack): 记录出栈、入栈的操作
栈帧数据(Frame Data): 包括类文件方法等
6.2 、栈运行的原理
栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法有调用了B方法,于是产生的栈帧F2也被压入栈,B方法又调用了C方法,于是产生的栈帧F3也被压入栈,。。。。。执行完毕后,先弹出F3栈帧,再弹出F2栈帧,在弹出F1栈帧,遵循着“先进后出”“后进先出”的原则
三种JVM
sun公司的HotSpot
BEA公司的JRockit
IBM公司的J9 VM
堆内存示意图:
注:伊侚区(Eden Space) 幸存0区(Survivor 0 Space) 幸存1区(Survivor 1 Space) 养老区(Tenure Generation Space) 永久存储区(Permanent Space)
7.1 新生区
新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden space )和幸存者区(Survicor space),所有的类都是在伊甸区被new 出来的。幸存区有两个:0区(Survicor 0 space)和 1区(Survivor 1 space)。当伊甸园的空间用完时,程序有需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不在被其他对象引用的对象进行销毁。然后将伊甸园中剩余对象移动到幸存0区。若幸存0区也满了,在对该区进行垃圾回收,然后移动到1区。如果1区也满了,在移动到养老区。若养老区也满了,那么这个时候产生Major GC(FullGC ),进行养老区内存清理。若养老区执行了Full GC之后发现依然无法进行对象的保存,就会产生OOM异常“outofmemoyrError”
如果出现java.lang.OutofMemoryError:Java heap space异常,说明java虚拟机的堆内存不够。原因有二:
(1)java虚拟机的堆内存设置的不够,可以通过参数-Xms、-Xmx来调整。
(2)代码中创建了大量的大对象,并且长时间不能被垃圾收集器手机(存在被引用)
7.2 养老区
养老区用于保存从新生区筛选出来的JAVA对象,一般池对象都在这个区域活跃。
7.3 永久区
永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占的内存。
如果出现java.lang.outofmemoryError:PermGen space,说明是java虚拟机对永久代Prem内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断的被加载,最终导致Prem区被占满。
JDK1.6及以前:有永久代,常量池1.6在方法区
JDK1.7:有永久代,但已经逐步“去永久代”,常量池1.7在堆
JDK1.8:无永久代,常量池1.8在元空间
实际而言,方法区(Method)和对一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等,虽然JVM规范将方法区描述为堆的一个逻辑部分,但它还有一个别名叫做Non-Heap(非堆),目的就是要和对分开
对于HotSpot虚拟机,很多开发者习惯于将方法区称之为“永久代”,但严格本质上说两者是不同的,或者说使用永久代来实现方法区而已,永久代是方法区(相当于一个接口interface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。
常量池(Constant pool)是方法区的一部分,class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,这部分内容将在类加载后进入方法区的运行时常量池中存放 。
jdk 1.8之后将最初的永久代取消了,有元空间取代
目的:将HotSpot 与 JPockit 两个虚拟机的标准合起来了
堆内存调优简介:
-Xms : 设置初始分配大小,默认为物理内存的“1/64”
-Xmx : 最大分配内存,默认为物理内存的“1/4”
-xx+PrintGCDetail: 输出详细的GC处理日志
一般什么时候发生GC,如何处理;
java中的GC会有两种回收: 年轻代的Minor GC,另外一种就是老年代的FUll GC,新对象创建的时候,如果伊甸园的空间不足会触发MinorGC,如果此时老年代的内存空间不足会触发FULL GC,如果空间都不足就会抛出OutOf MemoryError。
GC回收策略的理解:
年轻代(伊甸园+两个幸存区),GC回收策略为“复制”
老年代的保存空间一般较大,GC回收策略为“整理-压缩”;
JVM在进行GC时,并非每次都对上面三个内存区域一起回收,大部分时候收的都是指新生代。
因此GC按照回收的区域又分为了两种类型,一种是普通GC(minor GC),一种是全局GC(major GC or FUll GC),普通GC(minor GC):只针对新生代区域的GC
全局GC(major GC or full GC ): 针对老年代GC,偶尔伴随对新生代GC以及对永久代的GC。
GC的三种算法:
1、年轻代中使用的是Minor GC,这种GC算法采用的是复制算法(Coping)
原理:Minor GC 会把Eden中的所有活的对象都移到Survivor区域中,如果Survivor区中放不下,那么剩下的活的对象就被移到Old generation中,也即一旦收集后,Eden就变成空的了
当对象在Eden(包括一个Survivor区域,这里假设是from区域)出生后,在经过一次Minor GC后,如果对象还存活,并且能够被另外的一块Survivor区域所容纳(上面已经假设为from区域,这里应为to区域,即to区域又足够的内存空间来存储Eden和from区域中存活的对象,)则使用复制算法将这些任然还存活的对象复制到另外一块Survivor区域(即to区域),然后清理所使用过的Eden以及Survivor区域(即from区域),并且将这些对象的年龄设置为1,以后对象在Survivor区每熬过一次Minor GC,就将对象的的年龄+1,当对象的年龄达到某个值时(默认是15岁,通过-xx:maxTenuringThreshold来设定参数),这些对象就会成为老年代
-XX:MaxTenuringThreshold --设置对象在新生代中存活的次数
年轻代中的GC,主要是复制算法(Copying)
HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to),默认比例是8:1:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果任然存活,将会被移到Survivor区,对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当他的年龄增加到一定的程度的时候,就会被移动到年老代中。因为年轻代中的对象基本都是朝生夕死的(80%)以上,所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中的一块,当这一块内存用完,就将还活着的对象复制到另一块上。复制算法不会产生内存碎片。
在GC开始的时候,对象只会存在于Eden区和名为“from”的Survivor区,Survivor区“to”是空的,紧接着进行GC,Eden区所有存活的对象都会被复制到“To”,而在from区中,任存活的对象会根据他们的年龄值来决定去向,年龄达到一定的值(年龄域值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到老年代中,没有达到阈值的对象会被复制到“To”区域,经过这次GC后,Eden和From区已经被清空,这个时候,“from”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的From,新的“from”就是上次GC前的“To”,不管怎样,不管怎样都会保证名为To的Survivor区域是空的,Minor GC会一直重复这样的过程,知道“To”区被填满之后,会将多有的对象移动代老年代中。
因为Eden区对象一般存活率低,一般的,使用两块10%的内存作为空闲和活动区间,而另外80%的内存,则是用来给新建对象分配内存的。一旦发生GC,将10%的活动区与另外80%中存活的对象转移到10%的空闲区间,接下来,将之前90%的内存全部的释放,一次类推。
复制算法的优点和缺点:
复制算法弥补了标记/清除算法中,内存布局混乱的缺点,不过与此同时,它的缺点也是相当明显的。
1、它浪费了一半的内存,这太要命了
2、如果对象的存活率很高,我们可以极端一点,假设是100%的存活,那么我们需要将所有的对象都复制一遍,并将所有的引用地址重复一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变得不可忽视,所以从以上描述不难看出,复制算法要想使用,最起码对象的存活时间要非常低才行,而且最重要的是我们必须要克服50%的内存浪费。
标记清除/标记整理算法:FULLGC又叫MajorGC(全局GC)
老年代一般是由标记清除或者是标记清除与标记整理的混合实现。
标记清除: 1、标记:从根集合开始扫描,对存活的对象进行标记。
2、清除: 扫描整个内存空间,回收未被标记的对象,使用free-list记录可以标记的区域。
标记清除的优点和缺点:
1、首先,他的缺点是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常的差劲。
2、其次,主要的缺点是这种方式清理出来的空闲内存不是连续的,这点不难理解,我们的死亡对象都是随即的出现在内存的各个角落的,现在把他们清除之后,内存的布局自然会乱七八糟。而为了应付这一点,JVM就不得不维持一个内存的空闲列表。这又是一种开销,而且在分配数组对象的时候,寻找连续的内存空间会不太好找。
标记整理:标记整理算法唯一的缺点就是效率也不高,不仅要标记所有存活的对象,还要整理所有存活对象的引用地址。从效率上来说,标记/整理算法要低于复制算法。
内存效率:复制算法>标记清除算法>标记整理算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)
内存整齐度:复制算法=标记整理算法>标记清除算法
内存利用率:标记整理算法=标记清除算法>复制算法
有没有最有的GC算法:没有,只有最合适的算法======> 分代收集算法
以上是关于jvm相关知识点学习的主要内容,如果未能解决你的问题,请参考以下文章