前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >jvm入门3:6-8 本地方法接口+本地方法栈+堆

jvm入门3:6-8 本地方法接口+本地方法栈+堆

原创
作者头像
用户10832809
发布2025-02-24 21:30:18
发布2025-02-24 21:30:18
440
举报

06 本地方法接口

本地方法

1一个Native Method是一个java调用非java代码的接口。一个Native Method由java语言实现, 这个特征非java所特有,其他的编程语言都有这个机制,C++的extern告知c++编译器调用c的函数;2在定义一个native method时,并不提供实现体,实体体由java语言在外面实现的;3本地接口的作用是融合不同的编程语言为java所用,初衷为融合c/c++程序

表示native可以与所有其他的java标识符连用,除了abstract

使用原因:1java使用起来方便,但有些层次的任务用java实现不容易;2主要原因,java应用与java外面的环境交互,如与底层系统,操作系统或硬件。本地方法提供了一种简洁的接口,无需了解java应用之外的繁琐细节

与操作系统交互:jvm支持java语言本身和运行时库,是java程序赖以生存的平台,由一个解释器(字节码)和一些连接到本地代码的库组成。毕竟不是一个完整的系统,依赖于底层系统的支持,底层系统常常是强大的操作系统。通过使用本地方法,以用java实现了jre的底层系统的交互,jvm的一部分为c所写。

sun的解释器用c所写,能像一些普通的c一样与外部交互。jre大部分是java实现,通过一些本地方法与外界交互。如类java.lang.Thread的setPriority(),实际调用的是该类里的本地方法,setPriority0()。该本地方法由c实现,植入jvm内部。

目前该方法使用的越来越少了,除非是与硬件有关的应用,如通过java程序驱动打印机或java系统管理生产设备。企业级应用中比较少见,现在的异构领域间的通信很发达,可以使用socket通信,或web service等

07本地方法栈

java虚拟机栈用于管理java方法的调用,本地方法栈用于管理本地方法的调用;2本地方法栈线程私有;3允许被实现成固定,或者是可动态扩展的内存大小(内存溢出方面是hi相同的);4本地方法使用C语言实现;5具体做法是native method stack中登记native方法,在exexution engine执行时加载本地方法库

当某个线程调用一个本地方法时,进入一个全新的但不受虚拟机限制的世界。它和虚拟机有同样的权限;1本地方法可以通过本地方法接口访问虚拟机内部的运行时数据区;2可以直接使用本地处理器中的寄存器;3直接从本地内存的堆中分配任意数量的内存;

并不是所有的jvm都支持本地方法,java虚拟机规范并没有明确要求本地方法栈的使用语言、具体实现方式、数据结构等。jvm产品不打算支持native方法,无需实现本地方法栈

hotspot jvm中,直接将本地方法栈和虚拟机栈合二为一

08 堆

概述

1jvm实例只存在于一个堆内存中,也是java内存管理的核心区域;2堆区在jvm启动时即被创建,空间大小也就确定了;jvm管理的最大一块内存空间,堆内存大小可调;3java虚拟机规范规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的;4所有的线程共享java堆,可以划分线程私有的缓冲区,tlab thread local allocation buffer

5几乎所有的对象实例都在这里分配内存;6数组和对象永远不会存储在堆上,栈帧中保存引用,这个引用指向对象或数组在堆中的位置;7方法结束后,堆中的对象不会被马上移除,仅仅在垃圾收集的时候才会被移除;8堆,是gc执行垃圾回收的重点区域

内存部分

现代垃圾收集器大部分都基于分代收集理论设计,堆空间细分为:java8之后分为新生区+养老区+元空间,Yong/new,Old/Tenure,Meta

新生代-新生区-年轻代,老年代-老年区-养老区,永久区-永久代

堆空间结构

设置堆内存大小与OOM

java堆区用于存储java对象实例,堆的大小在jvm启动时已经设定好了,可通过-Xmx,-Xms设置,分别表示堆区的起始内存、最大内存;2一旦堆区中的内存大小超过了-Xmx所指定的最大内存时,将抛出outofmemoryerror异常;3通常将-Xms,-Xmx两个参数配置相同的值,为了能够在java垃圾回收机制清理堆区后不需要再重新分配计算堆区大小,提高性能;4默认情况,初始内存大小,物理电脑内存的64分之一,最大内存大小为4分之一。

年轻代与老年代

存储在jvm中的java对象可以被划分为两类,1一类是声明周期较短的瞬时对象,这类对象的创建和消亡都是非常迅速的;2另外一类对象的生命周期非常长,在某些极端的情况下还能够与jvm的生命周期保持一致。

