☕【JVM原理探索】分析堆外内存(Direct Memory)使用和分析

☕【JVM原理探索】分析堆外内存(Direct Memory)使用和分析

这是我参与更文挑战的第19天,活动详情查看: 更文挑战 堆外内存 堆外内存,其实就是不受JVM控制的内存。简单来说,除了堆栈内存,剩下的就都是堆外内存了(当然,这是从Java运行时内存的角度来看),堆外内存直接受操作系统管理,而不是虚拟机。而使用堆外内存的原因, 相比于堆内内存有几个优势: 减少了垃圾回收的工作,因为垃圾回收会暂停其他的工作(可能使用多线程或者时间片的方式,根本感觉不到) 堆外内存是直接受操作系统管理的,而不是JVM,因此使用堆外内存的话,就可以保持一个比较小的堆内内存,减少垃圾回收对程序性能的影响。 就是提高IO操作的效率!这里就涉及用户态与内核态,以及内核缓冲区的概念,如果从堆内向磁盘写数据,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,但使用堆外内存的话则可以避免这个复制操作。 堆内内存其实就是用户进程的【进程缓冲区】,属于用户态;堆外内存由操作系统管理【内核缓冲区】,属于内核态。 自然也有不好的一面: 堆外内存难以控制,如果内存泄漏,那么很难排查 堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合。 因为是操作系统的内存机制,所以需要通过本地方法进行分配,较为复杂和缓慢 直接内存使用 堆外内存通过java.nio的ByteBuffer来创建,调用allocateDirect方法申请即可。 可以通过设置-XX:MaxDirectMemorySize=10M控制堆外内存的大小。 堆外内存的垃圾回收 由于堆外内存并不直接控制于JVM,因此只能等到full GC的时候才能垃圾回收!Full GC,一般发生在年老代垃圾回收以及调用System.gc的时候,这样肯定不能满足我们的需求! 手动的控制回收堆外内存了!其中sun.nio其实是java.nio的内部实现。 package xing.test; import java.nio.ByteBuffer; import sun.nio.ch.DirectBuffer; public class NonHeapTest { public static void clean(final ByteBuffer byteBuffer) { if (byteBuffer.isDirect()) { ((DirectBuffer)byteBuffer).cleaner().clean(); } } public static void sleep(long i) { try { Thread.sleep(i); }catch(Exception e) { /*skip*/ } } public static void main(String []args) throws Exception { ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 200); System.out.println("start"); sleep(5000); c

原来10张图就可以搞懂分布式链路追踪系统原理

原来10张图就可以搞懂分布式链路追踪系统原理

分布式系统为什么需要链路追踪? 随着互联网业务快速扩展,软件架构也日益变得复杂,为了适应海量用户高并发请求,系统中越来越多的组件开始走向分布式化,如单体架构拆分为微服务、服务内缓存变为分布式缓存、服务组件通信变为分布式消息,这些组件共同构成了繁杂的分布式网络。 微服务架构(极简版) 假如现在有一个系统部署了成千上万个服务,用户通过浏览器在主界面上下单一箱茅台酒,结果系统给用户提示:系统内部错误,相信用户是很崩溃的。 运营人员将问题抛给开发人员定位,开发人员只知道有异常,但是这个异常具体是由哪个微服务引起的就需要逐个服务排查了。 界面出现异常难以排查后台服务 开发人员借助日志逐个排查的效率是非常低的,那有没有更好的解决方案了? 答案是引入链路追踪系统。 什么是链路追踪? 分布式链路追踪就是将一次分布式请求还原成调用链路,将一次分布式请求的调用情况集中展示,比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。 链路跟踪主要功能: 故障快速定位:可以通过调用链结合业务日志快速定位错误信息。 链路性能可视化:各个阶段链路耗时、服务依赖关系可以通过可视化界面展现出来。 链路分析:通过分析链路耗时、服务依赖关系可以得到用户的行为路径,汇总分析应用在很多业务场景。 链路追踪基本原理 链路追踪系统(可能)最早是由Goggle公开发布的一篇论文 《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》 被大家广泛熟悉,所以各位技术大牛们如果有黑武器不要藏起来赶紧去发表论文吧。 在这篇著名的论文中主要讲述了Dapper链路追踪系统的基本原理和关键技术点。接下来挑几个重点的技术点详细给大家介绍一下。 Trace Trace的含义比较直观,就是链路,指一个请求经过所有服务的路径,可以用下面树状的图形表示。 traceId串联请求形成链路 图中一条完整的链路是:chrome -> 服务A -> 服务B -> 服务C -> 服务D -> 服务E -> 服务C -> 服务A -> chrome。服务间经过的局部链路构成了一条完整的链路,其中每一条局部链路都用一个全局唯一的traceid来标识。 Span 在上图中可以看出来请求经过了服务A,同时服务A又调用了服务B和服务C,但是先调的服务B还是服务C呢?从图中很难看出来,只有通过查看源码才知道顺序。 为了表达这种父子关系引入了Span的概念。 同一层级parent id相同,span id不同,span id从小到大表示请求的顺序,从下图中可以很明显看出

JVM内存结构、Java内存模型和Java对象模型

JVM内存结构、Java内存模型和Java对象模型

Java作为一种面向对象的,跨平台语言,其对象、内存等一直是比较难的知识点。而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚。比如本文我们要讨论的JVM内存结构、Java内存模型和Java对象模型,这就是三个截然不同的概念,但是很多人容易弄混。 可以这样说,很多高级开发甚至都搞不不清楚JVM内存结构、Java内存模型和Java对象模型这三者的概念及其间的区别。甚至我见过有些面试官自己也搞的不是太清楚。不信的话,你去网上搜索Java内存模型,还会有很多文章的内容其实介绍的是JVM内存结构。 首先,这三个概念是完全不同的三个概念。本文主要对这三个概念加以区分以及简单介绍。 JVM内存结构 我们都知道,Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途。 其中有些区域随着虚拟机进程的启动而存在,而有些区域则依赖用户线程的启动和结束而建立和销毁。在《Java虚拟机规范(Java SE 8)》中描述了JVM运行时内存区域结构如下: 各个区域的功能不是本文重点,就不在这里详细介绍了。这里简单提几个需要特别注意的点: 1、以上是Java虚拟机规范,不同的虚拟机实现会各有不同,但是一般会遵守规范。 2、规范中定义的方法区,只是一种概念上的区域,并说明了其应该具有什么功能。但是并没有规定这个区域到底应该处于何处。所以,对于不同的虚拟机实现来说,是有一定的自由度的。 3、不同版本的方法区所处位置不同,上图中划分的是逻辑区域,并不是绝对意义上的物理区域。因为某些版本的JDK中方法区其实是在堆中实现的。 4、运行时常量池用于存放编译期生成的各种字面量和符号应用。但是,Java语言并不要求常量只有在编译期才能产生。比如在运行期,String.intern也会把新的常量放入池中。 5、除了以上介绍的JVM运行时内存外,还有一块内存区域可供使用,那就是直接内存。Java虚拟机规范并没有定义这块内存区域,所以他并不由JVM管理,是利用本地方法库直接在堆外申请的内存区域。 6、堆和栈的数据划分也不是绝对的,如HotSpot的JIT会针对对象分配做相应的优化。 如上,做个总结,JVM内存结构,由Java虚拟机规范定义。描述的是Java程序执行过程中,由JVM管理的不同数据区域。各个区域有其特定的功能。 Java内存模型 Java内存模型看上去和Java内存结构(JVM内存结构)差不多,很多人会误以为两者是一回事儿,这也就导致面试过程中经常答非所为。 在前面的关于J

Java 伪共享的原理深度解析以及避免方法

Java 伪共享的原理深度解析以及避免方法

缓存系统中的缓存是以缓存行(cache line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,因为都会导致同一个缓存行失效而会无意中影响彼此的性能,这就是伪共享(false sharing)。由于从代码中很难看出是否会出现伪共享,有人将其描述成无声的性能杀手。 1 CPU Cache 众所周知,CPU的运算速度远远快于主内存、硬盘的运算速度。为了解决计算机系统中主内存与CPU 之间运行速度差问题,避免直接访问内存或者磁盘,降低因为速度差对CPU整体吞吐量的影响,一般会在CPU与主内存之间添加一级或者多级高速缓冲结构( Cache )。这个Cache 一般是被集成到CPU 内部的,所以也叫CPU Cache。 按照数据读取顺序和与 CPU 结合的紧密程度,CPU 缓存可以分为一级缓存L1,二级缓存L2,部分高端 CPU 还具有三级缓存L3。每一级缓存中所储存的全部数据都是下一级缓存的一部分,越靠近 CPU 的缓存越快也越小。L1和L2只能被一个单独的 CPU 核使用,L3被CPU 核共享,主存由所有 CPU 核共享。 当 CPU 执行运算的时候,它先去 L1 查找所需的数据,再去 L2,然后是 L3,最后如果这些缓存中都没有,所需的数据就要去主内存拿。走得越远,运算耗费的时间就越长。所以如果你在做一些很频繁的事,你要确保数据在 L1 缓存中。 引用《深入理解计算机系统》书中关于CPU缓存和内存、硬盘存储器的层次结构: 现代多核架构,一个物理 CPU 可以存在多个物理 Core,而每个 Core 又可以存在多个硬件线程: 2 Cache Line 计算机缓存系统中是以缓存行(cache line)为单位存储的,Cache Line 是 CPU 和主存之间数据传输的最小单位,缓存行通常是 64 字节。 当一行 Cache Line 被从内存拷贝到 Cache 里,Cache 里会为这个 Cache Line 创建一个条目。这个 Cache 条目里既包含了拷贝的内存数据,即 Cache Line,又包含了这行数据在内存里的位置等元数据信息。 当CPU 访问某个变量时,首先会去看CPU Cache 内是否有该变量,如果有则直接从中获取,否则就去主内存里面获取该变量,然后把该变量所在内存区域的一个Cache 行大小的内存复制到Cache 中。由于存放到Cache 行的是内存块而不是单个变量,所以可能会把多个连续的变量存放到一个Cache 行中。 由此,所以通常情况下访问连续存储的数据会比随机访问要快,访问数组结构通常比链结构快,因为通常数组在内存中是连续分配的。注意:一些JVM实现中大数组不是分配在连续空间的。 3 伪共享 如上图,变量x 和y 同时被放

C/C++ 語言測量時間函數,評估程式執行效能方法整理

C/C++ 語言測量時間函數,評估程式執行效能方法整理

這裡整理了 C/C++ 中各種測量時間的函數與用法,並提供完整的範例程式碼,讓程式開發者方便測量程式執行速度。 這裡我蒐集了一些在 C/C++ 中常見的程式執行速度測量方式,因為時間的量測方式與細節非常多,這裡只是簡單寫一些常用的方式與範例。 程式中的時間 在測量程式執行所花費的時間前,必須先認識一下時間的測量方式,不同的測量方法會得到不同的結果,其意義也不同。 Wall-Clock Time Wall-clock time 顧名思義就是真實世界的時間,相當於以牆上的時鐘為依據所計算出來的時間,這個時間會牽涉到校時、時區以及夏令時間之類的問題,詳細說明請參考維基百科的 Wall-clock time 說明。 由於 wall-clock time 並不是單調遞增(monotonic)的數值,所以它不是一個穩定的時間依據,只能做為參考用,若需要非常精準的量測程式效能,不建議使用這種時間。 CPU Time CPU time 是指程式真正使用 CPU 在執行的時間,而這個時間又可以細分為兩種: user time:程式本身執行的時間(user space)。 system time:作業系統層級執行的時間(kernel space)。 詳細說明請參考維基百科的 CPU time 與 User space 說明。 對於多執行緒(multithreading)的程式,其 CPU time 就是每條執行緒的執行時間總和,所以平行化的程式其 CPU time 可能會比 wall-clock time 還要長。 C 語言範例 這是一個利用蒙地卡羅演算法計算 pi 的範例: #include <stdlib.h> #include <stdio.h> #include <math.h> double pi(int n) { srand(5); int count = 0; double x, y; for (int i = 0; i < n; ++i) { x = (double) rand() / RAND_MAX; y = (double) rand() / RAND_MAX; if (x * x + y * y <= 1) ++count; } return (double) count / n * 4; } int main() { double result = pi(1e8); printf("PI = %f\n", result); } 使用 gcc 編譯: gcc -o pi pi.c 若使用比較舊的 gcc 編譯器,要加上 -std=c99 參數: gcc -std=c99 -o pi pi.c 以下我們將以這個程式為例,介紹測量程式執行時間的方法。 Linux time 指令 在 Linux 中有一個 time 指令可以直接測試程式的執行時間(CPU time): time ./pi PI = 3.142172 real 0m2.210s user 0m2.209s sys 0m0.001s time 指令的輸出分為 user time、system time 以及實際上所花費的時間。 如果在系統上同時有其他的程式也在使用 CPU 時,結果會有些差異。我先使用 stress 讓 CPU 滿載: stress --cpu

联系我们

联系电话

4000-640-466

联系邮箱

service@f-li.cn

办公地址

上海黄浦区外滩源1号

谢谢,您的信息已成功发送。
请填写信息。