JVM原理详解

JVM组成

JVM组成

JVM原理详解插图

使用Java语言编写.java Source Code文件,通过javac编译成.class Byte Code文件。

class loader类加载器:将所需所有类加载到内存,必要时将类实例化成实例。

图中中间部分是进程的内存逻辑结构,称为Jvm运行时区域,由下面几部分构成:

-方法区:所有线程共享的内存空间,存放已加载的类信息、常量和静态变量。

-heap堆:所有线程共享的内存空间,存放创建的所有对象。堆是靠GC垃圾回收器管理的。

-Java栈:每个线程会分配一个栈,存放线程用的本地变量、方法参数和返回值等。

-PC寄存器:PC, 即Program Counter,每一个线程用于记录当前线程正在执行的字节码指令地址。因为线程需要切换,当一个线程被切换回来需要执行的时候,知道执行到哪里了。

-本地方法栈:为本地方法执行构建的内存空间,存放本地方法执行时的局部变量、操作数等。

-所谓本地方法,简单的说是非Java实现的方法,例如操作系统的C编写的库提供的本地方法,Java调用这些本地方法接口执行。但是要注意,本地方法应该避免直接编程使用,因为Java可能跨平台使用,如果用了Windows API,换到了Linux平台部署就有了问题。

虚拟机

目前Oracle官方使用的是HotSpot, 它最早由一家名为"Longview Technologies"公司设计,使用了很多优秀的设计理念和出色的性能,1997年该公司被SUN公司收购。后来随着JDK一起发布了HotSpot VM。目前HotSpot是最主要的VM。

安卓程序需要运行在JVM上,而安卓平台使用了Google自研的Java虚拟机——Dalvid,适合于内存、处理器能力有限系统。

垃圾收集器

堆内存里面经常创建、销毁对象,内存也是被使用、被释放。如果不妥善处理,一个使用频繁的进程,将可能有内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的数据。

回收基本算法

1.引用计数

每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用堆内存就可以被回收。循环引用的对象都无法引用计数归零,就无法清除。

2.标记-清除 Mark-Sweep

分垃圾标记阶段和内存释放阶段。标记阶段,找到所有可访问对象打个标记。清理阶段,遍历整个堆,对未标记对象清理。

JVM原理详解插图(1)
JVM原理详解插图(2)

由图可知,标记-清除最大的问题会造成内存碎片。

3.复制 Copying
先将可用内存分为大小相同两块区域A和B,每次只用其中一块,比如A。当A用完后,则将A中存活的对象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净。缺点是比较浪费内存,能使用原来一半的内存,因为内存对半划分了,复制过程毕竟也是有代价。好处是没有碎片,复制过程中保证对象使用连续空间。

4.标记-压缩 Mark-Compact
分垃圾标记阶段和内存整理阶段。标记阶段,找到所有可访问对象打个标记。内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端。

JVM原理详解插图(3)

由上图可知,标记-压缩算法好处是整理后内存空间连续分配,有大段的连续内存可分配,没有内存碎片。缺点是内存整理过程有消耗。

5.分代收集算法
既然上述垃圾回收算法都有优缺点,能不能对不同数据进行区分管理,不同分区对数据实施不同回收策略,分而治之。

1.7及以前,堆内存分为新生代、老年代、持久代。

1.8开始,持久代没有了,取而代之MetaSpace。

STW

对于大多数垃圾回收算法而言,GC线程工作时,停止所有工作的线程,称为Stop The World。GC完成时,恢复其他工作线程运行。这也是JVM运行中最头疼的问题。

分代堆内存GC策略
堆内存分代

JVM原理详解插图(4)

Heap堆内存分为

  • 新生代:刚刚创建的对象
    • 伊甸园区
    • 存活区Servivor Space:有2个存活区,一个是from区,一个是to区。它们大小相等、地位相同、可互换。
    • to指的是本次复制数据的目标区
  • 老年代:长时间存活的对象
  • 持久代:JVM的类和方法
新生代回收

起始时,所有新建对象都出生在eden,当eden满了,启动GC。这个称为Young GC,Minor GC。

标记eden存活对象,然后将存活对象复制到s0(假设本次是s0,也可以是s1,它们可以调换),eden剩余所有空间都“清空”。GC完成。

继续新建对象,当eden满了,启动GC。

标记eden和s0中存活对象,然后将存活对象复制到s1。将eden和s0“清空”。

继续新建对象,当eden满了,启动GC。

标记eden和s1中存活对象,然后将存活对象复制到s0。将eden和s1“清空”。

以后就重复上面的步骤。

大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。

但是,如果一个对象一直存活,它最后就在from、to来回复制,如果from区中对象复制次数达到阈值,就直接复制到老年代。

老年代回收

进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。老年代GC称为Old GC,Major GC。

由于老年代对象一般来说存活次数较长,所有较常采用标记-压缩算法。

Full GC:对所有“代”的内存进行垃圾回收

Minor GC比较频繁,Major GC较少。但一般Major GC时,由于老年大对象也可以引用新生代对象,所以先进行一次Minor GC,然后在Major GC会提高效率。可以认为回收老年代的时候完成了一次Full GC。

GC触发条件

Minor GC触发条件:当eden区满了触发

Full GC触发条件:

  • 老年代满了
  • 新生代搬向老年代,老年代空间不够
  • 持久代满了
  • System.gc()手动调用。不推荐
调整策略
  • 减少STW时长,串行变并行
  • 减少GC次数,要分配合适的内存大小

对JVM调整策略应用极广

  • 在WEB领域中Tomcat等
  • 在大数据领域Hadoop生态各组件
  • 在消息中间件领域的Kafka等
  • 在搜索引擎领域的ElasticSearch、Solr等

在不同领域对JVM需要不同的调整策略

垃圾收集器类型

回收线程数:指的是GC线程是否串并行

  • 串行垃圾回收器:一个GC线程完成回收工作
  • 并行垃圾回收器:多个GC线程同时一起完成回收工作,充分利用CPU资源

JVM原理详解插图(5)

工作模式不同:指的是GC线程和工作线程是否一起运行

  • 并发垃圾回收器:让GC线程垃圾回收某些阶段可以和工作线程一起进行。
  • 独占垃圾回收器:只有GC在工作,STW一直进行到回收完毕,工作线程才能继续执行。

一般情况下,我们大概可以使用以下原则:

客户端或较小程序,内存使用量不大,可以使用串行回收;

对于服务端大型计算,可以使用并行回收;

大型WEB应用,用户端不愿意等,尽量少的STW,可以使用并发回收;

本文链接:http://www.yunweipai.com/35205.html

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/52753.html

(0)
上一篇 2021年8月6日
下一篇 2021年8月6日

相关推荐

发表回复

登录后才能评论