首页 文章 精选 留言 我的

精选列表

搜索[腾讯技术创作特训营S8],共10000篇文章
优秀的个人博客,低调大师

Java技术专题-JVM研究系列(39)Java各种类型对象占用内存情况分析(上)

前言 只有当你到了一定层次,需要了解JVM内部运行机制,或者高并发多线程下,你写的代码对内存有影响,你想做性能优化。当你想深入了解java对象在内存中,如何存储,或者每个对象占用多大空间时。 内存公式 Java对象的内存布局=对象头(Header)+实例数据(Instance Data)+补齐填充(Padding)。 补齐填充 Java对象占用空间是8字节对齐的,即所有Java对象占用bytes数必须是8的倍数。 Shallow Size 对象自身占用的内存大小,不包括它引用的对象。 针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。当然这里面还会包括一些java语言特性的数据存储单元。 针对数组类型的对象,它的大小是数组元素对象的大小总和。 Retained Size Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C,C就是间接引用) 换句话说,Retained Size就是当前对象被GC后,从Heap上总共能释放掉的内存。 不过,释放的时候还要排除被GC Roots直接或间接引用的对象。他们暂时不会被被当做Garbage。 接下来用JProfiler验证: 新建一个空对象,观察空对象内存占用 public class TestObject {} 对象占用内存 16byte,如图: 结论 一般自建空对象占用内存 16Byte,16byte = 12Byte(Header) + 4Byte(Padding) 在TestObj中新增一个int属性,观察对象内存占用 public class TestObj { private int i; } 对象占用内存16byte,如图 结论 int 占用 4byte,16byte = 12byte(Header) + 4byte(int)+0byte(padding) 在TestObj中新增一个 long 属性,观察对象内存占用 public class TestObj { private long i; } 对象占用内存 24b,如图 结论 long 占用 8byte, 24byte = 12(Header) + 8(long) + 4(Padding) 其余基本类型可以参照以上自行验证,原理一样 包装类型占用 包装类(Boolean/Byte/Short/Character/Integer/Long/Double/Float)占用内存的大小 = 对象头大小 + 底层基础数据类型的大小。 包装类和其他引用类一样,会产生一个引用(reference) 在TestObj中新增一个Integer属性,观察对象内存占用 public class TestObj { private Integer i =128; } 对象占用内存 32b,如图 结论 Integer 占用16b, 32 = 12 (Header) + 16(Integer) + 4(reference) 特别的:-128~127在常量池,只占用4b,且不产生引用(reference) 在TestObj中新增一个 Long属性,观察对象内存占用 public class TestObj { private Long l = new Long(1); } 对象占用内存 40b,如图 结论 Long 占用 24b, 40 = 12 (Header) + 24(Long) + 4(reference) 其余包装类型可以参照以上自行验证,原理一样 基本类型数组占用 64位机器上,数组对象的对象头占用24 bytes,启用压缩后占用16字节。比普通对象占用内存多是因为需要额外的空间存储数组的长度(普通16b-12b)。 对象数组本身的大小=数组对象头 + length * 存放单个元素大小 在TestObj中新增一个 char[] 属性,观察对象内存占用 public class TestObj { private char[] c = {'a','b','c'}; } char[] c占用内存 40b,如图 结论 char[3] 占用 24b, 24 = 40 - 16,24 = 16(Header) + 3 * 2(char) + 2(Padding) 封装类型数组占用 封装类型数组比基本类型的数组,需要多管理元素的引用 对象数组本身的大小=数组对象头+length * 引用指针大小 + length * 存放单个元素大小。 在TestObj中新增一个 Integer[] 属性,观察对象内存占用。 public class TestObj { private Integer[] i = {128,129,130}; } Integer[] i占用内存 80b,如图 结论 Integer[3] 占用 80b, 80 = 96 - 16, 80 = 16(Header) + 3 * 4 (reference)+ 3 * 16(Integer) +4(padding) String占用内存 在TestObj中新增一个空String属性,观察对象内存占用。 public class TestObj { private String s = new String(""); } 对象占用内存 40b,如图 结论 String本身占用24byte,24=40-16,也就是说空"",也需要16byte。 注意 这里为什么要写String s = new String("")?请自己思考,不写会怎么样? 答:如果写成String s = “”,是不会再堆中开辟内存的,也就看不到String占用的空间,你看到的将会是下面的,至于为什么,都是因为final。

优秀的个人博客,低调大师

数栈技术分享:一文带你了解Flink jm、tm启动过程和资源分配

