一文理清JVM和GC(上)

简介: 本文主要介绍 JVM和GC解析

一、JVM内存体系


其中方法区和堆被JVM中多个线程共享,比如类的静态常量就被存放在方法区,供类对象之间共享。


虚拟机栈、本地方法栈、程序计数器是每个线程独立拥有的,不会与其他线程共享。

所以Java在通过new创建一个类对象实例的时候,一方面会在虚拟机栈中创建一个对该对象的引用,另一方面会在堆上创建类对象的实例,然后将对象引用指向该对象的实例。对象引用存放在每一个方法对应的栈帧中。



  • 虚拟机栈:虚拟机栈中执行每个方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。


  • 本地方法栈:与虚拟机栈发挥的作用相似,相比于虚拟机栈为Java方法服务,本地方法栈为虚拟机使用的Native方法服务,执行每个本地方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。


  • 方法区:它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,方法区在JDK1.7版本及之前称为永久代,从JDK1.8之后永久代被移除。


  • 堆:堆是Java对象的存储区域,任何new字段分配的Java对象实例和数组,都被分配在了堆上,Java堆可使用 - Xms-Xmx 进行内存控制,从JDK1.7版本之后,运行时常量池从方法区移到了堆上。


  • 程序计数器:指示Java虚拟机下一条需要执行的字节码指令。


二、JAVA8之后的JVM


从图中我们可以看出JAVA8的JVM 用元空间取代了永久代




三、GC作用域



四、常见垃圾回收算法


引用计数法:


JVM的实现一般不采用这种方式



缺点:


  • 每次对对象赋值时均要维护引用计数器,且计数器本身也有一定的消耗;


  • 较难处理循环引用;


复制算法:


Java 堆从GC的角度可以细分为:新生代(Eden区、From Survivor区 和 To Survivor区)和 老年代。


特点


复制算法不会产生内存碎片,但会占用空间。用于新生代。



MinorGC的过程(复制 --> 清空 --> 互换):


  1. 复制: (Eden、SurvivorFrom 复制到 SurvivorTo,年龄加1)
    首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区域和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经到达了老年的标准,则复制到老年代区),同时把这些对象的年龄加1。


  1. 清空:(清空Eden、SurvivorFrom)
    清空Eden和SurvivorFrom中的对象,也即复制之后有交换,谁空谁是to。


  1. 互换:(SurvivorTo和SurvivorFrom 互换)
    最后,SurvivorTo和SurvivorFrom 互换,原SurvivorTo成为下一次GC是的SurvivorFrom区。


标记清除法


算法分成标记清除两个阶段,先标记出要回收的对象,然后统一回收这些。


特点:


不会占用额外空间,但会扫描两次,耗时,容易产生碎片,用于老年代



标记压缩法


优点:


没有内存碎片,可以利用bump


缺点:


需要移动对象的成本,用于老年代


原理:


标记:与标记清除一样



压缩:再次扫描,并往一段滑动存活对象



五、判断对象是否可回收


引用计数法


Java中,引用和对象是有关联的。如果要操作对象则必须用引用进行


因此,很显然的一个方法就是通过引用计数来判断一个对象是否可以回收。简单来说就是给对象添加一个引用计数器。每当有一个地方引用它,计数器的值加1,每当有一个引用失效时,计数器的值减1。


任何时刻计数器值为0的对象就是不可能再被使用的,那么这个对象就是可回收对象。

缺点:


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


枚举根节点做可达性分析(根搜索路径)


所谓GC roots或者说tracing GC 的 根集合 就是一组必须活跃的引用。


基本思路就是通过一系列名为GC Root 的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索。


如GC Roots没有任何引用链相连是,则说明此对象不可用。也即给定一个集合的引用作为根出发,通过引用关系



哪些可以做GCRoots对象


  • 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)
  • 方法区中的类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中N(Native方法)引用的对象


六、JVM的参数类型


1)标配参数


  • java -version


  • java -help



2)X参数


  • java -Xint -version :解释执行


  • java -Xcomp -version :第一次使用就编译成本地代码


  • java -Xmixed :混合模式


3)XX参数


  • Boolean类型


