不懂JVM看完这一篇文章你就会非常懂了,文章很长,非常详细!!!首先先了解电脑是二进制的系统,他只认识 01010101比如我们经常要编写 HelloWord.java 电脑是怎么认识运行的HelloWord.java是我们程序员编写的,我们人可以认识,但是电脑不认识Java文件编译的过程因此就需要编译:(这是一个大概的观念 抽象画的概念)这个夸平台是中间语言(JVM)实现的夸平台java有JVM从软件层面屏蔽了底层硬件、指令层面的细节让他兼容各种系统难道 C 和 C++ 不能夸平台吗 其实也可以C和C++需要在编译器层面去兼容不同操作系统的不同层面,写过C和C++的就知道不同操作系统的有些代码是不一样看Java官方的图片,Jdk中包括了Jre,Jre中包括了JVMJvm在倒数第二层 由他可以在(最后一层的)各种平台上运行Jre大部分都是 C 和 C++ 语言编写的,他是我们在编译java时所需要的基础的类库Jdk还包括了一些Jre之外的东西 ,就是这些东西帮我们编译Java代码的, 还有就是监控Jvm的一些工具为什么要学习Jvm,学习Jvm可以干什么首先先想:为什么Java可以霸占企业级开发那么多年 因为:内存管理那就有些人可能又会要说了,Jvm都做完了这些操作,为什么我们还要学习,学习个屁啊这就好像一个人一样,我一般情况吃什么从来不用考虑进入了身体那一个部位,可是总有一天,假如吃了不该吃的也是要进医院的注释:JVM就是Java虚拟机,Java虚拟机就是JVM什么是运行时数据区(就是我们java运行时的东西是放在那里的)总结:也可以把它叫做线程计数器例子:在java中最小的执行单位是线程,线程是要执行指令的,执行的指令最终操作的就是我们的电脑,就是 CPU。
在CPU上面去运行,有个非常不稳定的因素,叫做调度策略,这个调度策略是时基于时间片的,也就是当前的这一纳秒是分配给那个指令的。
假如:线程A在看直播突然,线程B来了一个视频电话,就会抢夺线程A的时间片,就会打断了线程A,线程A就会挂起然后,视频电话结束,这时线程A究竟该干什么?(线程是最小的执行单位,他不具备记忆功能,他只负责去干,那这个记忆就由:程序计数器来记录)解释:每虚拟机栈中是有单位的,单位就是栈帧,一个方法一个栈帧。
一个栈帧中他又要存储,局部变量,操作数栈,动态链接,出口等。
解析栈帧:思考:.一个方法调用另一个方法,会创建很多栈帧吗?答:会创建。
如果一个栈中有动态链接调用别的方法,就会去创建新的栈帧,栈中是由顺序的,一个栈帧调用另一个栈帧,另一个栈帧就会排在调用者下面栈指向堆是什么意思?栈指向堆是什么意思,就是栈中要使用成员变量怎么办,栈中不会存储成员变量,只会存储一个应用地址,堆中的数据等下讲递归的调用自己会创建很多栈帧吗?递归的话也会创建多个栈帧,就是一直排下去上面已经讲了运行时数据区,这里就差几个小组件了直接内存与堆内存的区别:直接内存申请空间耗费很高的性能,堆内存申请空间耗费比较低直接内存的IO读写的性能要优于堆内存,在多次读写操作的情况相差非常明显代码示例:(报错修改time 值)测试结果:代码来源:「猕猴桃0303」链接为:
https:--blog.csdn.net-leaf_0303-article-details-78961936虚拟机核心的组件就是执行引擎,它负责执行虚拟机的字节码,一般户先进行编译成机器码后执行。
-虚拟机-是一个相对于-物理机-的概念,虚拟机的字节码是不能直接在物理机上运行的,需要JVM字节码执行引擎编译成机器码后才可在物理机上执行。
程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。
垃圾收集系统是Java的核心,也是不可少的,Java有一套自己进行垃圾清理的机制,开发人员无需手工清理垃圾回收机制简称GCGC主要用于Java堆的管理。
Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。
程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。
GC是不定时去堆内存中清理不可达对象。
不可达的对象并不会马上就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行清楚那个对象,即使程序员能明确地判断出有一块内存已经无用了,是应该回收的,程序员也不能强制垃圾收集器回收该内存块。
程序员唯一能做的就是通过调用System.gc 方法来"建议"执行垃圾收集器,但是他是否执行,什么时候执行却都是不可知的。
这也是垃圾收集器的最主要的缺点。
当然相对于它给程序员带来的巨大方便性而言,这个缺点是瑕不掩瑜的。
手动执行GC:代码示例先不要管为什么要分代,后面有例子其实主要原因就是可以根据各个年代的特点进行对象分区存储,更便于回收,采用最适当的收集算法:新生代又分为Eden和Survivor (From与To,这里简称一个区)两个区。
加上老年代就这三个区。
数据会首先分配到Eden区当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。
当Eden没有足够空间的时候就会触发jvm发起一次Minor GC,。
如果对象经过一次Minor-GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空间当中。
并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代中了,当然晋升老年代的年龄是可以设置的。
Minor GC 触发条件一般为:Major GC和Full GC 触发条件一般为:Major GC通常是跟full GC是等价的引用计数法的优点:引用计数法的缺点:引用计数法的应用场景:那些可以作为GC Roots 的对象:可达性算法的优点:可达性算法的优点:可达性算法的应用场景:每个对象在创建的时候,就给这个对象绑定一个计数器。
每当有一个引用指向该对象时,计数器加一;每当有一个指向它的引用被删除时,计数器减一。
这样,当没有引用指向该对象时,计数器为0就代表该对象死亡,这时就应该对这个对象进行垃圾回收操作。
引用计数法的优点:引用计数法的缺点:引用计数法的应用场景:为每个对象存储一个标记位,记录对象的状态(活着或是死亡)。
分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行 GC 操作。
标记清除算法的优点:标记清除算法的缺点:标记清除算法的应用场景:标记清除算法和标记压缩算法非常相同,但是标记压缩算法在标记清除算法之上解决内存碎片化(有些人叫"标记整理算法"为"标记压缩算法")标记-整理法是标记-清除法的一个改进版。
同样,在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。
这样就达到了标记-整理的目的。
标记–整理算法优点:标记–整理算法缺点:标记–整理算法应用场景:该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。
这个算法与标记-整理算法的区别在于,该算法不是在同一个区域复制,而是将所有存活的对象复制到另一个区域内。
复制算法的优点:复制算法的缺点::复制算法的应用场景:这种算法,根据对象的存活周期的不同将内存划分成几块,新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
可以用抓重点的思路来理解这个算法。
新生代对象朝生夕死,对象数量多,只要重点扫描这个区域,那么就可以大大提高垃圾收集的效率。
另外老年代对象存储久,无需经常扫描老年代,避免扫描导致的开销。
新生代老年代图中展示了7种不同分代的收集器:Serial、ParNew、Parallel Scavenge、CMS、Serial Old、Parallel Old、G1而它们所处区域,则表明其是属于新生代还是老年代的收集器:两个收集器间有连线,表明它们可以搭配使用:垃圾回收器工作区域回收算法工作线程用户线程并行描述Serial新生带复制算法单线程否Client模式下默认新生代收集器。
简单高效ParNew新生带复制算法多线程否Serial的多线程版本,Server模式下首选, 可搭配CMS的新生代收集器Parallel Scavenge新生带复制算法多线程否目标是达到可控制的吞吐量Serial Old老年带标记-整理单线程否Serial老年代版本,给Client模式下的虚拟机使用Parallel Old老年带标记-整理多线程否Parallel Scavenge老年代版本,吞吐量优先CMS老年带标记-清楚多线程是追求最短回收停顿时间G1新生带 + 老年带标记-整理 + 复制算法多线程是JDK1.9默认垃圾收集器特点:使用方式:特点:使用方式:特点:使用方式:特点:使用方式:特点:使用方式:特点:使用方式:特点:使用方式:1、单个项目的应用2、全局的配置1、配置单个项目点击绿色图标右边的小箭头在点击:Run Configurations ->VM arguments2、配置全局JVM参数修改Eclipse的配置文件,在eclipse安装目录下的:eclipse.ini文件war肯定是部署在Tomcat上的,那就是修改Tomcat的JVM参数1、在Windows下就是在文件-bin-catalina.bat,增加如下设置:JAVA_OPTS(JAVA_OPTS,就是用来设置 JVM 相关运行参数的变量)2、Linux要在tomcat 的bin 下的catalina.sh 文件里添加Jar包简单,一般都是SpringBoot项目打成Jar包来运行注释:其实最主要的还是服务器要好,你硬件都跟不上,软件再好都没用注释:老年代GC很慢,新生代没啥事注释:默认的JVM堆大小好像是电脑实际内存的四分之一左右,我的电脑是8G的运行内存程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。
如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。
Jvm执行class文件总结就是:初始化是为类的静态变量赋予正确的初始值它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre-lib-rt.jar里所有的class,由C++实现,不是ClassLoader子类)。
由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载<JAVA_HOME>-lib-ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。
被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。
程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。
如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。
由Java语言实现,父类加载器为ExtClassLoader。
(Java虚拟机采用的是双亲委派模式即把请求交由父类处理,它一种任务委派模式,)类加载器加载Class大致要经过如下8个步骤:双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。
双亲委派机制的优势:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
我们进一步了解类加载器间的关系(并非指继承关系),主要可以分为以下4点启动类加载器,由C++实现,没有父类。
拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader自定义类加载器,父类加载器肯定为AppClassLoader。
开发大型 Java 应用程序的过程中难免遇到内存泄露、性能瓶颈等问题,比如文件、网络、数据库的连接未释放,未优化的算法等。
随着应用程序的持续运行,可能会造成整个系统运行效率下降,严重的则会造成系统崩溃。
为了找出程序中隐藏的这些问题,在项目开发后期往往会使用性能分析工具来对应用程序的性能进行分析和优化。
VisualVM 是一款免费的,集成了多个 JDK 命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优。
这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时,它能自动选择更快更轻量级的技术尽量减少性能分析对应用程序造成的影响,提高性能分析的精度。
他作为Oracle JDK 的一部分,位于 JDK 根目录的 bin 文件夹下。
VisualVM 自身要在 JDK6 以上的版本上运行,但是它能够监控 JDK1.4 以上版本的应用程序位于 JDK 根目录的 bin 文件夹下的jvisualvm.exe注:我的JDK11没有,不知道为什么,我jdk8就找的到此工具我这本地有了好几个进程,这是我IDea工具的我运行一个SpringBoot项目此时就开始监控了省略:::从Java 5开始 引入了 JConsole。
JConsole 是一个内置 Java 性能分析器,可以从命令行或在 GUI shell 中运行。
您可以轻松地使用 JConsole(或者,它更高端的 -近亲- VisualVM )来监控 Java 应用程序性能和跟踪 Java 中的代码。
点击jdk-bin 目录下面的jconsole.exe 即可启动,然后会自动自动搜索本机运行的所有虚拟机进程。
选择其中一个进程可开始进行监控