01-自动内存管理机制

news/2024/7/7 22:48:07

一、走进java

 每个文件都有自己的格式,java也不例外,而JVM通常是通过16进制对应的java汇编码来解释的,

magic(魔数)

每个Java class 文件的前4个字节被称为他的魔数(magic number):0x CAFEBABE (图四的前四个字节)。魔数的作用在于,可以轻松的分辨出java class文件和非java class 文件。如果一个文件不是以0xCAFEBABE 开头的,那他肯定不是java class文件。

minnor_version 和 major_version

class文件的下面4个字节包含了主、次版本号。随着Java 技术的发展,java class 文件格式可能会加入新特性。class 文件格式一旦发生变化,版本号也会随之变化。对于java 虚拟机来说,版本号确定了特定的class文件格式,通常只有给定主版本号和一系列次版本号后,java虚拟机才能够读取class文件。如果class文件的版本号超出了java虚拟机所能处理的有效范围,java虚拟机不会处理该class文件。jdk 1.8 的版本号是 52.

二、java内存区域与内存溢出异常

      1. 运行时数据区域

         

       1.1 程序计数器

        程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器.

        由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

       1.2 虚拟机栈

       与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

       1.3 本地方法栈

 本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

       1.4 java堆

        对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。

       1.5.方法区 

   方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

       1.6 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

       1.7 直接内存

      在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据

      2.HotSpot虚拟机对象探秘

         2.1 对象的创建

            在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定(如何确定将在2.3.2节中介绍),为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump the Pointer)。如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)。选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。

除如何划分可用空间之外,还有另外一个需要考虑的问题是对象创建在虚拟机中是非常频繁的行为,即使是仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案

         2.2 对象的内存布局

       在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

  

         2.3 对象的访问定位

建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而定的。目前主流的访问方式有使用句柄和直接指针两种。

三、垃圾收集与内存管理策略

        3.1 概述

        3.2 对象已死吗

             3.2.1 引用计数法

              给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

              缺陷:相互引用,用户不会被回收

             3.3.2 可达性分析法

             这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

在Java语言中,可作为GC Roots的对象包括下面几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。

扩充:引用

   强引用

   软引用

   弱引用

   虚引用

finalize() 方法

        3.3 垃圾收集算法

              3.3.1 标记-清除法

它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

             

              3.3.2 复制算法

它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。

   

              3.3.3 标记整理算法

        复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

              3.3.4 分代收集算法

在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。

         3.4 垃圾收集器

            

      3.4.1 Serial收集器

进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

      3.4.2 ParNew收集器

ParNew收集器除了多线程收集之外,其他与Serial收集器相比并没有太多创新之处,但它却是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作。

      3.4.3 Parallel Scavenge收集器

Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 +垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

      3.4.4 Serial Old收集器

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。

如果在Server模式下,那么它主要还有两大用途:一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使,另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

      3.4.5 Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

直到Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。

      3.4.6 CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
从名字(包含“Mark Sweep”)上就可以看出,CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:
初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
重新标记(CMS remark)
并发清除(CMS concurrent sweep)

其中,初始标记、重新标记这两个步骤仍然需要“Stop The World”。

      3.4.7 G1收集器

      3.4.8 理解GC日志

  3.5 内存分配与回收策略

      3.5.1 对象优先在Eden分配

      3.5.2 大对象直接进入老年代

所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组(笔者列出的例子中的byte[]数组就是典型的大对象)。大对象对虚拟机的内存分配来说就是一个坏消息(替Java虚拟机抱怨一句,比遇到一个大对象更加坏的消息就是遇到一群“朝生夕灭”的“短命大对象”,写程序的时候应当避免),经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。
虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。

      3.5.3 长期存活的对象将进入老年代

既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。
读者可以试试分别以-XX:MaxTenuringThreshold=1

      3.5.4 动态对象年龄判定

为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

      3.5.5 空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC

四、现有项目启动参数与gc日志分析

1启动参数说明

参数

说明

-noverify

关闭 Java 字节码的校验功能

-server

服务器模式

-Xmx2G

堆内存的初始大小,默认为物理内存的1/64

-Xms2G

堆内存的最大大小,默认为物理内存的1/4

-Xmn512m

堆内新生代的大小。通过这个值也可以得到老生代的大小:-Xmx减去-Xmn

-Xss256K

设置每个线程可使用的内存大小,即栈的大小

-XX:+ExplicitGCInvokesConcurrent

指定System.gc()采用 CMS 算法,FGC时停机时间会变短,可是CMS GC次数不会变。

-XX:+UseCMSInitiatingOccupancyOnly

配合上面参数使用,只有 old 区,占比条件知足状况下,才触发CMS GC

-XX:+UseConcMarkSweepGC

CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器,
如果老年代使用CMS垃圾回收器,需要添加虚拟机参数-"XX:+UseConcMarkSweepGC"。

-XX:+UseParNewGC

新生代使用并行垃圾收集器

-XX:+CMSParallelRemarkEnabled

开启并行remark

-XX:+UseCMSCompactAtFullCollection

CMS是不会整理堆碎片的,因此为了防止堆碎片引起full gc,通过会开启CMS阶段进行合并碎片选项

-XX:CMSFullGCsBeforeCompaction=0

-XX:CMSFullGCsBeforeCompaction作用:设置在执行多少次Full GC后对内存空间进行压缩整理。

因为内存压缩整理的过程是没法并发执行的,所以难免要停顿,要尽量的减少这个停顿时间。根据具体情况,如果Full GC比较频繁,只调优这个参数的情况下,那么就不能每次都整理内存空间,不然积少成多,停顿的时间也是很可观的,此时就要调大该参数,让CMS在经过多次Full GC后再对内存空间进行压缩整理。

