G1--深入理解JVM内存结构,熟练GC垃圾回收机制,可以进行简单的JVM调优

G1是什么

G1(Garbage-First)是一种垃圾回收器用于Java虚拟机(JVM)中的堆内存管理。

G1垃圾回收器是为了解决传统的垃圾回收器在大堆内存环境下可能出现的问题而研究出来的。传统的垃圾回收器(如CMS和Serial等)在堆内存较大的情况下,可能会导致长时间的停顿和低吞吐量。G1垃圾回收器通过将堆内存划分为多个区域(Region),并使用并发标记-整理算法,实现了更可控的垃圾回收过程。

G1垃圾回收器具有以下优点:
1. 低停顿时间:G1垃圾回收器通过将堆内存划分为多个区域,可以选择性地对某些区域进行垃圾回收,从而减少了全局停顿的时间。
2. 高吞吐量:G1垃圾回收器可以在多个区域上并行进行垃圾回收,提高了吞吐量。
3. 可预测的停顿时间:G1垃圾回收器会根据用户指定的停顿时间目标来自动调整垃圾回收的策略,以尽量满足停顿时间的要求。

JVM中使用G1垃圾回收器需要在启动JVM时添加以下参数:
-XX:+UseG1GC

此外,还可以通过其他参数进行更详细的配置,例如:
-XX:G1HeapRegionSize=n:指定每个区域的大小,默认值为堆内存的1/2000。
-XX:MaxGCPauseMillis=n:指定最大停顿时间的目标值。
-XX:InitiatingHeapOccupancyPercent=n:指定启动并发标记周期的堆占用率阈值。

总之,G1垃圾回收器是一种在大堆内存环境下具有低停顿时间和高吞吐量的垃圾回收器,可以通过适当的配置参数在JVM中使用。

G1的优点

G1是适合服务端的垃圾收集器,通过整理内存、可控的STW和区域划分等优势,取代了CMS收集器,提升了服务器性能。

        Java7发布 Java9替换成G1,适合于服务端的垃圾收集器;G1收集器的目标是取代CMS收集器,与CMS相比,以下方面更出色: G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片 G1的STW更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间,主要应用在多CPU和大内存服务器环境下,极大的减少垃圾收集的停顿时间,全面提升服务器的性能,逐步替换Java8以前的CMS收集器

        G1主要改变的是Eden Survivor(新生代)和Tenured(老年代)等内存区域不再是连续的了,而是变成了一个个大小一样的region(元空间)

        每个region从1M到32M不等,一个region有可能属于Eden Survivor(新生代)和Tenured(老年代)内存区域.

底层原理:

最大的好处是化整为零,避免全内存扫面,只需要按照区域来进行扫描即可

区域化内存划片Region,整体编为了一系列不连续的内存区域,避免了全内存区的GC操作。

在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。

大小范围在1MB~32MB,最多能设置2048个区域,也即能够支持的最大内存为: 32MB * 2048 = 65536MB = 64G内存

过程:

1、初始标记:只标记GC Root能直接关联到的对象 2、并发标记:进行GCRoots Tracing 的过程 3、最终标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象, 4、筛选回收:根据时间来进行价值最大化的回收

常用参数

-XX:+UseG1GC 使用 G1 (Garbage First) 垃圾收集器

-XX:G1HeapRegionSize=n 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.

-XX:MaxGCPauseMillis=n 设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽量去达成这个目标.

-XX:ParallelGCThreads=n 设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同.

-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.

与CMS相比:

1、G1不会产生内存碎片 2、可以精确控制停顿,该收集器是把整个堆(新生代,老年代)划分为多个固定大小的区域,每次根据允许停顿的时间去收集垃圾最多的区域。

常见的OOM

java.lang.out of memoryError:java Head space

Java.lang.OutOfMemeoryError:GC overhead limit exceded:当JVM资源利用出现问题时抛出,更具体地说,这个错误是由于JVM花费太长时间执行GC且只能回收很少的堆内存时抛出的。根据Oracle官方文档,默认情况下,如果Java进程花费98%以上的时间执行GC,并且每次只有不到2%的堆被恢复,则JVM抛出此错误。换句话说,这意味着我们的应用程序几乎耗尽了所有可用内存,垃圾收集器花了太长时间试图清理它,并多次失败。

