JVM相关总结

原创
2019/03/01 10:02
阅读数 94
 

a,内存模型

程序计数器:线程私有,一块较小的内存空间,是jvm执行程序的流水线,存放一些跳转指令,维护下一个将要执行指令的地址。(仅限于Java方法, Native方法该计数器值为undefined).

java虚拟机栈:线程私有,每个方法执行都会创建一个栈帧,存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法被调用至返回的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程(VM提供了-Xss来指定线程的最大栈空间, 该参数也直接决定了函数调用的最大深度).

本地方法栈:虚拟机使用到的Native方法服务

java堆:-Xmx最大堆内存 -Xms初始堆内存。所有线程共享,GC收集器管理的主要区域,jvm创建的对象存放于此,主要分为新生代和旧生代,而新生代又分为eden区(80%),from survivor(10%)和to survivor区,之所以这么分是因为新生代中98%的对象是朝生夕死,

##所以采用复制算法的GC策略,将eden和其中一块survivor中存活的对象复制到另一块survivor区,然后清理eden和用过的survivor区

方法区:所有线程共享,方法区是jvm规范中定义的一个概念,存放加载的类,常量,静态变量,JIT编译后的代码等数据。hotspot中用永久代来实现,别的jvm没有永久代的概念

在Java 6中,方法区中包含的数据,除了JIT编译生成的代码存放在native memory的CodeCache区域,其他都存放在永久代;

在Java 7中,Symbol的存储从PermGen移动到了native memory,并且把静态变量从instanceKlass末尾(位于PermGen内)移动到了java.lang.Class对象的末尾(位于普通Java heap内);

在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间(Metaspace),-XX:MaxPermSize 参数失去了意义,取而代之的是-XX:MaxMetaspaceSize。

元空间:jdk1.8中移除永久代,而使用元空间,两者都是对jvm方法区的实现,不过元空间并不在虚拟机中,而是使用本地内存。

使用元空间代替永久代的原因:

#字符串存在永久代中,容易出现性能问题和内存溢出

#类及方法信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出

#永久代会为GC带来不必要的复杂度,并且回收效率偏低

#oracle可能会将HotSpot与JRockit合二为一

内存分配原则:

#对象优先在Eden分配,当没有足够空间触发Minor GC

#大对象直接进入老年代,很长的字符串及数组都是大对象

#长期存活的对象进入老年代,新生代中经历一次Minor GC年龄就增加一岁,当达到默认的15岁,就进入老年代,这个默认值可以设置

注:直接内存:直接内存并不是JVM运行时数据区的一部分, 但也会被频繁的使用: 在JDK 1.4引入的NIO提供了基于Channel与Buffer的IO方式, 它可以使用Native函数库直接分配堆外内存(java.nio.ByteBuffer.allocateDirect()), 然后使用DirectByteBuffer对象作为这块内存的引用进行操作(详见: Java I/O 扩展),

这样就避免了在Java堆和Native堆中来回复制数据, 因此在一些场景中可以显著提高性能. 显然, 本机直接内存的分配不会受到Java堆大小的限制(即不会遵守-Xms、-Xmx等设置), 但既然是内存, 则肯定还是会受到本机总内存大小及处理器寻址空间的限制, 因此动态扩展时也会出现OutOfMemoryError异常.

b,GC机制

#判断对象死亡算法

#引用计数法:给对象添加一个引用计算器,每当有地方引用它就加1,引用失效就减1,为0时就表示对象不再被使用,简单效率高,但是存在一个对象之间相互循环引用问题。

#可达性分析算法:主流语言都采用此算法判断对象存活,原理是对象到GC Roots是不可达的,就认定为此对象可回收。

##在java语言中,可作为GC Roots的对象包括:虚拟机栈中引用的对象、java堆类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象

#垃圾收集算法

#标记-清除算法:最基础的收集算法,后续算法都是基于这种思路改进。分为标记和清楚两个过程,但是效率不高,且产生大量不连续内存碎片。再分配大对象时,可能无法找到足够的连续内存而导致提前出发一次gc

#复制算法:实现简单,效率高,现在商用虚拟机都采用这种算法来回收新生代。将eden和其中一块survivor中存活的对象复制到另一块survivor区,然后清理eden和用过的survivor区

#标记整理算法:复制算法当对象存活率较高时效率会降低,所以根据老年代特点设计出这种算法,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

#分代收集:根据对象存活周期不同选择以上三种不同的算法去收集,java堆新生代使用 复制算法,旧生代使用 标记-整理算法

#垃圾回收器

#串行垃圾回收器Serial:最基本,最古老的收集器,只用一个单独的线程进行垃圾回收,冻结所有应用程序进行工作,客户端可能会用

#并行垃圾回收器Parallel:使用多线程进行垃圾回收,停止其他所有应用程序线程

#并发标记扫描垃圾回收器CMS:基于标记-清除算法实现,主要有四步:初始标记-并发标记-重新标记-并发清除,初始标记,重新标记任要停止其他应用线程。并发收集,低停顿。缺点使用标记-清除算法

#G1垃圾回收器:目前最先进的收集器,替换CMS,将整个java堆划分为多个大小相等的独立区。特点:并行与并发、分代收集、空间整合(标记-整理)、 可预测停顿

c,类加载

#类加载过程5步

#加载

#通过一个类的全限定名来获取定义此类的二进制字节流

#将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

#在内存中生成一个代表这个类的class对象,作为方法区这个类的各种数据的访问入口

#验证

#文件格式验证:验证字节流是否符合Class文件格式规范

#元数据验证:是否有父类、父类是否继承final修饰类、不是抽象类是否实现父类或接口需要实现的方法

#字节码验证:确定语义合法、符合逻辑

#引用符号验证:检查引用的类、字段、方法的访问性是否可悲当前类访问等。

#准备

#为类变量分配内存并设置初始化值

#解析

#类或接口解析

#字段解析

#类方法解析

#接口方法解析

#初始化

#执行<clinit>()方法:调用静态代码块和给静态变量赋值,先从父类开始

#类加载器

#种类4类:启动类加载器(c++实现)、扩展类加载器->应用程序类加载器->自定义类加载器

#双亲委派模型:一个类加载器收到类加载请求,先判断自己是否已经加载了此类,如果没有则会把这个请求交给父类加载器完成,

##父类加载器同样判断自己是否已加载,如果没有加载则一层一层往上,直到有父类反馈这个类不该自己加载,子类才自己去加载,优点:安全,避免重复加载

#注意:很多文章介绍类加载等级时将启动类加载器列在扩展类加载器的上一级,其实不对,因为启动类加载器由c++实现,属于jvm的一部分,并不属于jvm的类等级结构,

##而且启动类加载器也没有子类

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部