一、JM启动过程 1、从日志角度分析启动流程 1)client生成jobGraph 详情请参考: https://www.bilibili.com/video/BV13K4y1P7ri 2)Yarn RM接收到请求(和yarn交互不重点分析) 3)在被分配的节点上的工作目录下启动launch_container.sh 4)在perJob模式下,最终调用的是YarnJobClusterEntrypoint 5)初始化相关运行环境,打印软件版本、运行环境、命令行参数、classpath 等信息 6)加载flink配置文件、初始化文件系统、启动各种内部服务(RpcService、HAService、BlobServer、HeartbeatServices、MetricRegistry、ExecutionGraphStore 等) 7)启动Flink资源管理核心组件ResourceManager(包含 YarnResourceManager 和 SlotManager 两个子组件) 8)启动Dispatcher加载JobGraph 文件、并启动JobManager 9)JobManager开始执行ExecutionGraph,向 ResourceManager申请资源 10)Flink ResourceManager 接收到新分配的 Container 资源后,准备好 TaskManager 启动上下文 11)TaskManager 进程加载并运行 YarnTaskExecutorRunner(Flink TaskManager入口类),初始化流程完成后启动 TaskExecutor(负责执行Task相关操作) 12)TaskExecutor向ResourceManager注册,向SlotManager汇报自己的 Slot 资源与状态 13)JobManager向TaskExecutor提交task,TaskExecutor启动新的线程运行Task 2、整体流程分析 1)输出各软件版本及运行环境信息、命令行参数项、classpath等信息 2)注册处理各种SIGNAL的handler:记录到日志 3)注册JVM关闭保障的shutdown hook:避免JVM退出时被其他shutdown hook阻塞 4)打印YARN运行环境信息:用户名 5)从运行目录中加载flink conf 3、AM启动过程 1)创建并启动各类内部服务(包括RpcService、HAService、BlobServer、HeartbeatServices、MetricRegistry、ExecutionGraphStore等) 2)将RPC address和port更新到flink conf配置 3)创建并启动resourceManager对象(Flink资源管理核心组件,包含YarnResourceManager和SlotManager两个子组件,YarnResourceManager负责外部资源管理,与YARN RM建立通信并保持心跳,申请或释放TaskManager资源,注销应用等;SlotManager则负责内部资源管理,维护全部Slot信息和状态) 4)创建并启动dispatcher(负责接收用户提供的作业,并且负责为这个新提交的作业拉起一个新的 JobManager)及相关服务(包括REST endpoint等)并加载JobGraph。 二、JM资源分配 JobManager开始执行ExecutionGraph,向ResourceManager申请资源。 ResourceManager将资源请求加入等待请求队列,并通过心跳向YARN RM申请新的Container资源来启动TaskManager进程。 后续流程如果有空闲Slot资源,SlotManager将其分配给等待请求队列中匹配的请求,不用再通过YarnResourceManager申请新的TaskManager。 Flink ResourceManager接收到新分配的Container资源后,准备好TaskManager启动上下文(ContainerLauncherContext,生成TaskManager配置并上传至分布式存储,配置其他依赖和环境变量等)。 然后向YARN NM申请启动TaskManager进程,YARN NM启动Container的流程与AM Container启动流程基本类似。 三、TM启动过程 输出各软件版本及运行环境信息、命令行参数项、classpath等信息 注册处理各种SIGNAL的handler:记录到日志 注册JVM关闭保障的shutdown hook:避免JVM退出时被其他shutdown hook阻塞 加载flink配置文件、初始化文件系统、启动各种内部服务(RpcService、HAService、BlobServer、HeartbeatServices、MetricRegistry等) 启动tm后就可以通过RPC接收远程调用,submitTask就是接收任务的服务。 回到在JM端启动scheduler后,就开始调度Execution,在Execution的deploy()方法中通过rpc调用TM的submitTask接口。 交互流程图如下: 当submitTask收到请求后加载jobInformation和taskInformation文件,初始化jobInformation和taskInformation,然后构造Task,启动Task线程,最终调用AbstractInvokable.invoke方法。 invokable.invoke( )将根据nameOfInvokableClass的不同调度不同的任务,包括批任务、Source任务、Sink任务、流任务 DataSourceTask:Kafka Source StreamTask:中间算子 DataSinkTask:Kafka Sink 这里以StreamTask例分析 初始化、run、close 初始化:创建状态后端、operator配置、特殊task初始化、恢复算子的状态、richfunction open run:执行task,处理record并发往下游 close:关闭和清理操作 这里以flinkX中的代码为例: 会被invoke()中的initialize-operator-states()执行并调用到DtInputFormatSourceFunction的initializeState方法恢复状态。 这里以flinkX中的代码为例: 会被invoke()中的open-operators()执行并调用到DtInputFormatSourceFunction的open方法恢复状态做一些初始化工作。 这里以flinkX中的代码为例: 会被invoke()中的run()执行并调用到DtInputFormatSourceFunction的run读取数据并往下游发送。 经过上面分析,任务已经启动,并等待数据流动。 相关参考: https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=65147077https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=65147077 https://files.alicdn.com/tpsservice/7bb8f513c765b97ab65401a1b78c8cb8.pdfhttps://files.alicdn.com/tpsservice/7bb8f513c765b97ab65401a1b78c8cb8.pdf https://zhuanlan.zhihu.com/p/87132673https://zhuanlan.zhihu.com/p/87132673 数栈是云原生—站式数据中台PaaS,我们在github和gitee上有一个有趣的开源项目:FlinkX,FlinkX是一个基于Flink的批流统一的数据同步工具,既可以采集静态的数据,也可以采集实时变化的数据,是全域、异构、批流一体的数据同步引擎。大家喜欢的话请给我们点个star!star!star! github开源项目:https://github.com/DTStack/flinkx gitee开源项目:https://gitee.com/dtstack_dev_0/flinkx