-XX+ 或者 - 某个属性值(+:表示开启,-:表示关闭)


例子


-XX: +PrintGCDetails: 开启打印GC收集细节


-XX: -PrintGCDetails: 关闭打印GC收集细节


-XX: +UseSerialGC: 开启串行垃圾收集器


-XX: -UseSerialGC:关闭串行垃圾收集器


  • KV设置类型


-XX: 属性key = 属性value


例子


-XX: MetaspaceSize = 128m:设置元空间大小为128m


-XX:MaxTenuringThreshold = 15:控制新生代需要经历多少次GC晋升到老年代中的最大阈值


  • jinfo -查看当前运行程序的配置


公式jinfo -flag 配置项  进程编号


例子


  1. 查看初始堆大小:


  1. 查看其他参数


  1. 查看使用哪种垃圾回收器


两个经典参数


  • -Xms 等价于 -XX: InitialHeapSize


  • -Xmx 等价于 -XX: MaxHeapSize


七、查看JVM默认值


  • -XX:+PrintFlagsInitial: 查看默认初始值


  • java -XX: +PrintFlagsInitial -version
  • java -XX: +PrintFlagsInitial


  • -XX:+PrintFlagsFinal :查看修改更新


  • java -XX:+PrintFlagsFinal
  • java -XX:+PrintFlagsFinal -version
  • java -XX:+PrintCommandedLineFlags


八、常用的配置参数


经典案例设置:


-Xms128m -Xmx4096m -Xss1024k -XX:Metaspacesize=512m -XX:+PrintCommandLineFlags -XX:PrintGCDetails -XX:UseSerialGC


  • -Xms


初始化大小内存,默认为物理内存1/64


等价于 -XX:InitialHeapSize


  • -Xmx


最大分配内存,默认为物理内存1/4


等价于 -XX:MaxHeapSize


  • -Xss


设置单个线程的大小,一般默认为5112K~1024K


等价于 -XX:ThreadStackSize


  • -Xmn


设置年轻代大小


  • -XX:MetaspaceSize


设置元空间大小


元空间的本质和永久代类似,都是对JVM规范中方法区的实现,不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制


  • -XX:+PrintGCdetails


输出详细的GC收集日志信息


  • -XX:SurvivorRatio


设置新生代中eden和S0/S1空间的比例


默认:


-XX:SurvivorRatio=8 --> Eden:S0:S1=8:1:1


修改:


-XX:SurvivorRatio=4 --> Eden:S0:S1=4:1:1


SurvivorRatio值就是设置eden区的比例占多少,S0/S1相同


  • -XX:NewRatio


设置年轻代与老年代在堆结构的占比


默认:


-XX:NewRatio=2: 新生代占1,老年代占2,年轻代占整个堆的1/3


修改:


-XX:NewRatio=4: 新生代占1,老年代占4,年轻代占整个堆的1/5


NewRatio值就是设置老年代的占比,剩下的1给新生代


  • -XX:MaxTenuringThreshold


设置垃圾最大年龄


-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。


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


九、强软弱虚




1)强引用


  • 当内存不足,JVM开始垃圾回收,对于强引用的对象,就算出现了OOM也不会对该对象进行回收,`死都不收`


  • 强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表名对象还活着,垃圾收集器不会碰这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的。即使该对象以后永远都不会被用到,JVM也不会回收。 因此强引用是造成Java内存泄漏的主要原因之一。


  • 对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,一般就是认为可以被垃圾收集(具体看垃圾收集策略)


public static void main(String[] args) {
        Object o1 = new Object();   //默认为强引用
        Object o2 = o1;     //引用赋值
        o1 = null;          //置空 让垃圾收集
        System.gc();
        System.out.println(o1);     // null
        System.out.println(o2);     // java.lang.Object@1540e19d
    }


2)软引用


  • 软引用就是一种相对强引用弱化了一些的引用。需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。


  • 系统内存充足  -> 不会回收


  • 系统内存不足  -> 会回收


  • 软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收


