若是在文中用词或者理解方面出现问题,欢迎指出。此文旨在说起和而不深究,但会尽可能效率地把知识点都抛出来css
JVM 是 Java Virtual Machine 的缩写,它是一个虚构出来的计算机,一种规范。经过在实际的计算机上仿真模拟各种计算机功能实现···java
好,其实抛开这么专业的句子不说,就知道JVM其实就相似于一台小电脑运行在windows或者linux这些操做系统环境下便可。它直接和操做系统进行交互,与硬件不直接交互,可操做系统能够帮咱们完成和硬件进行交互的工做。
linux
好比咱们如今写了一个 HelloWorld.java 好了,那这个 HelloWorld.java 抛开全部东西不谈,那是否是就相似于一个文本文件,只是这个文本文件它写的都是英文,并且有必定的缩进而已。web
那咱们的 JVM 是不认识文本文件的,因此它须要一个 编译 ,让其成为一个它会读二进制文件的 HelloWorld.class 面试
若是 JVM 想要执行这个 .class 文件,咱们须要将其装进一个 类加载器 中,它就像一个搬运工同样,会把全部的 .class 文件所有搬进JVM里面来。
算法
方法区 是用于存放相似于元数据信息方面的数据的,好比类信息,常量,静态变量,编译后代码···等sql
类加载器将 .class 文件搬过来就是先丢到这一块上windows
堆 主要放了一些存储的数据,好比对象实例,数组···等,它和方法区都同属于 线程共享区域 。也就是说它们都是 线程不安全 的数组
栈 这是咱们的代码运行空间。咱们编写的每个方法都会放到 栈 里面运行。安全
咱们会据说过 本地方法栈 或者 本地方法接口 这两个名词,不过咱们基本不会涉及这两块的内容,它俩底层是使用C来进行工做的,和Java没有太大的关系。
主要就是完成一个加载工做,相似于一个指针同样的,指向下一行咱们须要执行的代码。和栈同样,都是 线程独享 的,就是说每个线程都会有本身对应的一块区域而不会存在并发和多线程的问题。
一个简单的学生类
一个main方法
执行main方法的步骤以下:
其实也不用管太多,只须要知道对象实例初始化时会去方法区中找类信息,完成后再到栈那里去运行方法。找方法就在方法表中找。
以前也提到了它是负责加载.class文件的,它们在文件开头会有特定的文件标示,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,而且ClassLoader只负责class文件的加载,而是否可以运行则由 Execution Engine 来决定
从类被加载到虚拟机内存中开始,到释放内存总共有7个步骤:加载,验证,准备,解析,初始化,使用,卸载。其中验证,准备,解析三个部分统称为链接
初始化其实就是一个赋值的操做,它会执行一个类构造器的<clinit>()方法。由编译器自动收集类中全部变量的赋值动做,此时准备阶段时的那个 static int a = 3 的例子,在这个时候就正式赋值为3
GC将无用对象从内存中卸载
加载一个Class类的顺序也是有优先级的,类加载器从最底层开始往上的顺序是这样的
当一个类收到了加载请求时,它是不会先本身去尝试加载的,而是委派给父类去完成,好比我如今要new一个Person,这个Person是咱们自定义的类,若是咱们要加载它,就会先委派App ClassLoader,只有当父类加载器都反馈本身没法完成这个请求(也就是父类加载器都没有找到加载所需的Class)时,子类加载器才会自行尝试加载
这样作的好处是,加载位于rt.jar包中的类时无论是哪一个加载器加载,最终都会委托到BootStrap ClassLoader进行加载,这样保证了使用不一样的类加载器获得的都是同一个结果。
其实这个也是一个隔离的做用,避免了咱们的代码影响了JDK的代码,好比我如今要来一个
public class String(){
public static void main(){sout;}
}
复制代码
这种时候,咱们的代码确定会报错,由于在加载的时候实际上是找到了rt.jar中的String.class,而后发现这也没有main方法
好比说咱们如今点开Thread类的源码,会看到它的start0方法带有一个native关键字修饰,并且不存在方法体,这种用native修饰的方法就是本地方法,这是使用C来实现的,而后通常这些方法都会放到一个叫作本地方法栈的区域。
程序计数器其实就是一个指针,它指向了咱们程序中下一句须要执行的指令,它也是内存区域中惟一一个不会出现OutOfMemoryError的区域,并且占用内存空间小到基本能够忽略不计。这个内存仅表明当前线程所执行的字节码的行号指示器,字节码解析器经过改变这个计数器的值选取下一条须要执行的字节码指令。
若是执行的是native方法,那这个指针就不工做了。
方法区主要的做用技术存放类的元数据信息,常量和静态变量···等。当它存储的信息过大时,会在没法知足内存分配时报错。
一句话即是:栈管运行,堆管存储。则虚拟机栈负责运行代码,而虚拟机堆负责存储数据。
它是Java方法执行的内存模型。里面会对局部变量,动态链表,方法出口,栈的操做(入栈和出栈)进行存储,且线程独享。同时若是咱们听到局部变量表,那也是在说虚拟机栈
public class Person{
int a = 1;
public void doSomething(){
int b = 2;
}
}
复制代码
若是线程请求的栈的深度大于虚拟机栈的最大深度,就会报 StackOverflowError (这种错误常常出如今递归中)。Java虚拟机也能够动态扩展,但随着扩展会不断地申请内存,当没法申请足够内存时就会报错 OutOfMemoryError。
对于栈来讲,不存在垃圾回收。只要程序运行结束,栈的空间天然就会释放了。栈的生命周期和所处的线程是一致的。
这里补充一句:8种基本类型的变量+对象的引用变量+实例方法都是在栈里面分配内存。
咱们常常说的栈帧数据,说白了在JVM中叫栈帧,放到Java中其实就是方法,它也是存放在栈中的。
栈中的数据都是以栈帧的格式存在,它是一个关于方法和运行期数据的数据集。好比咱们执行一个方法a,就会对应产生一个栈帧A1,而后A1会被压入栈中。同理方法b会有一个B1,方法c会有一个C1,等到这个线程执行完毕后,栈会先弹出C1,后B1,A1。它是一个先进后出,后进先出原则。
局部变量表用于存放方法参数和方法内部所定义的局部变量。它的容量是以Slot为最小单位,一个slot能够存放32位之内的数据类型。
虚拟机经过索引定位的方式使用局部变量表,范围为[0,局部变量表的slot的数量]。方法中的参数就会按必定顺序排列在这个局部变量表中,至于怎么排的咱们能够先不关心。而为了节省栈帧空间,这些slot是能够复用的,当方法执行位置超过了某个变量,那么这个变量的slot能够被其它变量复用。固然若是须要复用,那咱们的垃圾回收天然就不会去动这些内存。
JVM内存会划分为堆内存和非堆内存,堆内存中也会划分为年轻代和老年代,而非堆内存则为永久代。年轻代又会分为Eden和Survivor区。Survivor也会分为FromPlace和ToPlace,toPlace的survivor区域是空的。Eden,FromPlace和ToPlace的默认占比为 8:1:1。固然这个东西其实也能够经过一个 -XX:+UsePSAdaptiveSurvivorSizePolicy 参数来根据生成对象的速率动态调整
堆内存中存放的是对象,垃圾收集就是收集这些对象而后交给GC算法进行回收。非堆内存其实咱们已经说过了,就是方法区。在1.8中已经移除永久代,替代品是一个元空间(MetaSpace),最大区别是metaSpace是不存在于JVM中的,它使用的是本地内存。并有两个参数
MetaspaceSize:初始化元空间大小,控制发生GC
MaxMetaspaceSize:限制元空间大小上限,防止占用过多物理内存。
复制代码
移除的缘由能够大体了解一下:融合HotSpot JVM和JRockit VM而作出的改变,由于JRockit是没有永久代的,不过这也间接性地解决了永久代的OOM问题。
当咱们new一个对象后,会先放到Eden划分出来的一块做为存储空间的内存,可是咱们知道对堆内存是线程共享的,因此有可能会出现两个对象共用一个内存的状况。这里JVM的处理是每一个线程都会预先申请好一块连续的内存空间并规定了对象存放的位置,而若是空间不足会再申请多块内存空间。这个操做咱们会称做TLAB,有兴趣能够了解一下。
当Eden空间满了以后,会触发一个叫作Minor GC(就是一个发生在年轻代的GC)的操做,存活下来的对象移动到Survivor0区。Survivor0区满后触发 Minor GC,就会将存活对象移动到Survivor1区,此时还会把from和to两个指针交换,这样保证了一段时间内总有一个survivor区为空且to所指向的survivor区为空。通过屡次的 Minor GC后仍然存活的对象(这里的存活判断是15次,对应到虚拟机参数为 -XX:TargetSurvivorRatio 。为何是15,由于HotSpot会在对象投中的标记字段里记录年龄,分配到的空间仅有4位,因此最多只能记录到15)会移动到老年代。老年代是存储长期存活的对象的,占满时就会触发咱们最常据说的Full GC,期间会中止全部线程等待GC的完成。因此对于响应要求高的应用应该尽可能去减小发生Full GC从而避免响应超时的问题。
并且当老年区执行了full gc以后仍然没法进行对象保存的操做,就会产生OOM,这时候就是虚拟机中的堆内存不足,缘由可能会是堆内存设置的大小太小,这个能够经过参数-Xms、-Xms来调整。也多是代码中建立的对象大且多,并且它们一直在被引用从而长时间垃圾收集没法收集它们。
图中程序计数器、虚拟机栈、本地方法栈,3个区域随着线程的生存而生存的。内存分配和回收都是肯定的。随着线程的结束内存天然就被回收了,所以不须要考虑垃圾回收的问题。而Java堆和方法区则不同,各线程共享,内存的分配和回收都是动态的。所以垃圾收集器所关注的都是堆和方法这部份内存。
在进行回收前就要判断哪些对象还存活,哪些已经死去。下面介绍两个基础的计算方法
1.引用计数器计算:给对象添加一个引用计数器,每次引用这个对象时计数器加一,引用失效时减一,计数器等于0时就是不会再次使用的。不过这个方法有一种状况就是出现对象的循环引用时GC无法回收。
2.可达性分析计算:这是一种相似于二叉树的实现,将一系列的GC ROOTS做为起始的存活对象集,从这个节点往下搜索,搜索所走过的路径成为引用链,把能被该集合引用到的对象加入到集合中。搜索当一个对象到GC Roots没有使用任何引用链时,则说明该对象是不可用的。主流的商用程序语言,例如Java,C#等都是靠这招去断定对象是否存活的。
(了解一下便可)在Java语言汇总能做为GC Roots的对象分为如下几种:
这种方法的优势是可以解决循环引用的问题,可它的实现须要耗费大量资源和时间,也须要GC(它的分析过程引用关系不能发生变化,因此须要中止全部进程)
首先必需要提到的是一个名叫 finalize() 的方法
finalize()是Object类的一个方法、一个对象的finalize()方法只会被系统自动调用一次,通过finalize()方法逃脱死亡的对象,第二次不会再调用。
补充一句:并不提倡在程序中调用finalize()来进行自救。建议忘掉Java程序中该方法的存在。由于它执行的时间不肯定,甚至是否被执行也不肯定(Java程序的不正常退出),并且运行代价高昂,没法保证各个对象的调用顺序(甚至有不一样线程中调用)。在Java9中已经被标记为 deprecated ,且java.lang.ref.Cleaner(也就是强、软、弱、幻象引用的那一套)中已经逐步替换掉它,会比finalize来的更加的轻量及可靠。
判断一个对象的死亡至少须要两次标记
若是肯定对象已经死亡,咱们又该如何回收这些垃圾呢
不会很是详细的展开,经常使用的有标记清除,复制,标记整理和分代收集算法
标记清除算法就是分为“标记”和“清除”两个阶段。标记出全部须要回收的对象,标记结束后统一回收。这个套路很简单,也存在不足,后续的算法都是根据这个基础来加以改进的。
其实它就是把已死亡的对象标记为空闲内存,而后记录在一个空闲列表中,当咱们须要new一个对象时,内存管理模块会从空闲列表中寻找空闲的内存来分给新的对象。
不足的方面就是标记和清除的效率比较低下。且这种作法会让内存中的碎片很是多。这个致使了若是咱们须要使用到较大的内存块时,没法分配到足够的连续内存。好比下图
此时可以使用的内存块都是零零散散的,致使了刚刚提到的大内存对象问题
为了解决效率问题,复制算法就出现了。它将可用内存按容量划分红两等分,每次只使用其中的一块。和survivor同样也是用from和to两个指针这样的玩法。fromPlace存满了,就把存活的对象copy到另外一块toPlace上,而后交换指针的内容。这样就解决了碎片的问题。
这个算法的代价就是把内存缩水了,这样堆内存的使用效率就会变得十分低下了
不过它们分配的时候也不是按照1:1这样进行分配的,就相似于Eden和Survivor也不是等价分配是一个道理。
复制算法在对象存活率高的时候会有必定的效率问题,标记过程仍然与“标记-清除”算法同样,但后续步骤不是直接对可回收对象进行清理,而是让全部存活的对象都向一端移动,而后直接清理掉边界之外的内存
这种算法并无什么新的思想,只是根据对象存活周期的不一样将内存划分为几块。通常是把Java堆分为新生代和老年代,这样就能够根据各个年代的特色采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少许存活,那就选用复制算法,只须要付出少许存活对象的复制成本就能够完成收集。而老年代中由于对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。
说白了就是八仙过海各显神通,具体问题具体分析了而已。
HotSpot VM中的垃圾回收器,以及适用场景
到jdk8为止,默认的垃圾收集器是Parallel Scavenge 和 Parallel Old
从jdk9开始,G1收集器成为默认的垃圾收集器
目前来看,G1回收器停顿时间最短并且没有明显缺点,很是适合Web应用。在jdk8中测试Web应用,堆内存6G,新生代4.5G的状况下,Parallel Scavenge 回收新生代停顿长达1.5秒。G1回收器回收一样大小的新生代只停顿0.2秒。
JVM的参数很是之多,这里只列举比较重要的几个,经过各类各样的搜索引擎也能够得知这些信息。
参数名称 | 含义 | 默认值 | 说明 |
---|---|---|---|
-Xms | 初始堆大小 | 物理内存的1/64(<1GB) | 默认(MinHeapFreeRatio参数能够调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制. |
-Xmx | 最大堆大小 | 物理内存的1/4(<1GB) | 默认(MaxHeapFreeRatio参数能够调整)空余堆内存大于70%时,JVM会减小堆直到 -Xms的最小限制 |
-Xmn | 年轻代大小(1.4or lator) | 注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不一样的。整个堆大小=年轻代大小 + 老年代大小 + 持久代(永久代)大小.增大年轻代后,将会减少年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8 | |
-XX:NewSize | 设置年轻代大小(for 1.3/1.4) | ||
-XX:MaxNewSize | 年轻代最大值(for 1.3/1.4) | ||
-XX:PermSize | 设置持久代(perm gen)初始值 | 物理内存的1/64 | |
-XX:MaxPermSize | 设置持久代最大值 | 物理内存的1/4 | |
-Xss | 每一个线程的堆栈大小 | JDK5.0之后每一个线程堆栈大小为1M,之前每一个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减少这个值能生成更多的线程.可是操做系统对一个进程内的线程数仍是有限制的,不能无限生成,经验值在3000~5000左右通常小的应用, 若是栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,须要严格的测试。(校长)和threadstacksize选项解释很相似,官方文档彷佛没有解释,在论坛中有这样一句话:-Xss is translated in a VM flag named ThreadStackSize”通常设置这个值就能够了 | |
-XX:NewRatio | 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代) | -XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5Xms=Xmx而且设置了Xmn的状况下,该参数不须要进行设置。 | |
-XX:SurvivorRatio | Eden区与Survivor区的大小比值 | 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10 | |
-XX:+DisableExplicitGC | 关闭System.gc() | 这个参数须要严格的测试 | |
-XX:PretenureSizeThreshold | 对象超过多大是直接在旧生代分配 | 0 | 单位字节 新生代采用Parallel ScavengeGC时无效另外一种直接在旧生代分配的状况是大的数组对象,且数组中无外部引用对象. |
-XX:ParallelGCThreads | 并行收集器的线程数 | 此值最好配置与处理器数目相等 一样适用于CMS | |
-XX:MaxGCPauseMillis | 每次年轻代垃圾回收的最长时间(最大暂停时间) | 若是没法知足此时间,JVM会自动调全年轻代大小,以知足此值. |
其实还有一些打印及CMS方面的参数,这里就不以一一列举了
根据刚刚涉及的jvm的知识点,咱们能够尝试对JVM进行调优,主要就是堆内存那块
全部线程共享数据区大小=新生代大小 + 年老代大小 + 持久代大小。持久代通常固定大小为64m。因此java堆中增大年轻代后,将会减少年老代大小(由于老年代的清理是使用fullgc,因此老年代太小的话反而是会增多fullgc的)。此值对系统性能影响较大,Sun官方推荐配置为java堆的3/8。
-Xmx –Xms:指定java堆最大值(默认值是物理内存的1/4(<1GB))和初始java堆最小值(默认值是物理内存的1/64(<1GB))
默认(MinHeapFreeRatio参数能够调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.,默认(MaxHeapFreeRatio参数能够调整)空余堆内存大于70%时,JVM会减小堆直到 -Xms的最小限制。简单点来讲,你不停地往堆内存里面丢数据,等它剩余大小小于40%了,JVM就会动态申请内存空间不过会小于-Xmx,若是剩余大小大于70%,又会动态缩小不过不会小于–Xms。就这么简单
开发过程当中,一般会将 -Xms 与 -Xmx两个参数的配置相同的值,其目的是为了可以在java垃圾回收机制清理完堆区后不须要从新分隔计算堆区的大小而浪费资源。
咱们执行下面的代码
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间
复制代码
注意:此处设置的是Java堆大小,也就是新生代大小 + 老年代大小
设置一个VM options的参数
-Xmx20m -Xms5m -XX:+PrintGCDetails
复制代码
再次启动main方法
这时候申请到的内存为18M,空闲内存为4.214195251464844M
咱们此时建立一个字节数组看看,执行下面的代码
byte[] b = new byte[1 * 1024 * 1024];
System.out.println("分配了1M空间给数组");
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
复制代码
此时free memory就又缩水了,不过total memory是没有变化的。Java会尽量将total mem的值维持在最小堆内存大小
byte[] b = new byte[10 * 1024 * 1024];
System.out.println("分配了10M空间给数组");
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间
复制代码
这时候咱们建立了一个10M的字节数据,这时候最小堆内存是顶不住的。咱们会发现如今的total memory已经变成了15M,这就是已经申请了一次内存的结果。
此时咱们再跑一下这个代码
System.gc();
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间
复制代码
此时咱们手动执行了一次fullgc,此时total memory的内存空间又变回5.5M了,此时又是把申请的内存释放掉的结果。
-XX:NewRatio --- 新生代(eden+2*Survivor)和老年代(不包含永久区)的比值
例如:-XX:NewRatio=4,表示新生代:老年代=1:4,即新生代占整个堆的1/5。在Xms=Xmx而且设置了Xmn的状况下,该参数不须要进行设置。
-XX:SurvivorRatio(幸存代)--- 设置两个Survivor区和eden的比值
例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10
-XX:NewSize --- 设置年轻代大小
-XX:MaxNewSize --- 设置年轻代最大值
能够经过设置不一样参数来测试不一样的状况,反正最优解固然就是官方的Eden和Survivor的占比为8:1:1,而后在刚刚介绍这些参数的时候都已经附带了一些说明,感兴趣的也能够看看。反正最大堆内存和最小堆内存若是数值不一样会致使屡次的gc,须要注意。
根据实际事情调整新生代和幸存代的大小,官方推荐新生代占java堆的3/8,幸存代占新生代的1/10
在OOM时,记得Dump出堆,确保能够排查现场问题,经过下面命令你能够输出一个.dump文件,这个文件可使用VisualVM或者Java自带的Java VisualVM工具。
-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=你要输出的日志路径
复制代码
通常咱们也能够经过编写脚本的方式来让OOM出现时给咱们报个信,能够经过发送邮件或者重启程序等来解决。
-XX:PermSize -XX:MaxPermSize
复制代码
初始空间(默认为物理内存的1/64)和最大空间(默认为物理内存的1/4)。也就是说,jvm启动时,永久区一开始就占用了PermSize大小的空间,若是空间还不够,能够继续扩展,可是不能超过MaxPermSize,不然会OOM。
tips:若是堆空间没有用完也抛出了OOM,有多是永久区致使的。堆空间实际占用很是少,可是永久区溢出 同样抛出OOM。
能够经过-Xss:调整每一个线程栈空间的大小
JDK5.0之后每一个线程堆栈大小为1M,之前每一个线程堆栈大小为256K。在相同物理内存下,减少这个值能生成更多的线程。可是操做系统对一个进程内的线程数仍是有限制的,不能无限生成,经验值在3000~5000左右
-XXThreadStackSize:
设置线程栈的大小(0 means use default stack size)
复制代码
这些参数都是能够经过本身编写程序去简单测试的,这里碍于篇幅问题就再也不提供demo了
形形色色的参数不少,就不会说把全部都扯个遍了,由于你们其实也不会说必定要去深究到底。
-XXThreadStackSize:
设置内存页的大小,不可设置过大,会影响Perm的大小
复制代码
-XX:+UseFastAccessorMethods:
设置原始类型的快速优化
复制代码
-XX:+DisableExplicitGC:
设置关闭System.gc()(这个参数须要严格的测试)
复制代码
-XX:MaxTenuringThreshold
设置垃圾最大年龄。若是设置为0的话,则年轻代对象不通过Survivor区,直接进入年老代.
对于年老代比较多的应用,能够提升效率。若是将此值设置为一个较大值,
则年轻代对象会在Survivor区进行屡次复制,这样能够增长对象再年轻代的存活时间,
增长在年轻代即被回收的几率。该参数只有在串行GC时才有效.
复制代码
-XX:+AggressiveOpts
复制代码
加快编译速度
-XX:+UseBiasedLocking
复制代码
-Xnoclassgc
复制代码
-XX:SoftRefLRUPolicyMSPerMB
设置每兆堆空闲空间中SoftReference的存活时间,默认值是1s。
复制代码
-XX:PretenureSizeThreshold
设置对象超过多大时直接在老年代分配,默认值是0。
复制代码
-XX:TLABWasteTargetPercent
设置TLAB占eden区的百分比,默认值是1% 。
复制代码
-XX:+CollectGen0First
设置FullGC时是否先YGC,默认值是false。
复制代码
真的扯了好久这东西,参考了多方的资料,有极客时间的《深刻拆解虚拟机》和《Java核心技术面试精讲》,也有百度,也有本身在学习的一些线上课程的总结。但愿对你有所帮助,谢谢。