【JVM】深入了解JAVA原理,你得先了解它!

【JVM专题】一篇文章掌握整个JVM,JVM超详细解析!!! 一文熟悉JVM 的基本概念,JVM 的运行过程,JVM 内存区域,垃圾回收与算法,JAVA 四中引用类型,JAVA IO/NIO,JVM 类加载机制等 阅读详情

前言

如果在文中用词或者理解方面出现问题,欢迎指出。此文旨在提及而不深究,但会尽量效率地把知识点都抛出来

一、JVM的基本介绍

JVM 是 Java Virtual Machine 的缩写,它是一个虚构出来的计算机,一种规范。通过在实际的计算机上仿真模拟各类计算机功能实现···

好,其实抛开这么专业的句子不说,就知道JVM其实就类似于一台小电脑运行在windows或者linux这些操作系统环境下即可。它直接和操作系统进行交互,与硬件不直接交互,可操作系统可以帮我们完成和硬件进行交互的工作。
在这里插入图片描述

1.1 Java文件是如何被运行的

比如我们现在写了一个 HelloWorld.java 好了,那这个 HelloWorld.java 抛开所有东西不谈,那是不是就类似于一个文本文件,只是这个文本文件它写的都是英文,而且有一定的缩进而已。

那我们的 JVM 是不认识文本文件的,所以它需要一个 编译 ,让其成为一个它会读二进制文件的 HelloWorld.class

① 类加载器
如果 JVM 想要执行这个 .class 文件,我们需要将其装进一个 类加载器 中,它就像一个搬运工一样,会把所有的 .class 文件全部搬进JVM里面来。

在这里插入图片描述

② 方法区
方法区 是用于存放类似于元数据信息方面的数据的,比如类信息,常量,静态变量,编译后代码···等

类加载器将 .class 文件搬过来就是先丢到这一块上

③ 堆
堆 主要放了一些存储的数据,比如对象实例,数组···等,它和方法区都同属于 线程共享区域 。也就是说它们都是 线程不安全 的

④ 栈
栈 这是我们的代码运行空间。我们编写的每一个方法都会放到 栈 里面运行。

我们会听说过 本地方法栈 或者 本地方法接口 这两个名词,不过我们基本不会涉及这两块的内容,它俩底层是使用C来进行工作的,和Java没有太大的关系。

⑤ 程序计数器
主要就是完成一个加载工作,类似于一个指针一样的,指向下一行我们需要执行的代码。和栈一样,都是 线程独享 的,就是说每一个线程都会有自己对应的一块区域而不会存在并发和多线程的问题。
在这里插入图片描述

小总结
Java文件经过编译后变成 .class 字节码文件

字节码文件通过类加载器被搬运到 JVM 虚拟机中

虚拟机主要的5大块:方法区,堆都为线程共享区域,有线程安全问题,栈和本地方法栈和计数器都是独享区域,不存在线程安全问题,而 JVM 的调优主要就是围绕堆,栈两大块进行

1.2 简单的代码例子

一个简单的学生类

在这里插入图片描述

一个main方法

在这里插入图片描述

执行main方法的步骤如下:

  1. 编译好 App.java 后得到 App.class 后,执行 App.class,系统会启动一个 JVM 进程,从 classpath 路径中找到一个名为 App.class 的二进制文件,将 App 的类信息加载到运行时数据区的方法区内,这个过程叫做 App 类的加载。
  2. JVM 找到 App 的主程序入口,执行main方法。
  3. 这个main中的第一条语句为Student studen = new Student(“tellUrDream”) ,就是让 JVM创建一个Student对象,但是这个时候方法区中是没有 Student 类的信息的,所以 JVM 马上加载 Student 类,把Student 类的信息放到方法区中。
  4. 加载完 Student 类后,JVM 在堆中为一个新的 Student 实例分配内存,然后调用构造函数初始化 Student 实例,这个Student 实例持有 指向方法区中的Student 类的类型信息 的引用。
  5. 执行student.sayName();时,JVM 根据 student 的引用找到 student 对象,然后根据 student对象持有的引用定位到方法区中 student 类的类型信息的方法表,获得 sayName() 的字节码地址。
  6. 执行sayName()

其实也不用管太多,只需要知道对象实例初始化时会去方法区中找类信息,完成后再到栈那里去运行方法。找方法就在方法表中找。

二、类加载器的介绍

之前也提到了它是负责加载.class文件的,它们在文件开头会有特定的文件标示,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,并且ClassLoader只负责class文件的加载,而是否能够运行则由 Execution Engine 来决定

