简单说说JVM堆区的相关知识

一、堆概述

  • 一个jvm实例(进程)只存在一个堆内存,堆也是java内存管理的核心区域。
  • java 堆区在jvm启动时即被创建,其空间大小也就被确定了
  • 《java虚拟机规范》规定,堆可以处于物理上不连续的内存空间,但在逻辑上它应该被称为连续的
  • 所有线程共享java堆,在这里和可以划分线程私有的缓冲区(tlab)
  • 所有对象实例以及数组都应在运行时分配在堆中
  • 方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集时候才会被移除
  • 堆是gc执行垃圾回收的重点区域

1.1 堆内存细分

现代垃圾收集器大部分基于分代收集理论设计,堆空间细分为:

  • java 7 之前堆内存逻辑分为:新生区+老年区+永久区
  • java 8 之后内存逻辑上分为:新生区+老年区+元空间

使用下面命令设置堆空间初始化 10m,最大空间 10m

-Xms10m -Xmx10m

使用java visual 查看 visual gc

可以看出通过参数设置的内存大小 只与新生代(Eden+s0+s1 ),老年代有关,而在逻辑上还要加上元空间。

1.2 堆空间大小的设置

1.2.1 通过参数设置

-Xms 用来设置堆空间(年轻代+老年代)的初始内存大小
  -X 是jvm的运行参数
  ms 是memory start

-Xmx 用来设置堆空间(年轻代+老年代)的初始内存大小
  • 一旦堆区中的内存大小超过“-Xmx”所指定的最大内存时,将会抛出OutOfMemoryError异常。
  • 通常会将 -Xms 和 -Xmx 两个参数配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能

1.2.2 默认空间大小

  • 初始化内存大小为物理内存的 1/64
  • 最大内存大小为物理内存大小 1/4

使用一下代码查看 当前jvm初始化内存与最大内存

/**
 * @program: jvmDemo
 * @description:
 * @author: wfg
 * @create: 2021-06-14 10:40
 */
public class Test9 {

    public static void main(String[] args) {
        //返回jvm中的内存总量(字节)
          long initialMemory = Runtime.getRuntime().totalMemory()/1024/1024;
        //虚拟机将尝试使用最大堆内存
         long maxMemory = Runtime.getRuntime().maxMemory()/1024/1024;
        System.out.println("-Xms:"+initialMemory+"m");

        System.out.println("-Xmx:"+maxMemory+"m");

        System.out.println("系统大小:"+initialMemory*64/1024+"G");
        System.out.println("系统大小:"+maxMemory*4/1024+"G");

    }

}

结果(本机运行内存为 8g)

1.2.3 通过参数设置堆空间大小后内存不一致问题

设置300

查看

/**
 * @program: jvmDemo
 * @description:
 * @author: wfg
 * @create: 2021-06-14 10:40
 */
public class Test9 {

    public static void main(String[] args) {
        //返回jvm中的内存总量(字节)
        long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        //虚拟机将尝试使用最大堆内存
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

        System.out.println("-Xms:" + initialMemory + "m");

        System.out.println("-Xmx:" + maxMemory + "m");

    }

}

结果

分析

在vm参数设置里面加上

-XX:+PrintGCDetails 

再次运行程序查看

原理

新生代的s0 和 s1 只能有一个生效

1.3 年轻代与年老代

  • 存储在jvm中的java对象可以划分为两类:

一类是生命周期较短的瞬时对象。

另外一类是对象生命周期非常长。

  • jvm堆区再次进行细分可以分为 年轻代与老年代
  • 其中年轻代可以划分为Eden空间,survivor0空间和survivor1空间(有时也叫 from区,to区)

  • 新生代与老年代空间大小 默认是 1:2

可以通过 -XX:NewRation设置新生代与老年代的比例,默认值是2.(一般都不会去设置)

  • Eden与s0,s1 的内存默认分配比例为 8:1:1

1.4 对象分配过程

1.new的对象先放伊甸区,此区有大小限制

2.当伊甸区满的时候,程序需要创建时,jvm的垃圾回收将对伊甸园区进行垃圾回收(minor gc)

3.然后将伊甸园中的剩余对象移动到辛存者0

4.如果再次触发垃圾回收,上次幸存下来的放到幸存者0区,没有回收,就会放到幸存者1区

5.再次经历垃圾回收会重新放回辛存者0区

6.当在辛存者区达到15次时,就可以去老年区了

可以设置参数:-XX:MaxTenuringThreshold=

