JVM 概述(体系结构、栈、堆、GC 三大算法)、JVM 参数(XX 参数介绍、调参、查看参数方法)

一、JVM 概述

熟悉JVM架构GC垃圾回收机制以及相应的堆参调优,有过在linux进行系统调优的经验

JVM是运行在操作系统之上的,他与硬件没有直接的交互。

二、JVM体系结构概览

 

运行数据区包括方法区、堆、本地方法栈、java 栈、程序计数器这五个部分。其中,(1)方法区用于保存类的元结构信息(保存所有定义的方法的信息,以及类中定义的静态变量、常量、运行时常量池等)、(2)堆内存中存放实例变量,(3)本地方法栈用于处理标记为 native 的代码,(4)程序计数器作为一个指针(指向方法区中的方法字节码,用于指示下一个要执行的指令代码是什么,告诉执行引擎下一条要执行的指令是什么),(5)栈内存是各线程私有的,生命周期为:在线程创建时创建,线程结束时释放。基本类型的变量、引用类型变量、实例方法都是在栈内存中存放的。

垃圾回收:主要针对方法区和堆内存(有些垃圾回收器不回收方法区内存),两者存在 OOM问题(OutOfMemoryError)

java 栈和本地方法栈不存在垃圾回收,但是存在 StackOverFlow(栈内存溢出问题)

  1. Class Loader类加载器

    负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,值与他是否可以允许,则由Execution Engine决定

  2. Execution Engine执行引擎 负责解释命令,提交操作系统执行

  3. Native Interface 本地接口

    Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。

  4. Native Method Stack 本地方法栈

    java在内存中专门开辟了一块区域处理标记为native的代码,他的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。

  5. Runtime Data Area 运行数据区

  6. Method Area方法区

    方法区是被所有线程共享,所有字段和方法字节码、以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,==此区属于共享区间==。用来保存装载的类的元结构信息。

    ==静态变量+常量+类信息+运行时常量池存放在方法区==

    ==实例变量存在堆内存中==

  7. PC Register 程序计数器

    每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),有执行引擎读取下一条指令,是一个非常小的内存空间,可以忽略不记

    ==栈管运行,堆管存储==

  8. Java Stack 栈

    栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。基本类型的变量、实例方法、引用类型变量都是在函数的栈内存中分配

栈管运行,堆管存储

三、栈(Stak)

栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。基本类型的变量、实例方法、引用类型变量都是在函数的栈内存中分配

3.1 栈存储什么

先进后出,后进先出即为栈

栈帧中主要保存3类数据

  • 本地变量(Local Variables):输入参数和输出参数以及方法内的变量;
  • 栈操作(Operand Stack):记录出栈、入栈的操作;
  • 栈帧数据(Frame Data):包括类文件、方法等。

3.2 栈运行原理

栈中的数据以栈帧的格式存在,每执行一个方法就创建一个栈帧,将该栈帧压入栈的顶部(栈遵循后入先出,先入后出的原则),当方法执行结束之后就将方法从栈的顶部弹出

栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存去块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,

当一个方法A被调用时就产生一个栈帧F1,并被压入到栈中,

A方法调用了B方法,于是产生栈帧F2也被压入到栈,

B方法调用了C方法,于是产生栈帧F3也被压入到栈。。。

执行完毕后,先弹出F3,再弹出F2,再弹出F1.。。。

遵循“先进后出/后进先出”的原则。

图示在一个栈中有两个栈:

栈2是最先被调用的方法,先入栈,

然后方法2调用了方法1,栈帧1处于栈顶的位置,

栈帧2处于栈底,执行完毕后,依次弹出栈帧1和栈帧2,

线程结束,栈释放。

3.3 判断JVM优化是哪里

主要是优化堆(对堆空间进行优化,如对 InitialHeapSize 和 MaxHeapSize 等参数进行调整,当执行 java -XX:PrintCommandLineFlags 时会打印出 JVM 调参常用参数,其中就包含和堆内存大小相关的参数)

3.4 三种JVM

  1. Sun公司的HotSpot
  2. BEA公司的JRockit
  3. IBM公司的 J9 VM

四、堆(Heap)

新生区分为伊甸区和幸存区,所有的类在被创建出来时位于伊甸区,当伊甸区的空间耗尽时,程序又需要创建新对象的话,JVM 的垃圾回收机制就会对伊甸区进行垃圾回收,其中不再被任何 GC Roots 所引用的对象作为垃圾被销毁,而幸存的其他对象则被移到幸存0区;当0区也满了的时候,则再对0区进行垃圾回收,幸存对象移到幸存1区;进而0区也满了的时候,再移动到养老区。最终如果养老区也满了,对养老区执行完垃圾回收后发现仍然无法创建新的对象,这时候就会产生 OOM 异常。