2.1 类加载器的流程

从类被加载到虚拟机内存中开始,到释放内存总共有7个步骤:加载,验证,准备,解析,初始化,使用,卸载。其中验证,准备,解析三个部分统称为连接。

2.1.1 加载
  1. 将class文件加载到内存
  2. 将静态数据结构转化成方法区中运行时的数据结构
  3. 在堆中生成一个代表这个类的 java.lang.Class对象作为数据访问的入口
2.1.2 链接
  1. 验证:确保加载的类符合 JVM 规范和安全,保证被校验类的方法在运行时不会做出危害虚拟机的事件,其实就是一个安全检查
  2. 准备:为static变量在方法区中分配内存空间,设置变量的初始值,例如 static int a = 3(注意:准备阶段只设置类中的静态变量(方法区中),不包括实例变量(堆内存中),实例变量是对象初始化时赋值的)
  3. 解析:虚拟机将常量池内的符号引用替换为直接引用的过程(符号引用比如我现在import java.util.ArrayList这就算符号引用,直接引用就是指针或者对象地址,注意引用对象一定是在内存进行)
2.1.3 初始化

初始化其实就是一个赋值的操作,它会执行一个类构造器的()方法。由编译器自动收集类中所有变量的赋值动作,此时准备阶段时的那个 static int a = 3 的例子,在这个时候就正式赋值为3

2.1.4 卸载

GC将无用对象从内存中卸载

2.2 类加载器的加载顺序

加载一个Class类的顺序也是有优先级的,类加载器从最底层开始往上的顺序是这样的

  1. BootStrap ClassLoader:rt.jar

  2. Extention ClassLoader: 加载扩展的jar包

  3. App ClassLoader:指定的classpath下面的jar包

  4. Custom ClassLoader:自定义的类加载器

2.3 双亲委派机制

当一个类收到了加载请求时,它是不会先自己去尝试加载的,而是委派给父类去完成,比如我现在要new一个Person,这个Person是我们自定义的类,如果我们要加载它,就会先委派App ClassLoader,只有当父类加载器都反馈自己无法完成这个请求(也就是父类加载器都没有找到加载所需的Class)时,子类加载器才会自行尝试加载

这样做的好处是,加载位于rt.jar包中的类时不管是哪个加载器加载,最终都会委托到BootStrap ClassLoader进行加载,这样保证了使用不同的类加载器得到的都是同一个结果。

其实这个也是一个隔离的作用,避免了我们的代码影响了JDK的代码,比如我现在要来一个

public class String(){
    public static void main(){sout;}
}

这种时候,我们的代码肯定会报错,因为在加载的时候其实是找到了rt.jar中的String.class,然后发现这也没有main方法

三、运行时数据区

3.1 本地方法栈和程序计数器

比如说我们现在点开Thread类的源码,会看到它的start0方法带有一个native关键字修饰,而且不存在方法体,这种用native修饰的方法就是本地方法,这是使用C来实现的,然后一般这些方法都会放到一个叫做本地方法栈的区域。

程序计数器其实就是一个指针,它指向了我们程序中下一句需要执行的指令,它也是内存区域中唯一一个不会出现OutOfMemoryError的区域,而且占用内存空间小到基本可以忽略不计。这个内存仅代表当前线程所执行的字节码的行号指示器,字节码解析器通过改变这个计数器的值选取下一条需要执行的字节码指令。

如果执行的是native方法,那这个指针就不工作了。

3.2 方法区

方法区主要的作用技术存放类的元数据信息,常量和静态变量···等。当它存储的信息过大时,会在无法满足内存分配时报错。

3.3 虚拟机栈和虚拟机堆

一句话便是:栈管运行,堆管存储。则虚拟机栈负责运行代码,而虚拟机堆负责存储数据。

3.3.1 虚拟机栈的概念

它是Java方法执行的内存模型。里面会对局部变量,动态链表,方法出口,栈的操作(入栈和出栈)进行存储,且线程独享。同时如果我们听到局部变量表,那也是在说虚拟机栈

public class Person{
    int a = 1;

    public void doSomething(){
        int b = 2;
    }
}
3.3.2 虚拟机栈存在的异常

如果线程请求的栈的深度大于虚拟机栈的最大深度,就会报 StackOverflowError (这种错误经常出现在递归中)。Java虚拟机也可以动态扩展,但随着扩展会不断地申请内存,当无法申请足够内存时就会报错 OutOfMemoryError。

