JVM内存模型和GC

原创
2019/04/08 20:51
阅读数 73

内存模型


这张图是我见过的最能描述JVM内存模型的图,JVM包括两个子系统和两个组件。两个子系统为:class loader(类装载)、Execution engine(执行引擎);两个组件为:Runtime data area(运行时数据区)、Native interface(本地接口)

Class loader功能:根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。程序中可以extends java.lang.ClassLoader类来实现自己的Class loader。

Execution engine功能:执行classes中的指令。任何JVM specification实现(JDK)的核心都是Execution engine,不同的JDK例如Sun的JDK和IBM的JDK好坏主要就取决于他们各自实现的Execution engine的好坏。

Native interface组件:与native libraries交互,是其它编程语言交互的接口。当调用native方法的时候,就进入了一个全新的并且不再受虚拟机限制的世界,所以也很容易出现JVM无法控制的native heap OutOfMemory。

Runtime Data Area组件:这就是我们常说的JVM的内存。主要分为五个部分(1~5):

1、Heap (堆):堆是用来对象的内存空间,几乎所有的对象都存储在堆中, 一个Java虚拟实例中只存在一个堆空间。

  • 线程共享,整个Java虚拟机只有一个堆,所有线程都访问同一个堆.
  • 在虚拟机启动时创建
  • 是垃圾回收的主要场地
  • 进一步可分为:新生代(Eden区 From Survior To Surviror) 老年代

不同的区域存放的不同生命周期的对象,这样可以根据不同区域使用不同的垃圾回收算法,更具有针对性. 堆的大小也可以固定也可以扩展,对于主流的虚拟机,堆大小可扩展的,因此当线程请求分配的内存,但堆已满,且内存已无法再扩展,就抛出OutOfMemoryError异常

2、Method Area(方法区域):Java虚拟机规范中定义方法区是堆的一个逻辑部分,方法区存放以下信息 已被虚拟机加载的类信息 /常量 /静态变量 /即时编译后代码。

  • 线程共享.方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享,整个 虚拟机中只有一个方法区
  • 永久代 方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因 此用堆的划分方法,把方法区称为"永久代"
  • 内存回收的效率低.方法区中的信息一般需要长期存在,回收一遍只有少量 信息无效.主要回收的目标是: 对常量池的回收;对类型的卸载
  • Java虚拟机规范l对方法区的要求比较宽松,和堆一样,允许固定大小.也允 许动态扩展,还允许不实现垃圾回收

运行时常量池:
      方法区中存放:类信息 常量 静态变量 即时编译器变编译后代码.常量就存放在运行时常量池中.当类被Java虚拟机加载后,.class文件中的常量就存在方法区的运行常量池,而且在运行期间,可以向常量池中添加新的常量,如String类的intern()方法就能在运行期间向常量池中添加字符串常量

3、Java Stack(Java虚拟机栈):描述Java方法运行过程的内存模型。虚拟机只会直接对Java stack执行两种操作:以帧为单位的压栈或出栈

  • 局部变量表随着栈帧的创建而创建,他的大小在编译时确定,创建时只需分 配事先规定的大小即可,在方法运行的过程中,局部变化表的大小不会 发生变化
  • Java虚拟机栈会出现两种异常:StackOverFlowError和 OutOfMemoryError
  • StackOverFlowError若Java虚拟机栈的大小不允许动态扩展,那么当前 线程请求的栈的深度超过当前的Java虚拟机栈的最大深度是,就会 抛出此异常
  • OutOFMemoryError,若允许动态扩展,那么当前线程的请求的栈内存用完 了,无法再动态扩展时,抛出此异常
  • Java虚拟机栈也是线程私有,随着线程创建而创建,随着线程的结束而销毁

4、Program Counter(程序计数器):程序计数器是一块较小的内存空间,是当前线程正在执行的哪一条字节码指令的地址,若当前线程正在执行的是一个本地方法,那么此时程序计数器为Undefined。

程序计数器的作用:

  • 字节码解释器通过改变程序计数器来依次获取指令,从而实现代码的流程的控制
  • 在在多线程情况下,程序计数器记录的是当前线程执行的执行的位置,从而当线程切换回来时,就知道上次线程执行到哪了

程序计数器的特点

  • 是一块较小的内存空间
  • 线程私有,每个线程都有自己的程序计数器
  • 生命周期:随着线程的创建而创建,随着线程的销毁而销毁
  • 是一个唯一不会出现的OutOfMemoryError的内存区域

5、Native method stack(本地方法栈):是为了JVM运行native方法准备的空间,由于很多native方法都是用C语言实现的,所以通常又叫C栈,它与Java虚拟机栈实现的功能类似,只不过本地方法栈描述本地方法运行过程的内存模型。
栈帧变化过程:
本地方法被执行时,在本地方法栈也会创建一块栈帧,用于存放该方法的局部变量表 /操作数栈 /动态链接 /方法出口等信息; 方法结束后,相应的栈帧也会出栈,并释放内存空间.也会抛出StackOverFlowError和OutOfMemoryError异常

