unsafe分配的jvm 内存分配是jvm的堆外jvm 内存分配吗

用户名:Me
文章数:15
访问量:1678
注册日期:
阅读量:5863
阅读量:12276
阅读量:384701
阅读量:1075966
51CTO推荐博文
&&&&直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁的使用,而且也可能导致OutOfMemoryError异常出现,&&&&在JDK1.4中新加入了NIO类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。&&&&显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存的大小及处理器寻址空间的限制。服务器管理员配置虚拟机参数时,一般会根据实际内存设置-Xmx等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常。欲知后事如何,且听下回分解本文出自 “” 博客,转载请与作者联系!
了这篇文章
类别:┆阅读(0)┆评论(0)摘要: 使用Java语言的同学们都知道, Java的虚拟机对内存的管理大部分情况下就是指堆内存的管理, GC的也是对堆内存的清理和回收. 下面就看一下堆外内存的对JVM的意义.
使用Java语言的同学们都知道, Java的虚拟机对内存的管理大部分情况下就是指堆内存的管理, GC的也是对堆内存的清理和回收. 下面就看一下堆外内存的对JVM的意义.
第一次了解到堆外内存的使用场景是在使用netty, netty中提到的一个概念, &零拷贝&, 也是netty高性能的原因之一. 零拷贝, 主要体现在三个方面:
Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外(直接)内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。
在使用堆外内存的同时也带来了新的问题, 相比较堆内存, 堆外内存的分配和回收要更耗时, 所以netty提供了基于内存池的缓冲区重用机制.
将本地缓存和堆外内存联系到一起, 是有一次调试线上频繁FULL GC然后OOM的问题, 当时的情况是, 线上频繁的报警有FULLGC, 怀疑有内存泄露,, 通过dump堆内存快照, 分析后发现有一个特别大的HashMap, 原因是打点日志引起的, 应用默认集成了日志系统会自动记录用户的所有行为, 应用这边合并日志后发送到日志接收端, 日志接收端挂掉了, 导致一直在重试, 重试的过程中不停的有新的日志加进来, 最后导致FULLGC. 当时我就在想,&如果将这种本地缓存移到堆外是不是就可以不用参与GC,
也可以使用更大的内存.
堆外内存有以下特点:
对于大内存有良好的伸缩性, 堆外内存突破JVM的内存限制对垃圾回收停顿的改善可以明显感觉到在进程间可以共享,减少虚拟机间的复制
堆外内存更适合生命周期中等或长期的对象
关于堆外内存的回收
堆外内存的回收其实依赖于我们的GC机制(堆外内存不会对GC造成什么影响)
首先我们要知道在java层面和我们在堆外分配的这块内存关联的只有与之关联的DirectByteBuffer对象了,它记录了这块内存的基地址以及大小,那么既然和GC也有关,那就是GC能通过操作DirectByteBuffer对象来间接操作对应的堆外内存了。
DirectByteBuffer对象在创建的时候关联了一个PhantomReference,说到PhantomReference它其实主要是用来跟踪对象何时被回收的,它不能影响GC决策.
GC过程中如果发现某个对象除了只有PhantomReference引用它之外,并没有其他的地方引用它了,那将会把这个引用放到java.lang.ref.Reference.pending队列里,在GC完毕的时候通知ReferenceHandler这个守护线程去执行一些后置处理, 而DirectByteBuffer关联的PhantomReference是PhantomReference的一个子类,在最终的处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的堆外内存块
什么是基地址 这里就提出了段的概念, 将1G的数据划分为n个段, 每一个段是64K, 每一个段也就是每一个64K就是一个基地址 段内的数据的地址就是当前基地址的偏移地址, 此时 段地址+偏移地址就能够找到真正的内存数据了.
为什么要主动调用System.gc
System.gc()会对新生代的老生代都会进行内存回收,这样会比较彻底地回收DirectByteBuffer对象以及他们关联的堆外内存.
DirectByteBuffer对象本身其实是很小的,但是它后面可能关联了一个非常大的堆外内存,因此我们通常称之为*冰山对象.
我们做ygc的时候会将新生代里的不可达的DirectByteBuffer对象及其堆外内存回收了,但是无法对old里的DirectByteBuffer对象及其堆外内存进行回收,这也是我们通常碰到的最大的问题.
如果有大量的DirectByteBuffer对象移到了old,但是又一直没有做cms gc或者full gc,而只进行ygc,那么我们的物理内存可能被慢慢耗光,但是我们还不知道发生了什么,因为heap明明剩余的内存还很多(前提是我们禁用了System.gc -- JVM参数DisableExplicitGC)。
JVM GC-Invisible Heap
淘宝基于OpenJDK HotSpot VM,定制且开源的服务器版Java虚拟机. GC-Invisible Heap,简称GCIH,是一种将Java对象从Java堆内移动到堆外,并且可以在JVM间共享这些对象的技术。
没有序列化和反序列化GC性能更高和直接操作JVM对象完全一样
如何从JVM移入堆外内存
遍历所有对象, 从根对象开始, 根据对象的引用关系递归调用moveIn(将该对象在GCIH的地址encode到刻对象的header上, 并设置header的最低2位为01).
如何阻止GC扫描
正常的GC过程是, 从root对象出发, 标记活的对象的同时, 将这个对象push到queue, 以便后续的GC线程处理 GCIH更改了GC过程, 当某个对象的地址在GCIH地址空间内时, 直接返回, 不将对象放到queue中
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1163次
排名:千里之外计算机内存,它算是CPU与计算机打交道最频繁的区域,所有数据都是先经过硬盘至内存,然后由CPU再从内存中获取数据进行处理,又将数据保存到内存,通过分页或分片技术将内存中的数据再flush至硬盘。那JVM的内存结构到底是如何呢?JVM做为一个运行在操作系统上,但又独立于os运行的平台,它的内存至少应该包括象寄存器、堆栈等区域。
JVM在运行时将数据划分为了6个区域来存储,而不仅仅是大家熟知的Heap区域。
一、PC寄存器:一块很小的内存区域,主要作用是记录当前线程所执行的字节码的行号。字节码解释器工作时就是通过改变当前线程的程序计数器选取下一条字节码指令来工作的。任何分支,循环,方法调用,判断,异常处理,线程等待以及恢复线程,递归等等都是通过这个计数器来完成的。
  由于Java多线程是通过交替线程轮流切换并分配处理器时间的方式来实现的,在任何一个确定的时间里,在处理器的一个内核只会执行一条线程中的指令。因此为了线程等待结束需要恢复到正确的位置执行,每条线程都会有一个独立的程序计数器来记录当前指令的行号。计数器之间相互独立互不影响,我们称这块内存为“线程私有”的内存。