3.3.3 虚拟机栈的生命周期

对于栈来说,不存在垃圾回收。只要程序运行结束,栈的空间自然就会释放了。栈的生命周期和所处的线程是一致的。

这里补充一句:8种基本类型的变量+对象的引用变量+实例方法都是在栈里面分配内存。

3.3.4 虚拟机栈的执行

我们经常说的栈帧数据,说白了在JVM中叫栈帧,放到Java中其实就是方法,它也是存放在栈中的。

栈中的数据都是以栈帧的格式存在,它是一个关于方法和运行期数据的数据集。比如我们执行一个方法a,就会对应产生一个栈帧A1,然后A1会被压入栈中。同理方法b会有一个B1,方法c会有一个C1,等到这个线程执行完毕后,栈会先弹出C1,后B1,A1。它是一个先进后出,后进先出原则。

3.3.5 局部变量的复用

局部变量表用于存放方法参数和方法内部所定义的局部变量。它的容量是以Slot为最小单位,一个slot可以存放32位以内的数据类型。

虚拟机通过索引定位的方式使用局部变量表,范围为[0,局部变量表的slot的数量]。方法中的参数就会按一定顺序排列在这个局部变量表中,至于怎么排的我们可以先不关心。而为了节省栈帧空间,这些slot是可以复用的,当方法执行位置超过了某个变量,那么这个变量的slot可以被其它变量复用。当然如果需要复用,那我们的垃圾回收自然就不会去动这些内存。

3.3.6 虚拟机堆的概念

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问题。

3.3.7 Eden年轻代的介绍

当我们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.3.8 如何判断一个对象需要被干掉

在这里插入图片描述

图中程序计数器、虚拟机栈、本地方法栈,3个区域随着线程的生存而生存的。内存分配和回收都是确定的。随着线程的结束内存自然就被回收了,因此不需要考虑垃圾回收的问题。而Java堆和方法区则不一样,各线程共享,内存的分配和回收都是动态的。因此垃圾收集器所关注的都是堆和方法这部分内存。

在进行回收前就要判断哪些对象还存活,哪些已经死去。下面介绍两个基础的计算方法

1.引用计数器计算:给对象添加一个引用计数器,每次引用这个对象时计数器加一,引用失效时减一,计数器等于0时就是不会再次使用的。不过这个方法有一种情况就是出现对象的循环引用时GC没法回收。

2.可达性分析计算:这是一种类似于二叉树的实现,将一系列的GC ROOTS作为起始的存活对象集,从这个节点往下搜索,搜索所走过的路径成为引用链,把能被该集合引用到的对象加入到集合中。搜索当一个对象到GC Roots没有使用任何引用链时,则说明该对象是不可用的。主流的商用程序语言,例如Java,C#等都是靠这招去判定对象是否存活的。

(了解一下即可)在Java语言汇总能作为GC Roots的对象分为以下几种:

  1. 虚拟机栈(栈帧中的本地方法表)中引用的对象(局部变量)

  2. 方法区中静态变量所引用的对象(静态变量)

  3. 方法区中常量引用的对象

  4. 本地方法栈(即native修饰的方法)中JNI引用的对象(JNI是Java虚拟机调用对应的C函数的方式,通过JNI函数也可以创建新的Java对象。且JNI对于对象的局部引用或者全局引用都会把它们指向的对象都标记为不可回收)

  5. 已启动的且未终止的Java线程

这种方法的优点是能够解决循环引用的问题,可它的实现需要耗费大量资源和时间,也需要GC(它的分析过程引用关系不能发生变化,所以需要停止所有进程)

3.3.9 如何宣告一个对象的真正死亡

首先必须要提到的是一个名叫 finalize() 的方法

finalize()是Object类的一个方法、一个对象的finalize()方法只会被系统自动调用一次,经过finalize()方法逃脱死亡的对象,第二次不会再调用。

补充一句:并不提倡在程序中调用finalize()来进行自救。建议忘掉Java程序中该方法的存在。因为它执行的时间不确定,甚至是否被执行也不确定(Java程序的不正常退出),而且运行代价高昂,无法保证各个对象的调用顺序(甚至有不同线程中调用)。在Java9中已经被标记为 deprecated ,且java.lang.ref.Cleaner(也就是强、软、弱、幻象引用的那一套)中已经逐步替换掉它,会比finalize来的更加的轻量及可靠。
  
在这里插入图片描述