产生 OOM 异常的原因有两点:(1)java 虚拟机对堆内存的设置太小,可以对-Xms 和-Xmx 参数进行设置,从而调整堆初始内存大小(默认为物理内存的16分之1)、和堆最大内存大小(默认为物理内存的64分之1)  (2)代码中创建了过多的大对象,并且由于这些对象存在被引用,长时间无法被垃圾回收器清理

永久存储区(实际上为方法区,永久区逻辑上属于堆内存空间)存储 JDK 自带的类、接口的元数据,即存储运行环境所必须的类信息,只有在关闭 JVM 时才会释放此区域所占用的内存。如果出现永久区内存 OOM 的话,一般原因是JVM 要加载过多的第三方 jar 包。

4.1 堆内存示意图

堆内存示意图

4.2 新生区

新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生去又分为两部分:伊甸区(Eden Space)和幸存者区(Survivor Space),所有的类都是再伊甸区被new出来。幸存区有两个:0区和1区。当伊甸园的空间用完是,程序有需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园区中的生于对象移动到幸存0区,若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。如果1区也满了,再移动到养老区。若养老区也满了,那么这时候将产生MajorGC(FullGC),进行养老区的内存清理。若养老区执行了FullGC后发现依然无法进行对象保存,就会产生OOM异常(OutOfMemoryError)。

  • 如果出现java.lang.OutOfMemoryError:Java heap space异常,说明java虚拟机的堆内存不够。原因有二:

    1. Java虚拟机的对内存设置不够,可以通过参数-Xms、-Xmx来调整

      默认最大内存是机器的四分之一大小

    2. 代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)

==JDK1.8之后,永久代取消了,由元空间取代==

4.3 养老区

养老区用于保存从新生区筛选出来的JAVA对象,一般池对象都在这个区域活跃。

对象池技术基本原理的核心有两点:缓存和共享,即对于那些被频繁使用的对象,在使用完后,不立即将它们释放,而是将它们缓存起来,以供后续的应用程序重复使用,从而减少创建对象和释放对象的次数,进而改善应用程序的性能。事实上,由于对象池技术将对象限制在一定的数量,也有效地减少了应用程序内存上的开销。

common-pool 提供了一套对象池组件,实现的对象池的步骤如下:

  1. 实现自己的池化工厂,继承BasePoolableObjectFactory,再其中覆写makeObject 方法(创建池子中的相应对象)、passivateObject方法(其中可能调用对象的一些set方法等,当用完对象还回池子中时,调用这个方法还原对象状态,如修改对象的某些成员变量)
  2. 在主程序中:(1)创建 ObjectPool (对象池对象),传入自定义池化工厂对象(ObjectPool 接口定义了对对象池操作的方法,如borrowObject方法从自定义池子中取出一个对象)   (2)调用对象池对象的borrowObject方法得到一个对象  (3)用得到的对象进行一系列对象方法调用 (4)调用池子的returnObject 方法归还对象

4.4 永久区

永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存

  • 如果出现java.lang.OutOfMemoryError:PermGen space,说明是Java虚拟机对永久区Perm内存设置不够,一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被沾满。
    • Jdk1.6之前:有永久代,常量值1.6在方法区
    • Jdk1.7:有永久代,但已经逐步“去永久代”,常量池1.7在堆
    • Jdk1.8之后:无永久代,常量池1.8在元空间

4.5 小总结

逻辑上堆由新生代、养老代、元空间构成、实际上堆只有新生和养老代;方法区就是永久代,永久代是方法区的实现

  • 方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的类信息、普通常量、静态常量、编译器编译后的代码等,虽然JVM规范将方法去描述为堆的一个逻辑部分,但他却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
  • 对于HotSpot虚拟机,很多开发者习惯将方法区成为“永久代”,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于一个接口Interface)的一个实现,JDK1.7的版本中,已经将原本放在永久代的字符串常量池移走。
  • 常量池(Constant Pool)是方法区的一部分,Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,这部分内容将在类加载后进入方法区的运行时常量池中存放

常量池