public static void main(String[] args) {
        Object o1 = new Object();
        SoftReference softReference = new SoftReference(o1);
        o1 = null;
        System.gc();
        System.out.println(o1);
        System.out.println(softReference.get());
    }


3)弱引用


  • 弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短


  • 对于弱引用的对象,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。


public static void main(String[] args) {
        Object o1 = new Object();
        WeakReference weakReference = new WeakReference(o1);
        o1 = null;
        System.gc();
        System.out.println(o1);                     //null
        System.out.println(weakReference.get());    //null
    }


5)虚引用


  • 虚引用需要java.lang.ref.PhantomReference类来实现。


  • 形如虚设,它不会决定对象的生命周期。


  • 如果一个对象持有虚引用,那么它就和没有任何一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它来访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。


  • 虚引用的主要作用是跟踪对象被垃圾回收的状态,仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。PhantomReferenceget()方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。


public static void main(String[] args) {
        Object o1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        PhantomReference<Object> phantomReference = new PhantomReference<>(o1,referenceQueue);
        System.out.println(o1);                         //java.lang.Object@1540e19d
        System.out.println(phantomReference.get());     //null
        System.out.println(referenceQueue.poll());      //null
    }


扩展:软弱引用适用场景


假如有一个引用需要读取大量的本地图片


存在问题


  1. 如果每次读取图片都从硬盘读取则会严重影响性能。


  1. 如果一次性全部加载到内存中有可能造成内存溢出。


解决思路


用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。


Map imgMap = new HashMap()

WeakHashMap


public static void main(String[] args) {
        WeakHashMap<Integer,String> weakHashMap = new WeakHashMap<>();
        Integer key = new Integer(1);
        weakHashMap.put(key,"测试1");
        System.out.println(weakHashMap);    //{1=测试1}
        key=null;
        System.out.println(weakHashMap);    //{1=测试1}
        System.gc();
        System.out.println(weakHashMap+"\t"+weakHashMap.size());    //{} 0
    }


目录
相关文章
|
25天前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
6月前
|
Arthas 监控 Java
(十一)JVM成神路之性能调优篇:GC调优、Arthas工具详解及各场景下线上最佳配置推荐
“在当前的互联网开发模式下,系统访问量日涨、并发暴增、线上瓶颈等各种性能问题纷涌而至,性能优化成为了现时代开发过程中炙手可热的名词,无论是在开发、面试过程中,性能优化都是一个常谈常新的话题”。
595 3
|
3月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
140 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
2月前
|
存储 监控 Java
JVM进阶调优系列(8)如何手把手,逐行教她看懂GC日志?| IT男的专属浪漫
本文介绍了如何通过JVM参数打印GC日志,并通过示例代码展示了频繁YGC和FGC的场景。文章首先讲解了常见的GC日志参数,如`-XX:+PrintGCDetails`、`-XX:+PrintGCDateStamps`等,然后通过具体的JVM参数和代码示例,模拟了不同内存分配情况下的GC行为。最后,详细解析了GC日志的内容,帮助读者理解GC的执行过程和GC处理机制。
|
3月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
127 3
|
3月前
|
算法 Java
JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
本文详细介绍了JVM中的GC算法,包括年轻代的复制算法和老年代的标记-整理算法。复制算法适用于年轻代,因其高效且能避免内存碎片;标记-整理算法则用于老年代,虽然效率较低,但能有效解决内存碎片问题。文章还解释了这两种算法的具体过程及其优缺点,并简要提及了其他GC算法。
 JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
|
3月前
|
存储 Java PHP
【JVM】垃圾回收机制(GC)之引用计数和可达性分析
【JVM】垃圾回收机制(GC)之引用计数和可达性分析
97 0
|
6月前
|
运维 Java Linux
(九)JVM成神路之性能调优、GC调试、各内存区、Linux参数大全及实用小技巧
本章节主要用于补齐之前GC篇章以及JVM运行时数据区的一些JVM参数,更多的作用也可以看作是JVM的参数列表大全。对于开发者而言,能够控制JVM的部分也就只有启动参数了,同时,对于JVM的性能调优而言,JVM的参数也是基础。
141 8
|
5月前
|
算法 Java 应用服务中间件
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
|
5月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用