判断一个对象的死亡至少需要两次标记

  1. 如果对象进行可达性分析之后没发现与GC Roots相连的引用链,那它将会第一次标记并且进行一次筛选。判断的条件是决定这个对象是否有必要执行finalize()方法。如果对象有必要执行finalize()方法,则被放入F-Queue队列中。

  2. GC对F-Queue队列中的对象进行二次标记。如果对象在finalize()方法中重新与引用链上的任何一个对象建立了关联,那么二次标记时则会将它移出“即将回收”集合。如果此时对象还没成功逃脱,那么只能被回收了。

如果确定对象已经死亡,我们又该如何回收这些垃圾呢

3.4 垃圾回收算法

不会非常详细的展开,常用的有标记清除,复制,标记整理和分代收集算法

3.4.1 标记清除算法

标记清除算法就是分为“标记”和“清除”两个阶段。标记出所有需要回收的对象,标记结束后统一回收。这个套路很简单,也存在不足,后续的算法都是根据这个基础来加以改进的。

其实它就是把已死亡的对象标记为空闲内存,然后记录在一个空闲列表中,当我们需要new一个对象时,内存管理模块会从空闲列表中寻找空闲的内存来分给新的对象。

不足的方面就是标记和清除的效率比较低下。且这种做法会让内存中的碎片非常多。这个导致了如果我们需要使用到较大的内存块时,无法分配到足够的连续内存。比如下图

此时可使用的内存块都是零零散散的,导致了刚刚提到的大内存对象问题

3.4.2 复制算法

为了解决效率问题,复制算法就出现了。它将可用内存按容量划分成两等分,每次只使用其中的一块。和survivor一样也是用from和to两个指针这样的玩法。fromPlace存满了,就把存活的对象copy到另一块toPlace上,然后交换指针的内容。这样就解决了碎片的问题。

这个算法的代价就是把内存缩水了,这样堆内存的使用效率就会变得十分低下了

在这里插入图片描述

不过它们分配的时候也不是按照1:1这样进行分配的,就类似于Eden和Survivor也不是等价分配是一个道理。

3.4.3 标记整理算法

复制算法在对象存活率高的时候会有一定的效率问题,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存
在这里插入图片描述

3.4.4 分代收集算法

这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。

说白了就是八仙过海各显神通,具体问题具体分析了而已。

3.5 (了解)各种各样的垃圾回收器

HotSpot VM中的垃圾回收器,以及适用场景

在这里插入图片描述

到jdk8为止,默认的垃圾收集器是Parallel Scavenge 和 Parallel Old

从jdk9开始,G1收集器成为默认的垃圾收集器
目前来看,G1回收器停顿时间最短而且没有明显缺点,非常适合Web应用。在jdk8中测试Web应用,堆内存6G,新生代4.5G的情况下,Parallel Scavenge 回收新生代停顿长达1.5秒。G1回收器回收同样大小的新生代只停顿0.2秒。

3.6 (了解)JVM的常用参数

JVM的参数非常之多,通过各种各样的搜索引擎也可以得知这些信息。

还有一些打印及CMS方面的参数,这里就不以一一列举了

四、关于JVM调优的一些方面

根据刚刚涉及的jvm的知识点,我们可以尝试对JVM进行调优,主要就是堆内存那块

所有线程共享数据区大小=新生代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m。所以java堆中增大年轻代后,将会减小年老代大小(因为老年代的清理是使用fullgc,所以老年代过小的话反而是会增多fullgc的)。此值对系统性能影响较大,Sun官方推荐配置为java堆的3/8。

4.1 调整最大堆内存和最小堆内存

-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方法
在这里插入图片描述

这里GC弹出了一个Allocation Failure分配失败,这个事情发生在PSYoungGen,也就是年轻代中

这时候申请到的内存为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了,此时又是把申请的内存释放掉的结果。

4.2 调整新生代和老年代的比值

-XX:NewRatio --- 新生代(eden+2*Survivor)和老年代(不包含永久区)的比值

例如:-XX:NewRatio=4,表示新生代:老年代=1:4,即新生代占整个堆的1/5。在Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。

4.3 调整Survivor区和Eden区的比值

-XX:SurvivorRatio(幸存代)--- 设置两个Survivor区和eden的比值

例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10

4.4 设置年轻代和老年代的大小

-XX:NewSize --- 设置年轻代大小

-XX:MaxNewSize --- 设置年轻代最大值