7.当养老区内存不足时,再次触发GC:major GC,进行养老区的内存处理

8.若养老区进行处理后,依然无法进行对象的保存,就会产生00m异常

java.lang.outofMemoryError:java heap space

9.总结

  • 针对幸存者s0,s1区总结:复制之后有交换谁空谁是to
  • 垃圾回收:频繁在新生区收集,很少在养老区收集,几乎不再永久区/元空间收集

10.流程

当Eden满时,会触发MinorGC算法来回收memory,旨在清理掉再无引用的数据(在内存里是Tree),意图存储到S0. 若此时S0也满了,会再次MinorGC意图回收S0无引用的数据,把有引用的数据移动到S1。如果S1够用,此时会清空S0;如果S1满了,会回滚刚存入S1的数据,直接把本次GC的数据存入Old区,S0保持刚刚MinorGC时的状态。延伸:如果Old也满了,会触发MajorGC,如果还是不够,则存入Permanent Generate,不幸这里也满了,会在允许的范围内按照内置的规则自动增长,可能不会发生GC,也可能会。当增长的量不够存时,会触发Full GC。若FullGC后还是不够存,自动增长的量也超过了允许的范围,则发生内存溢出。还有一种情况,就是分配的线程栈处于很深的递归或死循环时,会发生栈内存溢出。

二、对象分配过程:Tlab

2.1 为什么要有tlab

  • 堆区是线程共享的,任何线程都可以访问到堆区中的共享数据
  • 由于对象实例的创建在jvm中非常频繁,因此在并发环境下从堆区中划分内存空间是不安全的
  • 而加锁会影响分配速度

2.2什么是tlab

  • 从内存模型而不是垃圾收集角度,对eden区域进行划分,jvm为每个线程分配了一个私有缓存区域
  • 多线程同时分配内存时,使用tlab可以避免非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们将其称为 快速分配策略
  • jvm将tlab作为内存分配的首选
  • 在程序中可以通过参数-XX:UseTLAB设置是否开启(默认是开启的)
  • tlab仅占有eden空间大小的1%,可以通过-XX:TLABWasteTargetPercent设置tlab空间所占用eden空间的大小
  • 使用tlab空间分配内存失败时,jvm会使用加锁机制确保操作的原子性

对象分配流程

三、堆空间常用参数设置

  • -XX:+PrintFlagsInitial: 查看所有参数的默认初始值
  • -XX:+PrintFlagFinal: 查看所有参数的最终值
  • -Xms::初始化堆空间内存(默认为物理内存的1/64)
  • -Xmx: 最大堆空间内存(默认为物理内存的1/4)
  • -Xmn:设置新生代的大小(初始值及最大值)
  • -XX:NewRatio: 配置新生代与老年代在堆结构的占比
  • -XX:SurvivorRatio:设置新生代中 eden和s0、s1空间的比例
  • -XX:MaxTenuringThreshold:设置新生代垃圾最大的年龄
  • -XX:+PrintGCDetails:输出详细的gc处理日志
  • -XX:+PrintGC:输出简要的gc处理日志

四、堆是分配对象的唯一选择吗

  1. 随着jit编译期的发展与逃逸分析技术成熟,栈上分配与标量替换优化技术导致所有对象都分配到堆上不那么绝对了
  2. 如果经过逃逸分析后发现,一个对象并没有逃逸出方法的话,那么可能被优化成栈上分配

4.1 逃逸分析

  • 当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
  • 当一个对象在方法中被定义后,被外部方法所引用,则认为发生逃逸了

判断逃逸的方法:看new 的对象实体是否有可能在方法外被调用

4.2 代码优化

  • 栈上分配。将堆分配转化为栈分配。
  • 同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步
  • 分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分可以不存储在内存,而是存储在cpu寄存器中。
  • 标量是指一个无法在分解成更小的数据,相对的其它还可以在分解的称为聚合量
  • 在jit阶段进行逃逸分析,发现对象不会被外界访问(没有逃逸发生),经过jit优化,就回把对象分解为成员变量来代替,这个过程就是标量替换

