深入理解Java虚拟机:JVM高级特性与最佳实践(读书笔记)

关于JVM

JVM的主要知识点主要分为以下4个

  1. JVM内存模型
  2. GC(垃圾回收)
  3. 类加载机制
  4. JVM参数调优

JVM内存区域划分

深入理解Java虚拟机:JVM高级特性与最佳实践(读书笔记)_第1张图片

序号

名字

用途

是否线程共享

1

方法区

存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

1.1

运行时常量池

存放编译期生成的各种字面量和符号引用

2

在虚拟机启动时创建。存放对象实例,几乎所有的对象实例以及数组都要在堆上分配,是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”

3

虚拟机栈

虚拟机栈为虚拟机执行Java方法,线程私有,生命周期与线程相同。存放了对象引用(非对象本身)

4

本地方法栈

为虚拟机使用到的Native方法服务

 

5

程序计数器

一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

JDK1.8改动

元数据区取代了永久代,就是JDK8没有了PermSize相关的参数配置了。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存(堆外内存)

深入理解Java虚拟机:JVM高级特性与最佳实践(读书笔记)_第2张图片

GC(垃圾回收)

JAVA中的4种引用

强引用

强引用就是指在程序代码之中普遍存在的,类似"Object obj=new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

软引用

软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在在JDK 1.2之后,提供了SoftReference类来实现软引用。

弱引用

弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。

虚引用

虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。

 

垃圾回收算法

对象是否可回收判断算法

序号

名称

算法

缺点

1

引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

很难解决对象之间相互循环引用的问题。

2

可达性分析算法

通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

 

 

垃圾回收算法

序号

名称

算法

缺点

1

标记-清除算法

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

2

复制算法

将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor[1]。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1

这种算法的代价是将内存缩小了,复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

3

标记-整理算法

过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

 

4

分代收集算法

当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。

 

内存分配与回收策略

新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC.Major GC的速度一般会比Minor GC慢10倍以上

对象优先在Eden分配

对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。

大对象直接进入老年代

所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。

虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值得对象直接在老年代分配,这样做的目的是避免在Eden区及两个Suervivor区之间发生大量的内存复制。

长期存活的对象将进入老年代

虚拟机给每个对象定义了一个对象年龄计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且被Suervivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1.对象在Suervivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定层度(默认为15岁),就将会被晋升到老年代中。可以通过参数-XX:MaxTenuringThreshold设置。

动态对象年龄判定

虚拟机并不是永远要求对象的年龄必须达到了设置年龄才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一般,年龄大于或等于该年龄的对象就可以直接进入老年代。

空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果不成立,则虚拟机会查看设置值(HandlePromotionFailure)是否允许担保失败,如果允许,那么会检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则进行Minor GC,如果小于,或者设置值(HandlePromotionFailure)不允许冒险,那这时也要进行一次Full GC.

解释:在进行Minor GC之前,一共有多少对象会活下来在实际完成内存回收之前是无法明确知道的,所以只好取之前每次回收晋升到老年代的平均值作为经验值,取平均值是一个概率事件,也就是说,如果某次Minor GC对象突增,远远高于平均值的话,依然会担保失败,如果出现了担保失败,那就只好重新发起一次Full GC.虽然绕的圈子是最大的,但是一般情况下还是会将HandlePromotionFailure开关打开,避免Full GC过于频繁。

虚拟机的类加载机制

类的生命周期

深入理解Java虚拟机:JVM高级特性与最佳实践(读书笔记)_第3张图片

加载

  1. 通过一个类的全限定名获取对应于该类的二进制字节流
  2. 将这个二进制字节流转储为方法区的运行时数据结构
  3. 于内存中生成一个 java.lang.class 类型的对象,用于表示该类的类型信息。

验证

  1. 文件格式验证
  2. 元数据验证
  3. 字节码验证
  4. 符号引用验证

准备

  1. 类变量赋「系统初值」的过程

解析

  1. 用来实际指向运行时常量池中的直接引用的地址

初始化(Initialization)

发生以下5种情况会出现初始化

  1. 遇到new、getstatic、putstatic或invokestatic这4条字节指令时
  2. 反射调用
  3. 当初始化一个类的时候,发现其父类还没有进行过初始化,则需要先触发其父类初始化
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类
  5. 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

双亲委派模型

类加载器主要分为四个不同类别

  1. Bootstrap 启动类加载器
  2. Extention 扩展类加载器
  3. Application 系统类加载器
  4. 用户自定义类加载器

所谓双亲委派就是类加载器在加载一个类的时候会先委托它的父类尝试去加载,父类又会委托父类尝试去加载。如果父类加载不了子类再自己加载,这样可以保证相同类型的类他们的类加载器是同一个。

深入理解Java虚拟机:JVM高级特性与最佳实践(读书笔记)_第4张图片

JVM参数调优

-Xms12g:初始化堆内存大小为12GB。-Xmx12g:堆内存最大值为12GB 。

-Xmn2400m:新生代大小为2400MB,包括 Eden区与2个Survivor区。

-XX:SurvivorRatio=1:Eden区与一个Survivor区比值为1:1。

-XX:MaxDirectMemorySize=1G:直接内存。报java.lang.OutOfMemoryError: Direct buffer memory 异常可以上调这个值。

-XX:+DisableExplicitGC:禁止运行期显式地调用 System.gc() 来触发fulll GC。

注意: Java RMI的定时GC触发机制可通过配置-Dsun.rmi.dgc.server.gcInterval=86400来控制触发的时间。

-XX:CMSInitiatingOccupancyFraction=60:老年代内存回收阈值,默认值为68。

-XX:ConcGCThreads=4:CMS垃圾回收器并行线程线,推荐值为CPU核心数。

-XX:ParallelGCThreads=8:新生代并行收集器的线程数。

-XX:MaxTenuringThreshold=10:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

-XX:CMSFullGCsBeforeCompaction=4:指定进行多少次fullGC之后,进行tenured区 内存空间压缩。

-XX:CMSMaxAbortablePrecleanTime=500:当abortable-preclean预清理阶段执行达到这个时间时就会结束。

 

 

参考:

https://blog.csdn.net/jisuanjiguoba/article/details/80176223

https://www.cnblogs.com/yangming1996/p/8618343.html

https://blog.csdn.net/javazejian/article/details/72772461

 

 

你可能感兴趣的:(Java)