可以通过设置不同参数来测试不同的情况,反正最优解当然就是官方的Eden和Survivor的占比为8:1:1,然后在刚刚介绍这些参数的时候都已经附带了一些说明,感兴趣的也可以看看。反正最大堆内存和最小堆内存如果数值不同会导致多次的gc,需要注意。

4.5 小总结

根据实际事情调整新生代和幸存代的大小,官方推荐新生代占java堆的3/8,幸存代占新生代的1/10

在OOM时,记得Dump出堆,确保可以排查现场问题,通过下面命令你可以输出一个.dump文件,这个文件可以使用VisualVM或者Java自带的Java VisualVM工具。

-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=你要输出的日志路径

一般我们也可以通过编写脚本的方式来让OOM出现时给我们报个信,可以通过发送邮件或者重启程序等来解决。

4.6 永久区的设置

-XX:PermSize -XX:MaxPermSize

初始空间(默认为物理内存的1/64)和最大空间(默认为物理内存的1/4)。也就是说,jvm启动时,永久区一开始就占用了PermSize大小的空间,如果空间还不够,可以继续扩展,但是不能超过MaxPermSize,否则会OOM。

tips:如果堆空间没有用完也抛出了OOM,有可能是永久区导致的。堆空间实际占用非常少,但是永久区溢出 一样抛出OOM。

4.7 JVM的栈参数调优

4.7.1 调整每个线程栈空间的大小

可以通过-Xss:调整每个线程栈空间的大小

JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右

4.7.2 设置线程栈的大小
-XXThreadStackSize:
    设置线程栈的大小(0 means use default stack size)

这些参数都是可以通过自己编写程序去简单测试的,这里碍于篇幅问题就不再提供demo了

4.8 (可以直接跳过了)JVM其他参数介绍

形形色色的参数很多,就不会说把所有都扯个遍了,因为大家其实也不会说一定要去深究到底。

4.8.1 设置内存页的大小
-XXThreadStackSize:
    设置内存页的大小,不可设置过大,会影响Perm的大小
4.8.2 设置原始类型的快速优化
-XX:+UseFastAccessorMethods:
    设置原始类型的快速优化
4.8.3 设置关闭手动GC
-XX:+DisableExplicitGC:
    设置关闭System.gc()(这个参数需要严格的测试)
4.8.4 设置垃圾最大年龄
-XX:MaxTenuringThreshold
    设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代.
        对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,
    则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,
    增加在年轻代即被回收的概率。该参数只有在串行GC时才有效.
4.8.5 加快编译速度
-XX:+AggressiveOpts
加快编译速度
#### 4.8.6 改善锁机制性能
-XX:+UseBiasedLocking
4.8.7 禁用垃圾回收
-Xnoclassgc
4.8.8 设置堆空间存活时间
-XX:SoftRefLRUPolicyMSPerMB
    设置每兆堆空闲空间中SoftReference的存活时间,默认值是1s。
4.8.9 设置对象直接分配在老年代
-XX:PretenureSizeThreshold
    设置对象超过多大时直接在老年代分配,默认值是0。
4.8.10 设置TLAB占eden区的比例
-XX:TLABWasteTargetPercent
    设置TLAB占eden区的百分比,默认值是1% 。 
4.8.11设置是否优先YGC
-XX:+CollectGen0First
    设置FullGC时是否先YGC,默认值是false。

finally

真的扯了很久这东西,参考了多方的资料,有极客时间的《深入拆解虚拟机》和《Java核心技术面试精讲》,也有百度,也有自己在学习的一些线上课程的总结。希望对你有所帮助,谢谢。

通讯模块_三菱IQ-R PLC串口通讯模块RJ71C24进行Modbus-RTU通讯 在日常的选型中,很多电气工程师咨询我,三菱iQ-R系列有没有Modbus通讯模块,我在这里可以肯定的回复您,这个肯定是有的;我们共有三种可以选择分别是RJ71C24、RJ71C24-R2、RJ71C24-R4。RJ71C24系列支持MC协议通信、通信协议通信、无顺序协议通信、双向协议通信全、MODBUS通信。当我帮助他们选型之后,他们在使用的时候,又是一头雾水。那么今天我抽一些时间讲解一RJ71... 阅读详情

相关推荐

Zynq 双核AMP 通信

4、配置离线编译(这里不讲),配置linux的内核地址空间(0x4000_0000改为0x2000_0000),以及单核启动。CPU1设置为0x2000_0000~0x3FFF_FFFF(也可以小于0x3FFF_FFFF)如果禁用CPU1的L2 cache,在CPU1的bsp里面添加编译时候所需要的宏。3、配置工程,将vivado产生的.xsa文件复制到amp_com项目目录下。9、使用petalinux-package --boot 打包。一、vivado工程。5、编译u-boot。