到此这篇关于简单说说JVM堆区的相关知识的文章就介绍到这了,更多相关JVM堆区内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈Java堆外内存之突破JVM枷锁

    对于有Java开发经验的朋友都知道,Java中不需要手动的申请和释放内存,JVM会自动进行垃圾回收:而使用的内存是由JVM控制的. 那么,什么时机会进行垃圾回收,如何避免过度频繁的垃圾回收?如果JVM给的内存不够用,怎么办? 此时,堆外内存登场!利用堆外内存,不仅可以随意操控内存,还能提高网络交互的速度. 背景1:JVM内存的分配 对于JVM的内存规则,应该是老生常谈的东西了,这里我就简单的说下: 新生代:一般来说新创建的对象都分配在这里. 年老代:经过几次垃圾回收,新生代的对象就会放在年老代里

  • JVM 堆和栈的区别

    栈内存: 程序在栈内存中运行 栈中存的是基本数据类型和堆中对象的引用 栈是运行时的单元 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据 一个线程一个独立的线程栈 堆内存:  程序运行所需的大部分数据保存在栈内存中 堆中存的是对象 堆是存储的单元,堆只是一块共享的内存 堆解决的是数据存储的问题,即数据怎么放,放在哪儿 所有线程共享堆内存 Java中的参数传递( 传值呢?还是传引用? ): 程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题,不会直接传递对象本

  • JVM入门之内存结构(堆、方法区)

    目录 1.堆 1.1 定义 1.2 堆的作用 1.3 特点 1.4 堆内存溢出 1.5 堆内存诊断 2.方法区 2.1 结构(1.6 对比 1.8) 2.2 内存溢出 2.3 常量池 2.4 运行时常量池 2.5 常量池与串池的关系 2.6 StringTable的位置 2.7 StringTable 垃圾回收 2.8 方法区的垃圾回收 3.直接内存 释放原理 1.堆 1.1 定义 是Java内存区域中一块用来存放对象实例的区域[几乎所有的对象实例都在这里分配内存] 通过new关键字创建的对象都

  • 深入JVM剖析Java的线程堆栈

    在这篇文章里我将教会你如何分析JVM的线程堆栈以及如何从堆栈信息中找出问题的根因.在我看来线程堆栈分析技术是Java EE产品支持工程师所必须掌握的一门技术.在线程堆栈中存储的信息,通常远超出你的想象,我们可以在工作中善加利用这些信息. 我的目标是分享我过去十几年来在线程分析中积累的知识和经验.这些知识和经验是在各种版本的JVM以及各厂商的JVM供应商的深入分析中获得的,在这个过程中我也总结出大量的通用问题模板. 那么,准备好了么,现在就把这篇文章加入书签,在后续几周中我会给大家带来这一系列的专

  • 记一次公司JVM堆溢出抽丝剥茧定位的过程解析

    背景 公司线上有个tomcat服务,里面合并部署了大概8个微服务,之所以没有像其他微服务那样单独部署,其目的是为了节约服务器资源,况且这8个服务是属于边缘服务,并发不高,就算宕机也不会影响核心业务. 因为并发不高,所以线上一共部署了2个tomcat进行负载均衡. 这个tomcat刚上生产线,运行挺平稳.大概过了大概1天后,运维同事反映2个tomcat节点均挂了.无法接受新的请求了.CPU飙升到100%. 排查过程一 接手这个问题后.首先大致看了下当时的JVM监控. CPU的确居高不下 FULL

  • JVM 心得 OOM时的堆信息获取方法与分析

    JVM的框架知识了解之后,实际的项目里发生了OOM异常的话,怎么获取以及分析异常信息后怎么分析呢. 这里稍微做一下归纳. 第一步,首先通过下面两个方法的任何一种,把发生OOM时的heap信息dump下来. 有两个方法,通过设置可以把OOM时的dump信息获取到: 1)方法1:在JVM的启动参数里添加如下命令 -XX:+HeapDumpOnOutOfMemoryError 2)方法2:在JDK的安装目录下,找到bin目录,然后双击执行"jvisualvm.exe" 执行程序之后,在视图里

  • 详解JVM栈溢出和堆溢出

    一.栈溢出StackOverflowError 栈是线程私有的,生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息. 栈溢出:方法执行时创建的栈帧个数超过了栈的深度. 原因举例:方法递归 [示例]: public class StackError { private int i = 0; public void fn() { System.out.println(i++); fn(); } public static void mai

  • 简单说说JVM堆区的相关知识

    一.堆概述 一个jvm实例(进程)只存在一个堆内存,堆也是java内存管理的核心区域. java 堆区在jvm启动时即被创建,其空间大小也就被确定了 <java虚拟机规范>规定,堆可以处于物理上不连续的内存空间,但在逻辑上它应该被称为连续的 所有线程共享java堆,在这里和可以划分线程私有的缓冲区(tlab) 所有对象实例以及数组都应在运行时分配在堆中 方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集时候才会被移除 堆是gc执行垃圾回收的重点区域 1.1 堆内存细分 现代垃圾收集器大部分基

  • 简单了解springboot中的配置文件相关知识

    这篇文章主要介绍了简单了解springboot中的配置文件相关知识,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 springboot中可以有多个配置文件,配置文件可以是.properties或则yml结尾的文件,并且配置文件有优先级,相同种类的配置application.properties 的优先级比较高,不同种类配置同时都会生效. 也可以自定义配置文件,随意命名,但是后缀名必须按照要求来!后台也可以通过@Value("${key}&quo

  • 简单了解python协程的相关知识

    什么是协程 协程是python种一种实现多任务的方式,他是一种比线程更加小的单元,占用更小的执行单元(资源),为啥说他是一个执行单元,因为他自带CPU上下文,这样在合适gr的时机,可以把一个协程切换到另一个协程,只要在这个过程中保存和恢复cpu上下文那么程序还是可以运行的 通俗的理解: 一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量,然后切换到另一个函数中运行,并且切换的次数以及社么时候再切换回来是可控的 协程和线程的差异 在实现多任务时,线程会自己欢子一些数据,操作系统切换时需

  • 解决jmap命令打印JVM堆信息异常的问题

    jmap命令可以打印java进程的JVM堆信息,今天在某台机器上运行该命令查看 19560进程的堆信息 jmap -heap 19560 出现以下异常 Attaching to process ID 19560, please wait... Debugger attached successfully. Server compiler detected. JVM version is 24.79-b02 using thread-local object allocation. Paralle

  • JVM内存结构相关知识解析

    最近在看< JAVA并发编程实践 >这本书,里面涉及到了 Java 内存模型,通过 Java 内存模型顺理成章的来到的 JVM 内存结构,关于 JVM 内存结构的认知还停留在上大学那会的课堂上,一直没有系统的学习这一块的知识,所以这一次我把< 深入理解Java虚拟机JVM高级特性与最佳实践 >.< Java虚拟机规范 Java SE 8版 >这两本书中关于 JVM 内存结构的部分都看了一遍,算是对 JVM 内存结构有了新的认识.JVM 内存结构是指:Java 虚拟机定义

  • 简单了解Spring Cloud Alibaba相关知识

    这篇文章主要介绍了简单了解Spring Cloud Alibaba相关知识,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 官方github地址 Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案.此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务. 主要功能 服务限流降级:默认支持 WebServlet.WebFlux, OpenFeign

  • 简单谈谈Python面向对象的相关知识

    一.私有化 上篇说过封装,既将我们不想让别人看到代码的内容,但是又需要用到的内容,通过类内部调用来实现调用. 说到这里却不得不提一下上篇的: class Person(object): def __init__(self, name, age): self.xxx = name self.xxxx = age 这里面self后面的名字,是可以自己随意命名的,上一篇和后面一样只是为了好记忆罢了 只要你记得住,便是颠倒也是无事 1.1 属性私有化 何为属性私有? 举个例子便是:你的私房钱,你的手机电

  • JAVA内存空间相关知识汇总

    Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识.一般Java在内存分配时会涉及到以下区域: ◆寄存器:我们在程序中无法控制 ◆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中 ◆堆:存放用new产生的数据 ◆静态域:存放在对象中用static定义的静态成员 ◆常量池:存放常量 ◆非RAM存储:硬盘等永久存储空间 Java内存

  • Java泛型机制与反射原理相关知识总结

    一.泛型的概念 1.1 基础案例 泛型在Java中的应用非常广泛,最常见则是在集合容器中,先看下基础用法: public class Generic01 { public static void main(String[] args) { Map<Integer,String> map = new HashMap<>() ; map.put(88,"hello") ; // map.put("99","world") ;

  • Java数组与堆栈相关知识总结

    一.数组创建 1.1 声明并赋值 int[] a = {1,2,3}; 1.2 声明数组名开辟空间并且赋值 int[] a; a = new int[]{1,2,3}; 1.3 声明数组时指定元素个数然后赋值 int[] a= new int[3]; 这里Java会默认数组元素值为0 1.4 在以上的基础上创建多维数组 int[][] a = {{1,2,3},{4,5,6},{7,8,9}}; //每个子数组元素个数不要求均相同 int[][] a = new int[m][n]; //其中n

随机推荐