jvm堆进一步细分的话,可划分为年轻代和老年代

其中年轻代又可划分为eden空间,survivior0空间,survivo1空间

下类参数一般不会调:配置新生代和老年代在堆结构中的占比,默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占堆的1/3;

可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5

年轻代与老年代

1在hotspot在,eden空间和另外两个survivor空间缺省所占的比例是8:1:1;2开发人员可通过选项-XX:SurvivorRatio调整这个空间比例。3几乎所有java对象都是在eden区被new出来的;4绝大部分的java对象的销毁都在新生代进行;5可以使用选项-Xmn设置新生代最大内存大小

对象分配过程

为新对象分配内存是一个非常严谨和复杂的任务,jvm的设计者不仅需要考虑内存如何分配,在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,还需要考虑GC执行完内存回收后是否会在内存空间中产生碎片

1new的对象先放在伊甸园区,此区有大小限制;2当伊甸园的空间填满时,程序又需要创建对象,jvm的垃圾回收器将堆伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象销毁。再加载新的对象放到伊甸园区;3然后将伊甸园中剩余对象移动到幸存者0区;4如果再次出发垃圾回收,此时上次幸存下来的放到幸存者0区,如果没有回收,就会放到幸存者1区;5如果再次经历垃圾回收,此时重新放回幸存者0区,接着再去幸存者1区;6啥时候去养老区?默认设置次数为15次,。-XX:MaxTenuringThreshold=<N>

7当养老区内存不足时,再次出发GC,major gc,进行养老区内存清理;8养老区执行了major gc后发现仍然无法进行对象的保存,就会产生oom异常

总结:针对幸存者0-1,复制之后有交换,谁空谁是to(始终保持一个幸存者区为空)

常用调优工具:jdk命令行、jconsole、visualvm、jprofiler、java flight recorder、gcviewer、gc easy

minor GC, major GC, full GC

jvm在进行GC时,并非每次都对上面三个内存(新生代、老年代、方法区)区域进行回收,大部分的回收都是指新生代;2针对hotspot vm的实现,它里面的GC按照回收区域分为两大种类型,一种是部分收集partial gc,一种是整堆收集 full gc

部分收集:不是完整收集整个java堆的垃圾收集,分为新生代收集minor gc/yong gc、老年代收集 major gc/old gc;目前只有cmc gc会有单独收集老年代的行为;很多时候major gc和full gc混淆使用,具体分辨是老年代回收还是整堆回收

混合收集:mixed gc,收集整个新生代以及部分老年代的垃圾收集,目前只有g1 gc会有这种行为

整堆收集 full gc:收集整个java堆和方法区的垃圾收集

最简单的分代式gc的触发条件

年轻代gc触发机制:1当年轻代空间不足时,就会触发minor gc,这里的年轻代满指的是eden代满,survivor满不会引发gc,每次minor gc会清理年轻代的内存;2java对象大多具备朝生夕死的特性,minor gc非常频繁,回收速度也特别快;3minor gc会触发stw,暂停其他用户进程,等垃圾回收结束,用户线程才恢复运行

老年代gc触发机制:1指发生在老年代的gc,对象从老年代消失时,major gc或full gc发生了;2出现major gc,经常会伴随至少一次的minor gc(非绝对,在parallel scanvenge 收集器的收集策略里就有直接进行major gc);3major gc的速度一般会比minor gc慢10倍以上,stm的时间更长;4major gc后,内存还不足,就报oom;5major gc速度一般会比minor gc慢10倍以上

full gc触发机制:1调用System.gc(),系统建议执行full gc,但不必然执行;2老年代空间不足;3方法区空间不足;4通过minor gc后进入老年代的平均大小老年代的可用内存;5由eden区、suvivor spac0区向suvivor space1区复制时,对象大小大于to space可用内存,则把该对象转存到老年代,老年代的可用内存小于该对象大小

full gc是开发或调优中尽量要避免的,这样暂时时间会短一些

堆空间分代思想

为什么要把java堆分代,不分代不能正常工作吗?

--不同对象的生命周期不同,70%-99%的对象是临时对象;1新生代,有eden、两块大小相同的survivor构成,to总为空;2老年代,存放新生代中经历多次gc仍然存活的对象;3不分代可以,分代的理由是优化性能,没有分代,gc时需要对所有区域扫描,分代会优先扫描朝生夕死对象,可以快速腾出大片空间

内存分配策略

如果对象在eden出生并经过一次minor gc后仍然存活,并且能被survivor容纳的话,将被移动到survivor空间中,并将对象年龄设为1.对象在survivor区每熬过一次minor gc,年龄就增加1岁,当他年龄增加到一定程度,默认15岁,就会被晋升到老年代中。对象晋升老年代的阈值可通过 -xx:MaxTenuringThreshold设置