y183128184的博客 1088

JVM(Java虚拟机)-史上最全、最详细JVM笔记

JVM的中文名称叫Java虚拟机,它是由软件技术模拟出计算机运行的一个虚拟的计算机。 JVM也充当着一个翻译官的角色,我们编写出的Java程序,是不能够被操作系统所直接识别的,这时候JVM的作用就体现出来了,它负责把我们的程序翻译给系统“听”,告诉它我们的程序需要做什么操作。

weixin_56846554的博客 3万+

告别纯文本RAG:用Dify 1.11.0多模态知识库,5步搭建一个能“读图”的智能客服助手

本文详细介绍了如何利用Dify 1.11.0多模态知识库构建智能视觉客服助手,通过5个步骤实现图片内容理解与专业回复。从准备多模态素材到配置知识库节点,再到设计工作流逻辑和性能优化,帮助开发者快速搭建高效客服系统,显著提升用户体验和问题解决效率。

weixin_30832143的博客 452

【JVM】JVM内存模型详解

JVM屏蔽了与操作系统平台相关的信息,使得Java程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可在多种平台上不加修改的运行,这也是Java能够“一次编译,到处运行的”原因。方法区存储的内容有:类型信息(比如类全称、父类全称)、域信息(域名称、域修饰符private等)、方法信息(方法名称、方法修饰符、返回类型等)、字面量(字面量包括文本字符串、八种基本类型的值 、被声明为final的常量等)。Java虚拟机栈是当前线程在执行方法时存储所需的数据、指令、返回地址的一种栈结构(先进后出)。

Money、坤的博客 1万+

HFSS实战:手把手教你设计微带发夹线滤波器(附完整参数计算流程)

本文详细介绍了使用HFSS设计微带发夹线滤波器的完整流程,包括参数计算、HFSS建模和优化策略。通过实战案例,帮助读者掌握微波滤波器设计的核心技巧,特别是奇偶模阻抗计算和HFSS仿真中的关键设置,适用于通信系统设计和射频工程应用。

weixin_29251567的博客 441

可变形卷积(Deformable Conv)原理解析与torch代码实现

图6为可变形卷积示意图。图1所示为普通卷积在输入特征图上进行卷积计算的过程,卷积核大小为3*3,在输入特征图尺寸为7*7上进行卷积,将卷积核权重与输入特征图对应位置元素相乘并求和得到输出特征图元素,按一定方式滑动窗口就能计算得到整张输出特征图。图6所示,在input feature map上普通卷积操作对应卷积采样区域是一个卷积核大小的正方形(绿框),而可变形卷积对应的卷积采样区域为一些蓝框表示的点,这就是可变形卷积与普通卷积的区别。先来一张图感受一下,图3为标准卷积与可变形卷积的卷积示例。

panghuzhenbang的博客 5万+

JVM(java虚拟机)

一、了解JVM 1、什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟计算机功能来实现的,JVM屏蔽了与具体操作系统平台相关的信息,Java程序只需生成在Java虚拟机上运行的字节码,就可以在多种平台上不加修改的运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。 2、JRE/JDK/JVM是什么关系 JRE(Java Runtime Environment):是Java运行环境,所有

weixin_43771403的博客 2万+

工业质检新利器:手把手教你用AD-DINOv3实现零样本缺陷检测(附MVTec AD实战)

本文详细介绍了AD-DINOv3框架在工业质检中的应用,通过零样本异常检测技术解决传统方法的标注成本高和泛化能力弱问题。文章包含技术原理解析、MVTec AD数据集实战部署、模型训练流程及工业部署优化技巧,帮助读者快速掌握这一创新技术。

seed的博客 765

JVM原理-超详细总结