可以分为静态常量池和运行时常量池,静态常量池指的是 class 字节码文件中(主要存放字面量和引用符号,其中字面量包括 :final常量值和文本字符串,引用符号则包含三种:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符),运行时常量池是静态常量池中的类或接口常量的运行时表示形式,会在 JVM 对 class 文件进行加载时,对class 文件中的常量信息进行加载(并根据静态常量池中的对类和接口符号引用,将相应类的 class 文件加载到方法区内存空间中,并将原本静态常量池中的符号引用替换为直接引用),每一个 class 文件都对应拥有一个自己的运行时常量池。

常量池:

Java 中静态/运行时常量池并非特指保存 final 常量,它还保存诸如字面量、类和接口全限定名、字段、方法名称、修饰符等永恒不变的东西。

一个 Java 程序启动时加载了众多的类,有JDK的,也有我们自己定义的,那么我们怎么在程序运行的时候准确定位到类的位置呢?比如 String str = new String("xxx"),我们怎么在虚拟机内存中找到 String 这个类的定义(或者说类的字节码)呢?

答案就在常量池的符号引用中。在未加载到JVM的时候,在 .class 文件的静态常量池中我们可以找到这么一项 CONSTANT_Class(常量表类型),当然这一项仅仅只是符号引用,我们只知道有 java.lang.String 这么一个类。只有等 JVM 启动,并判断程序用到 java.lang.String 的时候才会加载 String 的 .class 文件到内存中(准确地说是方法区),之后,我们就可以在运行时常量池中将原本的符号引用替换为直接引用了。也就是说实际上我们的定位是依靠运行时常量池的,这也就是为什么运行时常量池对于动态加载非常重要的原因。

 

字符串池:

在 JDK 1.6 以及以前的版本中,字符串池是放在 Perm 区(Permanent Generation,永久代)。Perm 区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,容量是固定的,默认在 32 M 到 96 M 间,我们可以通过 -XX:MaxPermSize = N 来配置永久代的大小,但是在运行过程中它仍然还是固定大小的。也有说 Perm 区实际上就是 HotSpot 下的方法区,HotSpot 的开发人员更愿意将方法区称为 Permanent Generation,这里我们不做过多的探讨

在 JDK 1.7 的版本中,字符串池移到Java Heap。在 JDK 1.8 中永久代的说法被废弃,元空间成为方法区的替代品。(本文 5.1 章节补充关于为什么永久代被废弃).
字符串池的实现——StringTable,String 类中提及缓存/池的概念只有intern() 这个方法(可以将字符串加入字符串池,若字符串池已经存在,则将原引用指向池中的字符串),字符串池(String pool)实际上是一个 HashTable

native方法(intern是 native 方法)

native方法,本身并不是由 Java 语言实现的,而是通过 jni (Java Native Interface)调用了其他语言(如C/C++)实现的一些外部方法,StringTable 的 intern() 方法跟 Java 中的 HashMap 的实现是差不多的。

五、JVM垃圾收集(Java Garbage Collection)

5.1 堆内存调优简介

-Xms设置初始分配大小,默认为物理内存的“1/64”
-Xmx最大分配内存,默认为物理内存的“1/4”
-XX:+PrintGCDetails输出详细的GC处理日志

七、GC三大算法

GC 三大算法为:复制算法、标记清除算法、标记整理算法。

当新对象创建时,如果新生代空间不足,则会触发普通 GC(MinorGC),其回收策略称为复制;如果老年代空间不足(老年代空间一般比较大)则会触发全局 GC(FullGC),其回收策略成为整理压缩。频繁收集新生(young)区,较少收集老年(old)区, 基本不动 永久(Perm) 区

普通 GC:复制算法的垃圾回收机制主要包括复制、清空、互换三个部分,首先将新生代分为伊甸区和幸存区(幸存区分为两部分,只有一部分作为内存使用,始终保持另一部分为空内存),其中一块空的幸存区称为 to 区,另一块幸存区和伊甸区构成 From 区,新生代中在一次GC中存活下来的所有对象会被移动到to 区(对象的 age 设为1),然后原来的 From 区会被清空,这时两个幸存区交换身份,即原本为 from 区的幸存区转为 to 区,剩下的幸存区和伊甸区构成新的 From 区,当再次进行GC 的时候,复制算法重新执行上述操作,交换的对象的 age 每次增加1。当对象的 age 增加到15时,这些对象被移动到老年区(称为老年代)。

全局 GC:标记清除算法、标记整理算法的垃圾回收机制称为全局垃圾回收,均是一次扫描整个内存区域,对仍然存活的对象进行标记,然后对未被标记对象进行清除,但标记整理算法会对存活的引用进行整理,保证清理出来的空间是连续的。

