JVM运行时数据区和GC算法

1.JVM

1.1 JVM和JMM(java memory model )的区别

JVM是java虚拟机,英文名字是java virtual machine,jvm使得java可以跨平台运行,示例如图1所示。

                                                             图1-1 java跨平台的示意

已经有九种语言可以在jvm上运行了,都产生了.class文件,可以说jvm不仅是平台无关性,而且是语言无关性了。

Java平台无关性的支持:java语言规范,.class文件,jvm

                                                                   图1-2 java平台无关性

Java语言本身也为平台无关性做了努力。(例如int 统一为4个字节)

Jvm是平台有关的,因为不同的操作系统对同一操作的指令是不同的,如图1-3所示。

                                                                   图1-3 jvm平台有关

Jvm的设计有指导规范,但是各家商业机构设计还是有区别。

JRockit和HotSpot 还有J9(IBM),这三家现在是主要的。

包含关系:jdk>JRE>jvm。今天主要说的是Java虚拟机运行时数据区。JMM是java内存模型(java memory model),这两个东西没有直接的关系。JMM是多线程的基础,这里不再详细介绍了。

要想讲清楚JVM就需要把java程序的整个运行过程整出来,在1.2节介绍。

1.2 java程序的运行过程

1.2.1 类的生命周期

                                                                    图1-4 类加载过程

  1. 加载:查找和装载class文件,读取class文件的形式有很多;
  2. 验证:复合虚拟机的规范;
  3. 准备:为类变量分配内存,并赋予初值。Int 0;但如果是final的话,直接初始化;
  4. 解析:JVM会将常量池中的符号引用替换为直接引用;
  5. 初始化:对类变量初始化,也就是static修饰的变量,先初始化其父类,如果是多个静态变量,自上而下初始化;
  6. 使用
  7. 卸载

1.2.2 类的加载机制

双亲委派模型:没有体现“双”,我觉得就是父亲委派模型。没有父子的继承关系。

                                                             图1-5 双亲委派类加载流程

1.3 JVM运行时数据区

                                                                    图1-6 JVM运行时数据区

这是1.8之前的运行时数据区的划分,在1.8(含)以后,把方法区挪到了机器的内存空间中,成为元空间。

下面的小节分别介绍这几个模块。

1.3.1虚拟机栈 & 本地方法栈

这两个非常相似,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。

1.3.2程序计数器

可以看作是当前线程所执行的字节码的行号指示器。是线程私有的,如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。

1.3.3 堆

堆用来分配对象实例,是垃圾回收器管理的内存区域,可以被成为GC堆。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

1.3.4方法区

用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。在JDK8以前有的把方法区称为永久代,也使用了垃圾回收算法,从JDK8开始,把方法区全部移到了本地内存的元空间(不占用虚拟机的内存空间了,占用操作系统的内存空间),根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。

1.4 java的发展

Java从9开始执行每隔半年发布一版;

Java8的版本新特性:函数式接口和 Lambda 表达式、接口默认方法等;     

2.GC算法

(GC算法和GC收集器要解决的问题)

那些内存需要回收?

何时回收?

如何回收?

GC算法主要发生的地方是在堆和方法区(常量池  “abc”)。

那些内存需要回收?

  1. 引用计数法:在对象头分配一个空间,保存对象被引用的次数。虽然容易理解,但是对于对象互相引用的问题不能解决,容易造成内存泄漏。所有主流的虚拟机都不采用这种方法。
    public class ReferenceCountingGC {
    
        public Object instance;
    
        public ReferenceCountingGC(String name){}
    }
    
    public static void testGC(){
    
        ReferenceCountingGC a = new ReferenceCountingGC("objA");
        ReferenceCountingGC b = new ReferenceCountingGC("objB");
    
        a.instance = b;
        b.instance = a;
    
        a = null;
        b = null;
    }

     

  2. 可达性分析算法:核心就是确定GC Roots,

                                                图2-1 利用可达性分析算法判定对象是否可回收

哪些可以作为GC Roots呢?

·在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。

·在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。

·在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。

·在本地方法栈中JNI(即通常所说的Native方法)引用的对象。

·Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。

·所有被同步锁(synchronized关键字)持有的对象。

·反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

对象的自救?