优秀的个人博客,低调大师

Java技术专题-JVM研究系列(26)让你完全攻克内存溢出(OOM)这一难题

每日一句 只有经历地狱般的磨练,才能创造出天堂般的力量。 堆(Heap)内存不足 报错信息: java.lang.OutOfMemoryError: Java heap space 导致原因 代码中可能存在大对象分配 可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。 业务场景会剧增对象数据,应该提升内存空间。 解决方法 检查是否存在大对象的分配,最有可能的是大数组分配 通过jmap命令,把堆内存dump下来,使用mat工具分析一下,检查是否存在内存泄露的问题 如果没有找到明显的内存泄露,使用 -Xms/-Xmx 加大堆内存 还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性 方法区溢出 报错信息: java.lang.OutOfMemoryError: PermGen space java.lang.OutOfMemoryError: Metaspace 导致原因 JDK8之前,永久代是HotSot 虚拟机对方法区的具体实现,存放了被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。 JDK8后,元空间替换了永久代,元空间使用的是本地内存,还有其它细节变化: 字符串常量由永久代转移到堆中 和永久代相关的JVM参数已移除 出现永久代或元空间的溢出的原因可能有如下几种: 在Java7之前,频繁的错误使用String.intern方法。 生成了大量的代理类,导致方法区被撑爆,无法卸载。 应用长时间运行,没有重启。 解决方法 永久代/元空间 溢出的原因比较简单,解决方法有如下几种: 检查是否永久代空间或者元空间设置的过小。 检查代码中是否存在大量的反射操作或者class加载操作以及生产class字节码。 dump之后通过mat检查是否存在大量由于反射生成的代理类 放大招,重启JVM GC overhead limit exceeded 报错信息 java.lang.OutOfMemoryError:GC overhead limit exceeded 导致原因 这个是JDK6新加的错误类型,一般都是堆太小导致的。 Sun 官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。 解决方法 检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。 添加参数-XX:-UseGCOverheadLimit 禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap space。 dump内存,检查是否存在内存泄露,如果没有,加大内存。 虚拟机栈和本地方法栈溢出 由于在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说,-Xoss参数(设置本地方法栈大小)虽然存在,但实际上是无效的,栈容量只由-Xss参数设定。关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常: 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。 这里把异常分成两种情况看似更加严谨,但却存在着一些互相重叠的地方:当栈空间无法继续分配时,到底是内存太小,还是已使用的栈空间太大,其本质上只是对同一件事情的两种描述而已。 虚拟机的 StackOverflowError 异常 -Xss参数减小栈内存的容量,然后不断调用方法造成栈溢出,StackOverflowError 异常。 public class JVMStackSOF { private int stacklength = 1; // 记录栈深度 // 调用这个递归方法以造成栈溢出 public void stackPush(){ stacklength++; stackPush(); } public static void main(String[] args) throws Throwable{ JVMStackSOF sof = new JVMStackSOF(); try{ sof.stackPush(); }catch(Throwable e){ System.out.println("stack length = " + sof.stacklength); throw e; } } } openjdk@ubuntu:~$ java -Xss256k -cp /home/openjdk/NetBeansProjects/JavaApplication1/build/classes test_JVMStackSOF.JVMStackSOF stack length = 1888 Exception in thread "main" java.lang.StackOverflowError at test_JVMStackSOF.JVMStackSOF.stackPush(JVMStackSOF.java:17) at test_JVMStackSOF.JVMStackSOF.stackPush(JVMStackSOF.java:18) -Xss256K:设置参数栈内存容量为256K 在单个线程下,无论是由于栈帧太大,还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。 使用-Xss参数减少栈内存容量。结果:抛出StackOverflowError异常,异常出现时输出的栈深度相应缩小。 定义了大量的本地变量,增加此方法帧中本地变量表的长度。结果:抛出StackOverflowError异常时输出的栈深度相应缩小。 虚拟机栈隔离的,每个线程都有自己独立的虚拟机栈。 在 Java 虚拟机规范中,对虚拟机栈这个区域规定了两种异常状况: 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常; 如果虚拟机栈可以动态扩展(当前大部分的 Java 虚拟机都可动态扩展),在扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常。 虚拟机的 OutOfMemoryError 异常 通过-Xss2M参数增大栈内存的容量,然后不断开启新的线程,抛出OutOfMemoryError 异常。 public class JVMStackOOM { private void dontStop() { while (true) { } } public static void main(String[] args) { // 不断开启新的线程消耗虚拟机栈空间 while (true) { new Thread(new Runnable() { @Override public void run() { dontStop(); } }).start(); } } } 原理 主要是因为-Xss参数设置的是一个线程的栈大小,前面已经说过虚拟机栈是线程私有的,即每个线程都有一个自己的栈。 操作系统分配给每个进程的内存是有限制的,譬如 32 位的 Windows 限制为 2GB。Java虚拟机提供了参数来控制 Java 堆和方法区的这两部分内存的最大值。 2GB(操作系统限制的内存大小)减去 Xmx(最大堆容量),再减去 MaxPermSize(最大方法区容量),程序计数器消耗内存很小,可以忽略掉。如果虚拟机进程本身耗费的内存不计算在内,剩下的内存就由虚拟机栈和本地方法栈“瓜分”了。 所以每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽。第一例中把栈空间占满而抛出 StackOverflowError 异常,第二例中把内存消耗完而抛出 OutOfMemoryError 异常。 方法栈溢出(从属于虚拟机栈的异常) 报错信息 java.lang.OutOfMemoryError : unable to create new native Thread 导致原因 出现这种异常,基本上都是创建的了大量的线程导致的,以前碰到过一次,通过jstack出来一共8000多个线程。 解决方法 通过 -Xss 降低的每个线程栈大小的容量 线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制 /proc/sys/kernel/pid_max /proc/sys/kernel/thread-max max_user_process(ulimit -u) /proc/sys/vm/max_map_count 非常规溢出 下面这些OOM异常,可能大部分的同学都没有碰到过,但还是需要了解一下 分配超大数组 报错信息 java.lang.OutOfMemoryError: Requested array size exceeds VM limit 这种情况一般是由于不合理的数组分配请求导致的,在为数组分配内存之前,JVM 会执行一项检查。要分配的数组在该平台是否可以寻址(addressable),如果不能寻址(addressable)就会抛出这个错误。 解决方法就是检查你的代码中是否有创建超大数组的地方。 swap区溢出 报错信息 : java.lang.OutOfMemoryError: Out of swap space 这种情况一般是操作系统导致的,可能的原因有: swap 分区大小分配不足; 其他进程消耗了所有的内存。 解决方案 其它服务进程可以选择性的拆分出去 加大swap分区大小,或者加大机器内存大小 本地方法溢出 报错信息 : java.lang.OutOfMemoryError: stack_trace_with_native_method 本地方法在运行时出现了内存分配失败,和之前的方法栈溢出不同,方法栈溢出发生在 JVM 代码层面,而本地方法溢出发生在JNI代码或本地方法处。 本机直接内存溢出 直接内存可以通过:-XX:MaxDirectMemorySize 来设置大小,如果不设置,默认和堆在最大值-Xmx一样大。 设置本机直接内存的原则就是,各种内存大小+本机直接内存大小<机器物理内存。 下面程序利用 DirectByteBuffe 模拟直接内存溢出的情况 import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; public class DirectBufferOom { public static void main(String[] args) { final int _1M = 1024 * 1024; List<ByteBuffer> buffers = new ArrayList<>(); int count = 1; while (true) { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1M); buffers.add(byteBuffer); System.out.println(count++); } } } 在命令行运行 java -XX:MaxDirectMemorySize=10M DirectBufferOom ,很快控制台就会出现异常 Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory at java.nio.Bits.reserveMemory(Bits.java:695) at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123) at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311) at DirectBufferOom.main(DirectBufferOom.java:12) 其实它并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配,于是手动抛出异常。下 面的程序利用 Unsafe 类模拟直接内存溢出 import sun.misc.Unsafe; import java.lang.reflect.Field; public class UnsafeOom { private static final int _1M = 1024 * 1024; public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException { Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true) { unsafe.allocateMemory(_1M); } } } 在命令行运行 java -XX:MaxDirectMemorySize=10M UnsafeOom ,结果如下 Exception in thread"main"java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at org.fenixsoft.oom.DMOOM.main(DMOOM.java:20) 由 DirectMemory 导致的内存溢出,一个明显的特征是在 Heap Dump 文件中不会看见明显的异常,如果读者发现 OOM 之后 Dump 文件很小,而程序中又直接或间接使用了 NIO ,那就可以考虑检查一下是不是这方面的原因。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册