JVM概念 JVM是java的核心和基础在java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,可以在上面执行java的字节码程序。java编译器只要面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。 JVM的生命周期: JVM的诞生:当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(Strin

weixin_45105261的博客 3万+

数字IC行业笔试面试全攻略

数字IC行业迎来快速发展,国产替代加速,技术创新活跃,应用领域持续扩展,但人才缺口扩大。职业发展路径清晰,从初级到首席工程师/架构师的技术路线,或转向管理岗位。薪资待遇优厚,应届生起薪25-35W,5-10年经验可达70-110W,架构师年薪超百万。薪资受公司平台、技术方向、学历背景、项目经验和地域因素影响。AI芯片、汽车电子等热门方向溢价明显,成功流片经验尤其重要。

2301_79143213的博客 761

智能手表PCB布局:PPG与IMU传感器噪声隔离与应力解耦设计

在可穿戴设备硬件开发中,PCB布局已超越传统布线范畴,成为决定信号完整性、电磁兼容性与机械可靠性的核心环节。心率传感器(PPG)依赖微伏级光电容积脉搏波信号,极易受数字开关噪声与电源纹波干扰;姿态传感器(IMU)则对PCB弯曲形变引发的机械应力高度敏感。其布局需融合电磁场建模、热-力-电多物理协同及高密度互连约束。关键技术价值体现在噪声路径切断、地弹抑制、应力方向解耦与电源域隔离等工程实践。典型应用场景涵盖智能手表、医疗级贴片设备及微型运动传感终端。本文聚焦PPG与IMU两大模组在STM32L4平台上的实操

weixin_32869687的博客 175

一步到位!彻底了解Java中JDK、JRE、JVM分别是什么及它们之间的联系

首先,next()一定要读取到有效字符后才可以结束输入,对输入有效字符之前遇到的空格键、Tab键或Enter键等结束符,next()方法会自动将其去掉,只有在输入有效字符之后,next()方法才将其后输入的空格键、Tab键或Enter键等视为分隔符或结束符。简单地说,next()查找并返回来自此扫描器的下一个完整标记。完整标记的前后是与分隔模式匹配的输入信息,所以next方法不能得到带空格的字符串...

weixin_45797022的博客 3940

Fastgpt本地使用Docker Compose 快速部署

修改文件,并执行再执行d重起容器。具体配置,参考配置详解。可以查看日志,在启动容器后,第一次请求网页,会进行配置文件读取,可以看看有没有读取成功以及有无错误日志。进入 FastGPT 容器,可以通过ls data查看目录下是否成功挂载文件。可通过查看配置文件。可能不生效的原因挂载目录不正确配置文件不正确,日志中会提示invalid json,配置文件需要是标准的 JSON 文件。

洛阳泰山的博客 1634

你对 JVM 的运行原理了解多少?,JVM运行原理全面讲解(附带视频解析)

前言 为什么 Java 研发系统需要 JVM? 你对 JVM 的运行原理了解多少? 我们写的 Java 代码到底是如何运行起来的? 视频参考JVM合集/零基础学习JVM、GC基础知识、GC算法、JVM的垃圾回收器、面试JVM实战、 实战调优​​​​​​​ 想要弄清楚这些问题,我们接下来从这三个维度去探讨: JVM 和操作系统的关系? JVM、JRE 、JDK的关系? JVM 虚拟机规范和 Java 语言规范的关系? ...

qq13321123的博客 634

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

想要学会JVM调优,先掌握JVM内存模型和JVM运行原理

想要学会JVM调优,先掌握JVM内存模型和JVM运行原理: 在Java中,JVM内存模型主要分为堆、程序计数器、方法区、虚拟机栈和本地方法栈。 JVM在很大程度上减轻了Java开发人员投入到对象生命周期的管理精力。 在使用对象的时候,JVM会自动分配内存给对象,在不使用的时候,垃圾回收器会自动回收 对象,释放占用的内存。

qq_30713721的博客 1492

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

是时候深入了解JVM了

刚开始接触Java时,对JVM是无知的,确实,是无感知的。因为Java的设计者,为了降低语言的复杂度,屏蔽实现细节,将很多背后的操作"隐藏"了。 在很长时间里,不需要了解Java虚拟机,也能写出可以运行的代码,甚至完美地运行一个Java程序。什么垃圾回收机制,类加载机制统统都抛之脑后,好像也没人说你不懂Java。 直到…一次OOM的出现。 解决Bug的能力与手段,决定了你想成为程序员还是好程序员。...

随-记的专栏 1057

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

深入了解JVM的底层原理

引言:什么是JVM? JVM在整个jdk(java 运行环境)中处于最底层,负责与操作系统的交互,用来屏蔽操作系统环境,提供一个完整的Java运行环境,因此也就虚拟计算机. 操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境. 1.创建JVM装载环境和配置 2.装载JVM.dll 3.初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例 ...

飞翔的宇宙 2万+

什么是JVM?深入解析JVM原理!

一、JVM是什么? JVM是Java Virtual Machine(Java虚拟机)的缩写,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。JVM屏蔽了与操作系统平台相关的信息,使得Java程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可在多种平台上不加修改的运行,这也是Java能够“一次编译,到处运行的”原因。 二、JRE、JDK和JVM的关系 JRE(Java Runtime Environment

GD_YuZhe的博客 1564

快速了解JVM结构和工作原理

快速了解JVM结构和工作原理 什么是Java虚拟机(JVM)? Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程,相当于一个普通应用程序。Java 源文件被编译成能被 Java 虚拟机执行的字节码文件。 java要在应用平台上执行,需要先安装执行虚拟机,java虚拟机屏蔽了平台操作系统的底层硬件信息,抽取整理了各平台公共的处理硬件的接口提供给开发用户使用,java开发用户只要基于JVM...

ZhiXiongWu的博客 480

[Java] JAVA和JVM运行原理

个人博客导航页(点击右侧链接即可打开个人博客):大牛带你入门技术栈 JVM是java的核心和基础,在java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,可以在上面执行java的字节码程序。 编译器负责把Java程序转成Class文件,方便Jvm来读取它. Jvm是java虚拟机,其实它就是解释器,把Class文件中的命令转成某种...

JKX_geek的博客 1949

先学JAVA还是JVM_关于JVM学还是不学

前言说起JVM大多数给予的回应是JAVA虚拟机,是JAVA语言跨平台的武器。而且我们在开发中遇到和JVM有关的问题基本上就是OutOfMemoryError(内存溢出),然后一般解决方法就是找到相关的代码进行优化或者调整-Xms(初始内存)和-Xmx(最大能够使用内存大小)。尤其现在很多语言、框架概括来说就是以特有的方式抽象出来,隐藏具体的细节实现。那关于底层的这块学还是不学呢?误区其实很多人在谈...

weixin_36410316的博客 995

JVM是什么?Java程序为啥需要运行在JVM中?

JVM 代表Java 虚拟机,本质上是一个虚拟机或运行时环境,允许 Java 程序在多个设备和操作系统上运行和操作。Java 程序不能在没有 JVM 支持的机器上运行,它是 Java 运行时环境 (JRE) 的一个组成部分。JVM 具有许多共同运行 Java 程序的规范。它本质上是平台相关的,即每个操作系统都有不同的JVM。规范实现实例规范是正式描述 JVM 实现所需内容的文档,拥有单一规范可确保所有实现都是可互操作的。JVM 实现是满足 JVM 规范要求的计算机程序。

m0_73311735的博客 1251

通过java程序运行过程来了解JVM原理

今天这篇文章我们具体分析一下java程序运行起来之中所包含的具体细节,通过这些细节带我们窥探JVM实现的原理。 一、前端编译 使用javac命令将.java程序编译为JVM可解析的.class字节码文件,java作为一种解释型编程语言(当然这么说带有片面性,jvm自带的即时编译器同样可以将字节码直接编译为机器码运行,但是这里我们就不分析即时编译器是如何运行的了),负责将字节码翻译为机器码运行,...

派大星的博客 384

java开发?你就得去了解JVM!

JAVA程序员,三年是个坎,如果过了三年你还没有去研究JVM的话,那么你这个程序员只能是板砖的工具了。下面来个JVM的解析可好? JVM是Java Virtual Machine(Java虚拟机)的缩写,也就是指的JVM虚拟机,属于是一种虚构出来的计算机,在我们实际的电脑上来进行模拟各种计算机的功能的这么个东西。 因为有了JVM的存在,搞JAVA的不再需要去关心什么时候去释放内存,也不会像C++程序员那样为了一点点内存而惆怅,对就是你,JVM虚拟机帮你把这些东西都完成了,那么我们来说说JAVA的JVM吧!

Java_Yhua的博客 304

关于JVM 你该了解的一切

类加载过程和Java (JVM)内存模型以及调优

极客星云-优快云博客 945

了解JVM

文章目录JVM是什么jvm和Java的关系 JVM是什么 虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。 所谓的java虚拟机,就是一台虚拟的机器。它是一款软件,用来执行一系列虚拟计算机指令,大体上虚拟机可以分为系统虚拟机和程序虚拟机。vis

weixin_40693638的博客 220
上一篇: getline()函数yyds!震惊,取代了cin的函数竟是它!!!
下一篇: C语言是用什么语言写的?鸡与蛋的关系
Andy in boots
博客等级 码龄7年 73粉丝 69原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值