使用finalize方法,但是线程的优先级较低(final finally)

2.1堆的划分

Java堆分为新生代(占三分之一)和老年代(占三分之二),新生代又分为Eden区、Servivor To区和Servivo rFrom区,Eden区占十分之八,To和From分别占十分之一。

                                                                图 2-2 堆的划分

Q:新生代为何这么划分(为什么是8:1:1)?

经验值  可以调

几个名词:

  1. Minor GC/young GC:新生代收集,指目标只是新生代的垃圾收集。

      Q:什么情况下会触发Minor  GC

          实例化之后,年轻代满了,担保机制失效(major GC)

         system.gc() full GC;

   2.Major GC:老年代收集,指目标只是老年代的垃圾收集。

      Q:什么情况下会触发Major GC

     担保机制失效

MajorGC 的速度一般会比 Minor GC 慢 10倍以上。

如何避免Major GC

   3.Full GC:整堆收集,收集整个Java堆和方法区的垃圾收集。

   Q:什么情况下回触发Full GC

   System.gc()

  4.Mixed GC:混合收集,指目标是收集整个新生代以及部分老年代的垃圾收集。

     目前只有G1收集器会有这种行为。

2.2垃圾收集算法

2.2.1标记-清除算法

                                                               图2-3 标记-清除算法示意图

Q:这个算法存在的缺点 & 适合哪一代

  1. 效率不高(回收的多)
  2. 产生碎片

适合老年代, 标记的少

2.2.2 标记-复制算法

                                                            图2-4 标记复制算法示意图

Q:这个算法解决的问题 & 存在的缺点 & 适合哪一代

解决的问题:解决效率不高问题 & 解决内存碎片问题  

缺点:浪费空间

适合年轻代

优化后的半区复制

怎么设计的,出现的问题是啥,怎么解决的

2.2.3 标记-整理算法

                                                      图2-5 标记整理算法示意图

Q:缺点:

效率低,stop the world 用于老年代

2.2.4 分代收集算法

应该是一个分代收集理论,融合上述三种算法的基础思想,打出一套组合拳。

在年轻代和老年代采用不同的算法

2.3垃圾收集器

                                                          图 2-6 垃圾收集器的组合使用

每个垃圾收集器采用的GC算法,以及优劣势

2.3.1 Serial收集器

单线程,复制,在新生代

Stop the world

Serial收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择。

2.3.2 ParNew收集器

Parrallel  new

多线程并行,复制,在新生代,默认和CPU同等数量的线程,可配置

2.3.3 Parallel Scavenge收集器

多线程并行,复制,新生代,提高了效率

提供了两个参数

关注吞吐量(业务)/(业务+GC)

2.3.4 Serial Old收集器

单线程,标记-整理 老年代

2.3.5 Parallel Old收集器

多线程并行,标记整理,老年代

2.3.6 CMS(Concurrent Mark Sweep)收集器

并发,缩短停顿时间,标记-清除,老年代

默认启动的垃圾回收的线程数是(处理器核心数量+3)/4

关注停顿时间

它的缺点是啥

对CPU核数比较敏感

2.3.7 Garbage First收集器(G1)

G1可以针对不同区域进行回收,优先回收价值最大的Region,只要针对多CPU以及大量内存的服务器

G1收集器的原理简单描述?

它的缺点是啥?

维护一个表 region价值大

额外空间占用比较多 维护列表

在小内存的机器上表现不如CMS

2.4 总结

使用 java -XX:+PrintCommandLineFlags -version 可以查看jvm使用的垃圾收集器

JDK 8默认使用默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

JDK9默认使用G1

CMS已经不被推荐使用

垃圾收集器的选择影响系统停顿时间和吞吐能力的重要因素之一,一切设计都是为了提升程序的运行效率和吞吐量。

3.JMM

Java内存模型(Java Memory Model,JMM),JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的工作空间。如图3-1所示。

                                                                       图 3-1 JMM示意图

这是java支持并发的基础。

并发解决的两个问题:线程之间如何通信,线程之间如何同步

3.1 JMM带来的问题

会出现可见性问题

出现了volatile

Synchronized加锁

Java有指令重排序,但是他定义了一些规则

参考资料:《深入理解java虚拟机》第三版 周志明

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值