6、直接内存(堆外内存):直接内存是除Java虚拟机之外的内存,但有可能被Java使用。

操作直接内存:
在NIO中引入了一种基于通道和缓存的IO方式,他可以调用本地方法的直接分配Java虚拟机之外的内存,然后通过一个存储在堆中的DirectByteBuffer对象直接操作该内存,而无需将外部内存中数据复制到堆中再进行操作,从而提高数据操作的效率,直接内存的大小不受Java虚拟机,也会抛出OutOfMemoryError异常

直接内存和堆内存比较

  • 直接内存申请空间耗费更高的性能
  • 直接内存读取IO的性能优于普通的堆内存
  • 直接内存的作用链:本地IO-->直接内存-->本地IO
  • 堆内存的作用链:本地IO-->直接内存-->非直接内存-->直接内存--->本 地IO

服务器管理员在配置虚拟机参数时,会根据实际内存设置 -Xmx等参数信息,但经常忽略直接内存,使得各个 内存区域总和大于物理内存,从而导致动态扩展时出现OutOFMemoryError

JVM GC收集器

	垃圾收集器中使用的四种收集算法:引用计数法,标记-清除、标记-整理、标记-复制算法。
	① 引用计数法: 原理是在此对象有个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回收时,只收集计数为0的对象.此算法的最致命的无法处理循环引用的问题

    ②: 标记-清除: 此算法分两个阶段,第一阶段从引用的根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除,此算法需要暂停应用,同时产生内存碎片

    ③: 标记-复制: 此算法把内存划分为两个相等的区域,每次只使用一个区域,垃圾回收时,遍历当前使用的区域,把正在使用的对象复制到另一个区域中每次算法每次只处理正在使用的对象,因此复制的成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现"碎片问题",此算法的缺点也很明显,需要两倍的内存空间

    ④: 标记-整理: 此算法结合了"标记-清除"和:复制算法的两个的优点,也是分两个阶段,第一个阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,清除未标记的对象并且把存活的对象"压缩"到堆的其中一块,按顺序排放,,此算法避免"标记-清除"的碎片问题,同时也避免"复制"的空间问题

收集器

CMS收集器 : CMS是Concurrent Mark Sweep,从名字可以看出,这是一款使用"标记-清除"算法的并发收集器。CMS垃圾收集器是一款以获取最短停顿时间为目标的收集器

由于并发标记和并发清理阶段是和应用系统一起执行的,而初始标记和重新标记相对来说耗时很短,所以可以认为CMS收集器在运行过程中,是和应用程序是并发执行的。由于CMS收集器是一款并发收集和低停顿的垃圾收集器,所以CMS收集器也被称为并发低停顿收集器。

不足
1、CMS收集器对CPU资源非常敏感,本身占用线程。
2、CMS收集器在处理垃圾收集的过程中,产生垃圾浮动。(如果在清理过程 中,用户线程产生了垃圾对象,由于过了标记阶段,所以这些垃圾对象 就成为了浮动垃圾)
3.使用的"标记-清除"算法,过多的内存碎片会影响大对象的分配.

G1收集器

是一款最先进的,一款面向服务端垃圾收集器。

   堆内存分配

    ① : JVM初始分配的内存由-Xms指定,默认是物理内存的1/64

    ②: JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4

    ③: 默认空余堆内存小于40%时,JVM就会增加堆直到-Xmx的最大限 制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制

    ④: 因此服务器一般设置-Xms -Xmx相等以避免在每次GC后调整 堆大小. 对象的堆内存由成为垃圾回收器的自动内存管理系统回收

   非堆内存分配:

    ①:JVM使用-XX:PermSize 设置非堆内存的初始值,默认物理内存 的1/64;

    ② :由XX:MaxPermSize设置设置最大非堆内存的大小

    ③: -Xmn2G :设置年轻代的大小为2G

    ④ :-XX:SurvivorRatio ,设置年轻代中Eden区与Survivor区的 比值

-XmsxxM : -Xms64M 设置最小堆内存为64MB
-XmxxxM : -XMx128M 设置最大堆内存128MB

如果以上参数设置的过于小会导致频繁的发生GC,导致应用的性能极大下降。如不必要使用默认就可以。一般JVM调优调整以上两个参数就可以。

Object o = new Object()的jvm分布

Object o 表示一个本地引用,存储在jvm栈的本地变量表里,表示一个reference类型数据,
new Object():作为实例对象存储在对堆中。 类的信息(即加载类时需要加载的信息、包括版本、file、方法、接口等信息)存储在方法区。

快速定位内存泄漏的命令:

jstat -gc pid

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部