针对不同年龄段对象分配原则:1优先分配到eden;2大对象直接分配到老年代,尽量避免程序中过多的大对象;3长期存活的对象分配到老年代;4动态对象年龄判断,如果survivor区中相同年龄所有对象大小的综合大于survivor空间的一半,年龄大于或等于该对象可以直接进入老年代,无需等到15;5空间分配担保,-XX:HandlePromotionFailture

为对象分配内存 TLAB thread local allocation buffer 线程本地分配缓冲区

为什么会有TLAB

1堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据;2由于对象实例的创建在jvm中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的;3为避免多个线程操作同一地址,需要使用加锁机制,进而影响分配速度

什么是TLAB

1从内存分配模型而不是垃圾收集的角度,对eden区域继续进行划分,jvm为每个线程分配了一个私有缓存区域,它包含在eden空间内;2多线程同时分配时,使用tlab可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此可以将这种内存分配方式称为快速分配策略;3openjdk衍生出来的jvm都提供了tlab设计

TLAB与工作内存

相同:TLAB 和工作内存都与线程相关,它们存储的数据都只对当前线程可见,为每个线程提供了独立的存储区域,避免了多线程访问共享数据时的竞争和冲突,在一定程度上提高了并发性能。为线程提供了相对独立的、更高效的内存操作空间。

不同:TLAB是 Java 堆内存中的一个特定区域,是一种线程私有的内存分配机制,主要用于对象的快速分配。为了提高对象分配的效率,减少多线程在堆内存中分配对象时的锁竞争,为每个线程预先分配一小块连续的内存空间来实现的;工作内存是 Java 虚拟机(JVM)定义的一个抽象概念,主要用于缓存主内存中的变量,以提高线程对变量的访问速度。并不对应着实际的物理内存区域。

补充说明

1不是所有的对象实例都能够在tlab中成功分配内存,但jvm确实是将tlab作为内存分配的首选;2在程序中,开发人员可以通过选项,-XX:UserTLAB,设置是否开启TLAB空间;3默认情况下,TLAB空间的内存非常小,仅占整个Eden空间的1%,可以通过选项-XX:TLABWastTargetPersent设置这个占比;4一旦对象在tlab空间分配内存失败,jvm就会尝试通过使用加锁机制确保数据操作的原子性,从而直接在eden空间中分配内存

堆空间参数设置

-XX:+PrintFlagesInitial:查看所有的参数的默认初始值

-XX:+PrintFlagesFinal:查看所有参数的最终值(可能会修改,不再是初始值)

-Xms:初始堆空间内存(默认物理内存1/64)

-Xmx:最大堆空间内存(默认1/4 )

-Xmn:设置新生代大小(初始值及最大值)

-XX:NewRatio:配置新生代与老年代在堆结构的占比

-XX:SurivivorRatio:设置新生代中Eden和S0/S1空间的比例

-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄

-XX:+PrintGCDetails:输出详细的GC处理日志,打印gc简要信息,-XX:+PrintGC -verbose:gc

-XX:HandlePromitionFailure:是否设置空间分配担保

在发生Minior gc之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。如果大于,则此次minor gc是安全的,如果小于则会查看hadlePromotionFailture是否允许担保失败;但在jdk6 update24之后,规则变为只要老年代的连续空间大于新生代对象总大小,或者历次晋升的平均大小就会进行Minor gc,否则进行full gc

堆是分配对象存储的唯一选择吗?

随着jit编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上变得不再绝对。如果经过逃逸分析 escape annalysis后发现,一个对象并没有逃逸出方法的话,那么久可能被优化成栈上分配。taobao jvm将生命周期较长的java对象移至heap外

逃逸分析并不成熟

本章小结

年轻代是对象的诞生、成长、消亡的区域,一个对象在这里产生、应用、最后被垃圾回收期收集,结束生命

老年代放置长生命周期的对象,通过都是从survivor区域筛选拷贝过去的,特殊情况,如果对象太大,tlab、eden、都放不下,直接分配到老年代

当gc只发生在年轻代时,回收年轻代对象的行为被称为minorgc,当gc发生在老年代时被称为major gc或者full gc。一般minor gc的发生频率比major gc高很多,老年代gc 频率远低于年轻代

参考资料

康师傅jvm

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 06 本地方法接口
  • 07本地方法栈
  • 08 堆
    • 年轻代与老年代
    • 为对象分配内存 TLAB thread local allocation buffer 线程本地分配缓冲区
    • 本章小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档