而如果Full GC发生的不频繁,间隔时间较长,就可以设置成每次Full GC后都会对内存空间进行压缩整理,影响也不大。

-XX:+CMSClassUnloadingEnabled

对永久代进行垃圾回收,Hotspot虚拟机中,永久代即方法区

-XX:LargePageSizeInBytes=8M

设置用于Java堆的大页面大小;它以 GB/MB/KB 为单位接受参数;通过更大的页面大小,我们可以更好地利用虚拟内存硬件资源;然而,这可能会导致PermGen 的空间更大,这反过来又会强制减少 Java 堆空间的大小

-XX:+UseFastAccessorMethods

get,set 方法转成本地代码(对于jvm来说是冗余代码,jvm将进行优化)

-XX:CMSInitiatingOccupancyFraction=70

当老年代使用率达到70%是,CMS触发

-XX:SoftRefLRUPolicyMSPerMB=0

这个参数是指设置软可访问对象在上次被引用后在堆上保持活动的时间量(以毫秒为单位)

那如果将这个参数数值调整为0,让所有的软引用对象都尽快的被释放,是不是可以提高内存的利用率

-XX:+PrintClassHistogram

–按下Ctrl+Break后,打印类的信息

-XX:+PrintGCDetails

打印GC日志

-XX:+PrintGCDateStamps

打印GC日志

-XX:+PrintGCTimeStamps

打印GC日志

-XX:+PrintHeapAtGC

每次一次GC后,都打印堆信息

-Xloggc:$APP_HOME/logs/gc_$CURRENT_DATE.log

生成日志

-XX:+HeapDumpOnOutOfMemoryError

参数表示当JVM发生OOM时,自动生成DUMP文件。

-XX:HeapDumpPath=$APP_HOME/temp/

dump位置

-XX:OnOutOfMemoryError=$APP_HOME/restart.sh

oom 重启

-Dcom.sun.management.jmxremote.port=26666

远程监控端口

-Dcom.sun.management.jmxremote.ssl=false

ssl false

-Dcom.sun.management.jmxremote.authenticate=false

不需要认证

2.gc日志说明

       

日志内容

说明:

1.如果是ParNew收集器,新生代名称就会变为“[ParNew”,意为“Parallel New Generation”。如果采用Parallel Scavenge收集器,那它配套的新生代称为“PSYoungGen”,老年代和永久代同理,名称也是由收集器决定的

2.其他说明

后面方括号内部的“3324K->152K(3712K)”含义是“GC前该内存区域已使用容量-> GC后该内存区域已使用容量 (该内存区域总容量)”。而在方括号之外的“3324K->152K(11904K)”表示“GC前Java堆已使用容量→GC后Java堆已使用容量(Java堆总容量)”。

五、下节预告

       

JDK的命令行工具

类文件结构

类加载


http://lihuaxi.xjx100.cn/news/1625144.html

相关文章

C++前缀和算法应用:矩形区域不超过 K 的最大数值和

基础知识点 C算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 题目 给你一个 m x n 的矩阵 matrix 和一个整数 k ,找出并返回矩阵内部矩形区域的不超过 k 的最大数值和。 题目数据保证总会存在一个数值和不超过 k 的矩形区域。 示例 1&#x…

将 vue2+ElementU 项目打包成安卓app

目标:将vue项目打包成安卓app 工具:HbuilderX 1.在HbuilderX中创建一个 5App 项目 创建好的app项目目录 2.将vue项目打包 2.1 在 vue.config.js 中添加公共路径(解决打包后的app图片不显示问题) module.exports defineConfig(…

在自己的摄像头上测试ORB_SLAM3

文章目录 硬件相机标定IMU标定依赖编译可能遇到的问题 硬件 x86电脑realsense d435i相机 相机标定 IMU标定 依赖 Ceres # CMake sudo apt-get install cmake # google-glog gflags sudo apt-get install libgoogle-glog-dev libgflags-dev # BLAS & LAPACK sudo apt…

nginx的正向代理,反向代理和负载均衡

nginx当中有两种代理方式以及含义: 1.七层代理 (http协议) 核心:代理的是http的请求和响应 客户端请求代理服务器:由代理服务器转发客户端的httpd请求,转发到内部的服务器(可以是单台和可以是一…

助力森林火情预警检测,基于YOLOv7-tiny、YOLOv7和YOLOv7x开发构建无人机航拍场景下的森林火情检测是别预警系统

火情的预警与检测识别对于保障林业安全,减少人员伤亡有着重要的意义,科学有效地早发现早扑灭是最有效的干预手段,本文的主要是想就是想要建立基于无人机航拍场景下的森林火情检测预警系统,整体效果如下所示: 这里文中选…

港联证券:从AI到华为产业链 主流基金为何屡屡错过新科技

干流基金“踏空”科技股 本年以来,较少有干流基金司理拥抱从AI到华为工业链的科技股,除非基金产品合同“强制”买进。 Wind数据显示,截至目前,本年以来基金产品收益率最高的已超50%,年内收益超15%的基金产品也有数十…

dubbo-admin 2.5.3源码编译

dubbo-admin 2.5.3 属于10年前的产品,当时官方阿里采用1.7版本的jdk进行项目编译,目前在项目中至少使用1.8版本的jdk,导致dubbo-admin 2.5.3在项目中需单独配置一套1.7版本的jdk。对着信创产业的推荐,在arm机器上无法查询到1.7版本…

samba服务器的功能是什么

Samba服务器是一个开源的网络文件共享服务,其主要功能是在不同操作系统之间实现文件和打印机共享。它最常用于将Linux/Unix系统与Windows系统互联,但也支持其他操作系统。 以下是Samba服务器的主要功能: 文件共享:Samba允许用户在…