复制算法和标记整理算法的内存整齐度更高(因为标记清除算法清理出来的空闲内存空间不是连续的),但复制算法的内存利用率低(因为浪费了一半的幸存区)

7.1 GC算法总体概述

JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。

Java中的GC回有两种回收:年轻带的MinorGC,老年代的FullGC;新对象创建时如果伊甸园空间不足会触发MinorGC,如果此时老年代的内存空间不足会触发FullGC,如果空间都不足抛出OutOfMemoryError。

因此GC按照回收的区域又分了两种类型,一种是普通GC(MinorGC),一种是全局GC(FullGC)

  • 普通GC:只针对新生代区域的GC

  • 全局GC:针对年老代的GC,偶尔伴随对新生代的GC以及对永久代的GC。

7.2 复制算法:MinorGC(普通GC)

新生代使用的MinorGC,这种GC算法采用的是复制算法(Copying),频繁使用

复制-->清空-->互换

7.2.1 原理

MinorGC会把Eden中的所有活着的对象都移到Survivor区域中,如果Survivor区中放不下,那么剩下的活的对象就被移到Old Generation中,也即一旦GC后,Eden区就变成空的了。

当对象在Eden(包括一个Survivor区域,这里假设是from区域)出生后,在经过一次MinorGC后,如果对象还存活,并且能够被另外一块Survivor区域所容纳(上面已经假设为from区域,这里应为to区域,即to区域有足够的内存空间来存储Eden和from区域中存活的对象),则使用复制算法将这些仍然还存活的对象复制到另外一块Survivor区域(即to区)中,然后清理所有使用过的Eden以及Survivor区域(即from区),并且讲这些对象的年龄设置为1,以后对象在Survivor区每熬过一次MinorGC,就将对象的年龄+1当对象的年龄达到某个值时(默认15,通过-XX:MaxTenuringThreshold来设定参数),这些对象就会成为老年代。

==-XX:MaxTenuringThreshold设置对象在新生代中存活的次数==

7.2.2 解释