在任意时刻,一条JVM线程只会执行一个方法的代码。该方法称为该线程的当前方法(Current Method)。如果方法是java方法,那PC寄存器保存JVM正在执行的字节码指令的地址。如果方法为native的,则PC寄存器值是undefined。
二、JVM栈:线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址,因此Java中基本类型的变量是值传递,而非基本类型的变量是引用传递,Sun JDK的实现中JVM栈的空间是在物理内存上分配的,而不是从堆上分配。
  由于JVM栈是线程私有的,因此其在内存分配上非常高效,并且当线程运行完毕后,这些内存也就被自动回收。
三、Heap:JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中对象的内存需要等待GC进行回收,Heap在32位的操作系统上最大为2G,在64位的操作系统上则没有限制。
大小通过-Xms和-Xmx来控制,-Xms为JVM启动时申请的最小Heap内存(默认为物理内存的1/64但小于1G),-Xmx为JVM可申请的最大Heap内存(默认为物理内存的1/4)。
默认当空余堆内存小于40%时,JVM会增大Heap的大小到-Xmx指定的大小,可通过-XX:MinHeapFreeRatio=来指定这个比例。
默认当空余堆内存大于70%时,JVM会将Heap的大小往-Xms指定的大小调整,可通过-XX:MaxHeapFreeRatio=来指定这个比例。
但对于运行系统而言,为了避免频繁的Heap Size的调整,通常都会将-Xms和-Xmx的值设成一样,因此这两个用于调整比例的参数通常是没用的。
四、Method Area:存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息、
运行时常量池(Runtime Constant Pool),当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,可见方法区域的重要性。同样,方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代,默认为64M,可通过-XX:PermSize以及-XX:MaxPermSize来指定其大小。
五、Native Method Stack:Java虚拟机可能会使用到传统的栈来支持native方法(使用Java语言以外的其它语言编写的方法)的执行,这个栈就是本地方法栈(Native Method Stack)。JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。
相关 [jvm 内存] 推荐:
- 移动开发 - ITeye博客
计算机内存,它算是CPU与计算机打交道最频繁的区域,所有数据都是先经过硬盘至内存,然后由CPU再从内存中获取数据进行处理,又将数据保存到内存,通过分页或分片技术将内存中的数据再flush至硬盘. 那JVM的内存结构到底是如何呢. JVM做为一个运行在操作系统上,但又独立于os运行的平台,它的内存至少应该包括象寄存器、堆栈等区域.
- ITeye博客
虚拟机的大小比较小,在对大数据进行处理时
就会报错:
java.lang.OutOfMemoryError. 设置
内存的方法,对于单独的
,可以用下面的方法对
内存进行设置. -Xms
是设置内存初始化的大小. -Xmx
是设置最大能够使用内存的大小(最好不要超过物理内存大小).
- ITeye博客
jdk自带的jmap就是java内存映像工具,可以用于上生成堆转储快照:. 在eclipse中启动一个java类,打开jdk安装目录下的C:\Program Files\Java\jdk1.6.0_11\bin目录,双击jconsole.exe,显示连接窗口:.
,单击pid为6920的选项,点连接进入,可以看到jvm运行时的多种参数,.
- 开源软件 - ITeye博客
我们使用的是 Eclipse Memory Analyzer V0.8,Sun JDK 6. 和其他插件的安装非常类似,MAT 支持两种安装方式,一种是“单机版“的,也就是说用户不必安装 Eclipse IDE 环境,MAT 作为一个独立的 Eclipse RCP 应用运行;另一种是”集成版“的,也就是说 MAT 也可以作为 Eclipse IDE 的一部分,和现有的开发平台集成.
- ITeye博客
JVM的相关知识是学习java高级特性必须要去深入学习的. 平时也有一些学习和实践,不过总结比较少. 今天有时间总结一下最基础的内存模型和GC策略的知识,在此记录一下. hotspot jvm内存模型. hotspot的内存模型很多地方都有类似总结,我也简单总结了一下,大概可以用下图表示:. 1.线程栈:线程创建是会为每个线程创建一个线程栈,线程栈里面会为每个方法调用创建一个栈帧.
- CSDN博客互联网推荐文章
GC-Invisible Heap. GC-Invisible Heap,简称GCIH,是一种将Java对象从Java堆内移动到堆外,并且可以在JVM间共享这些对象的技术. GCIH顾名思义就是GC访问不到的堆,它是对JVM内存管理机制的一个有益的补充. 在某些特殊的应用中有大量生命周期很长的对象,在应用运行的整个过程中它们都存在,不需要被GC回收.
- CSDN博客互联网推荐文章
I.JVM进程的生命周期. JVM实例的生命周期和java程序的生命周期保持一致,即一个新的程序启动则产生一个新的JVM进程实例,程序结束则JVM进程实例伴随着消失. 那么程序启动和程序终止就是JVM实例生命周期的两个边界,两个边界点可以这么理解:一个拥有程序入口(main函数)的class在执行main方法时,相应的JVM就被创建了(即JVM生命周期的起点),当由此main函数启动的所有非守护线程都终止时,JVM即退出(JVM实例生命周期的终点).
- 互联网 - ITeye博客
有时候web应用经常会发生FGC,我们想知道FGC把那些对象给回收了,思路很简单就是看看FGC之前内存中有那些实例,FGC之后内存中又有那些实例,通过前后的比较,我们就能很容易知道FGC回收了那些实例,当然我们可以手工去dump内存,在FGC发生之前dump一下内存,再在FGC发生之后dump一下内存,但是这dump的时间点不好把握,能否让JVM自动去dump就更好了.
- Java译站
这篇文章是关于最近一次性能调优的经历. 跟往常一样,开始的时候总会有一些模糊的征兆. 这次的现象看起来是”应用程序运行缓慢,但是我们无法获取到对应的源代码. 仔细观察下这个应用会发现它运行着一些批量任务. 分析下性能相关的指标会发现它在运行某个特定任务的时候花费的时间太长了. 进一步分析我得出了一个可量化的优化目标.
- Java - 编程语言 - ITeye博客
理想的情况下,一个Java程序使用JVM的默认设置也可以运行得很好,所以一般来说,没有必要设置任何JVM参数. 然而,由于一些性能问题(很不幸的是,这些问题经常出现),一些相关的JVM参数知识会是我们工作中得好伙伴. 在这篇文章中,我们将介绍一些关于JVM内存管理的参数. 知道并理解这些参数,将对开发者和运维人员很有帮助.
坚持分享优质有趣的原创文章,并保留作者信息和版权声明,任何问题请联系:@。

我要回帖

更多关于 jvm的内存分配 的文章

 

随机推荐