在这种情况下,用户会体验到应用程序响应非常缓慢,通常只需要几毫秒就能完成的某些操作,此时则需要更长的时间来完成,这是因为所有的CPU正在进行垃圾收集,因此无法执行其他任务。

代码演示

​
public class OutOfMemoryGCLimitExceed {
 
    public static void addRandomDataToMap() {
        Map<Integer, String> dataMap = new HashMap<>();
        Random r = new Random();
        while (true) {
            dataMap.put(r.nextInt(), String.valueOf(r.nextInt()));
        }
    }
    public static void main(String[] args) {
        addRandomDataToMap();
    }
}

解决办法:

1,查看项目中哪些对象占据了堆的大部分空间,优化代码。

2,JVM给出这样一个参数:-XX:-UseGCOverheadLimit 禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,替换成 java.lang.OutOfMemoryError: Java heap space。

3,增大堆内存 set JAVA_OPTS=-server -Xms512m -Xmx1024m -XX:MaxNewSize=1024m -XX:MaxPermSize=1024m

Java.lang.OutOfMemeoryError:Direct buffer memory:写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。

ByteBuffer.allocate(capability)是分配JVM堆内存,属于GC管辖范围,由于需要拷贝速度相对较慢。 ByteBuffer.allocteDirect(capability)是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快。

但是如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,程序也就直接崩溃了。

代码演示

参数设置:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

public class DirectBufferMemoryDemo {
    public static void main(String[] args) {
        System.out.println("查看配置的本地内存maxDirectMemory:"+(sun.misc.VM.maxDirectMemory() / (double)1024 / 1024) +"MB");
        //停顿一下方便看效果
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //-XX:MaxDirectMemorySize=5m 本地内存配置的是5MB,这里实际使用的是6MB
        ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);
​
    }
}

解决办法:

1)检查是否直接或间接使用了 nio ,例如手动调用生成 buffer 的方法或者使用了 nio 容器如 netty, jetty, tomcat 等等;

2)-XX:MaxDirectMemorySize 加大,该参数默认是 64M ,可以根据需求调大试试;

3)检查 JVM 参数里面有无: -XX:+DisableExplicitGC ,如果有就去掉.

如果jvm参数中设置了-XX:+DisableExplicitGC,那么代码中手动调用System.gc()就不会生效。而有些框架中因为是使用的堆外内存,必须手动调用System.gc()来释放。如果禁用掉就会导致堆外内存使用一直增长,造成内存泄露。

Java.lang.OutOfMemeoryError:unable to create new native thread:

导致原因 1 你的应用创建了太多线程了,一个应用进程创建多 个线程超过系统承载极限 2 你的服务器并不允许你的应用程序创建这么多线程,linix系统默认允许单个进程可以创建的线程数是1024个, 你的应用创建超过这个教量, 就会报jova. lang. OutofMemoryErroniinable tocreate new native thread

public class NativeThreadDemo {
​
  public static void main(String[] args) {
    for(int i=0; ; i++){
      System.out.println(" >>>>>>>>>>>> " + i);
​
      new Thread(new Runnable() {
        public void run() {
          try {
            Thread.sleep(Integer.MAX_VALUE);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      },""+i).start();
    }
  }
}

解快办法:

1.想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低 2.对于有的应用,确实需要创建很多线程,远超过Linux系统的默认1024个程的限制,可以通过修改linux服务器配置,扩大inux默认限制

这个异常问题本质原因是我们创建了太多的线程,而能创建的线程数是有限制的,导致了异常的发生。能创建的线程数的具体计算公式如下:

(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
​
MaxProcessMemory         指的是一个进程的最大内存
JVMMemory                JVM内存
ReservedOsMemory         保留的操作系统内存
ThreadStackSize          线程栈的大小

Java.lang.OutOfMemeoryError:MetaSpace:jdk1.8以后有了元空间:虚拟机加载的类信息(String ...)常量池,静态变量,即时编译后的代码,加载的那些信息,撑爆元空间 就报这个错误

解决方案:

第一:增加 Metaspace 的大小

-XX:MaxMetaspaceSize=512m

第二:直接去掉 Metaspace 的大小限制。 但需要注意, 不限制Metaspace内存的大小, 假若物理内存不足, 有可能会引起内存交换(swapping), 严重拖累系统性能。 此外,还可能造成native内存分配失败等问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值