HotSpot JVM把年轻代分为了三部分:1个Eden区和两个Survivor区,默认比例是8:1:1一般情况下,新创建的对象都会被分配到Eden区,这些对象经过第一次的MinorGC后,如果仍然存活,将会被移到Survivor区。对象Survivor区中每熬过一次MinorGC,年龄就增加一岁,当他的年龄增加到一定程度时,就会被移动到年老代中。因为年轻代中的对象基本都是朝生夕死(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将活着的对象复制到另外一块上面。复制算法不会产生内存碎片。

==复制要交换,谁空谁是to==

7.3.3 劣势

复制算法弥补了标记清除算法中,内存布局混乱的缺点。

  1. 浪费了一半的内存,太要命了
  2. 如果对象的存活率很高,我们可以极端一点,假设是100%存活率,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度是,将会变的不可忽视。所以从以上描述不难看出,复制算法想要使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要客服50%的内存的浪费

7.3 标记清除/标记整理算法:FullGC又叫MajorGC(全局GC)

老年代一般是由标记清除或者是标记清除与标记整理的混合实现

7.3.1 标记清除(Mark-Sweep)

7.3.1.1 原理

  1. 标记(mark)

    从根集合开始扫描,对存活的对象进行标记

  2. 清除(Sweep)

    扫描整个内存空间,回收未被标记的对象,使用free-list记录可以区域。

7.3.1.2 劣势

  1. 效率低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲
  2. 清理出来的空闲内存不是连续的,我们的死亡对象都是随机的出现在内存的各个角落,限制把他们清除之后,内存的布局自然会乱七八糟,而为了应付这一点,JVM不得不维持一个内存的空闲列表,这又是一种开销,而且在分配数组对象的时候,寻找连续的内存空间会不太好找。

7.3.2 标记整理(Mark-Compact)

7.3.2.1 原理

  1. 标记

    与标记-清除一样

  2. 压缩整理

    再次扫描,并往一段滑动存活对象

7.3.2.2 劣势

效率不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上说,效率要低于复制算法

7.4 小总结

  • 内存效率:复制算法>标记清除算法>标记整理算法
  • 内存整齐度:复制算法=标记整理算法>标记清除算法
  • 内存利用率:标记整理算法=标记清除算法>复制算法

分代收集算法

引用计数法:

  • 缺点:每次对对象赋值时均要维护引用计数器,且计数器本身也有一定的消耗
  • 较难处理循环引用

二、JVM 参数

JVM的参数类型:

  • 标配参数

    • -version java -version
    • -help
  • X参数(了解)

    • -Xint:解释执行
    • -Xcomp:第一次使用就编译成本地代码
    • -Xmixed:混合模式
  • XX参数(下一节)

JVM的XX参数之布尔类型


公式:-XX:+ 或者 - 某个属性值(+表示开启,-表示关闭)

如何查看一个正在运行中的java程序,它的某个jvm参数是否开启?具体值是多少?

jps -l   //查看一个正在运行中的java程序,得到Java程序号。
jinfo -flag PrintGCDetails (Java程序号 )   //查看它的某个jvm参数(如PrintGCDetails )是否开启。
jinfo -flags (Java程序号 )   //查看它的所有jvm参数

通过上述步骤,可以查看某个 正在运行的java程序的某个 JVM 参数的当前值是多少,如果不满意(需要调整),则可以在运行时修改 Edit Configuration 中的 VM Options ,在其中修改布尔类型或者设值类型的 JVM 参数(IDEA 环境下),修改完成之后再调用 jinfo 命令查看该参数,则会发现其值发生变化(如,可以将MetaSpaceSize 从大概22m,修改到128m)

是否打印GC收集细节

-XX:-PrintGCDetails
-XX:+PrintGCDetails
是否使用串行垃圾回收器

-XX:-UseSerialGC
-XX:+UserSerialGC

JVM的XX参数之设值类型

公式:-XX:属性key=属性值value

Case

  • -XX:MetaspaceSize=128m  //方法区中的元数据空间大小
  • -XX:MaxTenuringThreshold=15   //到多大的年龄, young区中的可以升级到 old 区

JVM的XX参数之XmsXmx坑题

两个经典XX参数:(JVM 在进行加载的时候,根据物理内存的大小进行值的变更)

  • -Xms等价于-XX:InitialHeapSize,初始大小内存,默认物理内存1/64   
  • -Xmx等价于-XX:MaxHeapSize,最大分配内存,默认为物理内存1/4

设值方式:-Xms100m(没有+-=号,但是仍然是-XX 参数,只不过因为经常使用,起了一个别名)

JVM盘点家底查看初始默认值

在进行 JVM 调优的时候,应该利用该命令对 JVM 参数的初始值进行查看

查看初始默认的参数值    -XX:+PrintFlagsInitial    公式:java -XX:+PrintFlagsInitial  后面可以带上-version 也可以不加

查看修改更新后参数值   -XX:+PrintFlagsFinal      公式:java -XX:+PrintFlagsFinal   命令返回的结果中 =表示默认,:=表示修改过的(如,由于-Xms 和-Xmx 参数是在 JVM 加载时重新设值的,因此其形式为:=)。

C:\Users\abc>java -XX:+PrintFlagsFinal
...
   size_t HeapBaseMinAddress                       = 2147483648                             {pd product} {default}
     bool HeapDumpAfterFullGC                      = false                                  {manageable} {default}
     bool HeapDumpBeforeFullGC                     = false                                  {manageable} {default}
     bool HeapDumpOnOutOfMemoryError               = false                                  {manageable} {default}
    ccstr HeapDumpPath                             =                                        {manageable} {default}
    uintx HeapFirstMaximumCompactionCount          = 3                                         {product} {default}
    uintx HeapMaximumCompactionInterval            = 20                                        {product} {default}
    uintx HeapSearchSteps                          = 3                                         {product} {default}
   size_t HeapSizePerGCThread                      = 43620760                                  {product} {default}
     bool IgnoreEmptyClassPaths                    = false                                     {product} {default}
     bool IgnoreUnrecognizedVMOptions              = false                                     {product} {default}
    uintx IncreaseFirstTierCompileThresholdAt      = 50                                        {product} {default}
     bool IncrementalInline                        = true                                   {C2 product} {default}
   size_t InitialBootClassLoaderMetaspaceSize      = 4194304                                   {product} {default}
    uintx InitialCodeCacheSize                     = 2555904                                {pd product} {default}
   size_t InitialHeapSize                          := 268435456                                 {product} {ergonomic}
...

JVM盘点家底查看修改变更值

运行java命令的同时打印出参数

java -XX:+PrintFlagsFinal -XX:MetaspaceSize=512m HelloWorld   会打印出修改后的JVM 参数(该命令行中对 JVM 参数的修改生效)以及程序返回值

打印命令行参数

-XX:+PrintCommandLineFlags   会打印出常见的JVM参数(如InitialHeapSize、MaxHeapSize、默认的垃圾回收器等)


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值