JVM随手笔记

84 阅读16分钟

1.我们开发人员编写Java代码是怎么让电脑认识的

首先,电脑是个二进制系统,只识别010101

我们编写的HelloWorld.java电脑是怎么运行的呢

  • 首先程序员编写.java文件

  • 由javac编译成字节码文件.class

  • 在由JVM编译成电脑认识的文件(对于电脑系统来说 文件代表一切)

相当于JVM来加载Class文件,并将其转换为二进制文件,计算机来进行识别

2.为什么说Java是跨平台的语言

这个跨平台是JVM实现的跨平台

java有JVM从软件层面屏蔽了底层硬件、指令层面的细节让他兼容各个系统

3.JDK JRE JVM的区别

4. 为什么学习JVM

  • jvm不需要进行内存管理,因为有垃圾回收机制

  • 内存出现问题了,出现内存溢出,内存泄漏问题怎么办,这就是学习的原因

科普完了,正餐

1.JVM运行时数据区

什么是运行时数据区(就是我们java运行时的东西是放在那里的)

小结:jvm的主要构成部分

  • 类加载器

  • 方法区

  • 本地方法栈

  • 程序计数器

  • 虚拟机栈

两个栈,一个虚拟机栈,一个本地方法栈,本地方法栈中主要是C++的一些方法

方法区(Method Area)

  • 方法区是所有线程共享的内存区域,它用于存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

  • 他有个别名叫Non-Heap(非堆),当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常

  • 顾名思义,方法区方法区,就是加载一些方法有关的静态变量啊什么的东西类信息啊代码的一些东西

Java堆(java Head)

  • java堆是java虚拟机中内存最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例

  • 所有的对象实例以及数组都要在堆上进行分配

  • java堆是垃圾收集器管理的主要区域,因此也称为GC堆

  • 从内存回收角度java堆可分为:新生代和老生代

  • 从内存分配的角度看,线程共享的java堆中可能划分出多个线程私有的分配缓冲区

  • 无论怎么划分,都与存放的内容无关,无论哪个取悦,存储的都是对象实例,进一步的划分都是问了更好的回收内存,或更快的分配内存

  • 根据java虚拟机规范的规定,java堆可以处在物理上的不连续的内存空间中,当前主流的虚拟机都是可扩展的(-Xmx和-Xms控制)如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常

  • 堆中存放对象,堆中存放对象,堆中存放对象,重要的事情说3遍!!!!

程序计数器(Program Counter Register)

  • 程序计数器是一块较小的空间,他可以看做是:保存当前线程所正在执行的字节码指令的地址(行号)

  • 由于Java虚拟机的多线程是通过线程轮流切换并范培处理器执行时间方式来实现的一个处理器都只会执行一条线程中的指令,因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间的计数器互不影响,独立存储,称之为线程私有内存,程序计数器内存取悦是虚拟机中唯一没有规定OutOfMemoryError情况的区域

  • 程序计数器程序计数器,顾名思义,记录当前线程执行到哪里了,下次继续执行

虚拟机栈(Java Virtual Machine Stacks)

  • java虚拟机是线程私有的,他的生命周期和线程相同

  • 虚拟机栈描述的是java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接、方法出口等信息

  • 每个虚拟机栈中都是有单位的,单位就是栈帧,一个方法一个栈帧,一个栈帧中他又要存储,局部变量,操作数栈,动态链接,出口等

    • 局部变量表:用来存储我们临时的8个基本数据类型,对象引用地址

    • 操作数栈:操作数栈就是用来操作的,他在一开始的时候就会进行操作,读取我们的代码,进行计算后在放入局部变量表中

    • 动态链接:假如我方法中,有个service.add()方法,要链接到别的方法中去,这就是动态链接,存储链接的地方

    • 出口:出口正常的话就是return 不正常的话就会抛出异常

一个方法调用另一个方法,会创建很多栈帧吗

会创建,至少2个,一个方法一个栈帧

栈指向堆是什么意思

就是栈中要使用成员变量怎么办,栈中不会存储成员变量,只会存储一个应用地址,堆中的数据等下再说

递归调用自己会创建很多栈帧吗

递归的话会一直排下去

本地方法栈(Native Method Stack)

  • 本地方法栈很好理解,他和栈很像,只不过带上了native关键字的栈字

  • 他是虚拟机栈执行java方法的服务

  • native关键字的方法是看不到的,源码是C和C++写的

  • 简而言之言而总之,java虚拟机中本地方法栈就是native修饰的用C和C++来写的,加载java方法的一些东西

2.java内存结构

直接内存不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,但是既然是内存,肯定还是受本机总内存大小以及处理器寻址空间的限制

直接内存和堆内存的区别

直接内存申请空间耗费很高的性能,堆内存申请空间耗费比较低

直接内存的IO读写的性能要优于堆内存,在多次读写操作的情况相差非常明显

JVM字节码执行引擎

虚拟机核心的组件就是执行引擎,他负责执行虚拟机的字节码,一般进行编译成机器码后执行,虚拟机相当于一个物理机的概念,虚拟机的字节码是不能直接在物理机上运行的,需要jvm字节码执行引擎编译成机器码后才可在物理机上执行

垃圾收集系统

程序在运行过程中,会产生大量的内存垃圾,为了确保程序运行时性能,java虚拟机在程序运行的过程中不断的进行自动垃圾回收

垃圾收集系统是java的核心,也是不可少的,java有一套自己进行垃圾清理的机制,开发人员无需手工清理

JVM的垃圾回收机制

垃圾回收机制简称GC,GC主要作用于java堆的管理,java中的堆是jvm所管理的最大的一块内存空间,主要用于存放各种类的实例对象

什么是垃圾回收机制

程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了他们,对程序而言他们已经死亡了)为了确保程勋运行时的性能,java虚拟机在程序运行的过程中不断的进行垃圾回收

GC是不定时去堆内存中清理不可达的对象,不可达的对象并不会马上就会直接回收,垃圾收集器在一个java程序中的执行是自动的,不能强制执行清楚那个对象,即时程序员能明确判断出一块内存已经无用了,是应该回收的,程序员也不嗯呢该前哪个组织垃圾收集器回收该内存块,程序员唯一能做的就是通过调用syatem.gc方法来建议执行垃圾收集器,但是他是否执行,什么时候执行却是不可知的,这也是垃圾收集器最主要的缺点,当然相当于他给程序员带来的巨大方便性而言,这个缺点小意思

finalize方法的作用

finalize()方法是在每次执行GC操作之前时会调用的方法,可以用他做必要的清理工作

finalize,子类覆盖finalize方法以整理系统资源或者执行其他清理工作,是在垃圾回收之前对这个对象的调用。

新生代、老年代、永久代的区别

java中堆是JVM所管理的最大一块的内存空间,主要用于存放各种类的实例对象

堆被划分为两种不同的区域:新生代和老年代

  • 老年代就一个区域,新生代分为3个区域

  • 这样划分的目的是为了使JVM能够更好的管理堆内存中的对象,包括内存的分配及回收

  • 默认的,新生代与老年代的比例是1:2,即新生代占三分之一的堆空间大小,老年代占三分之二的堆空间大小

  • 永久代就是JVM的方法区,在这里都是放着一些被虚拟机加载的类信息静态变量常量等数据,这个区中的东西比老年代和新生代更不容易回收

为什么这样分代

  • 新生代中,每次垃圾收集时都会发现大批对象死去,只有少量对象存活,便采用了复制算法,只需要付出少量存货对象的复制成本就可以完成收集

  • 而老年代中因为对象存活率高,没有额外空间对他进行分配担保,就必须采用标记-清理或者标记-整理算法

垃圾回收机制策略(也称GC算法)

1.引用计数算法

每个对象在创建的时候,就给这个对象绑定一个计数器,每当有一个引用指向该对象时,计数器+1,每当有一个指向他的引用被删除时,计数器-1,这样,当没有引用指向该对象时,计数器为0时就代表该对象死亡,这时就应该对这个对象进行垃圾回收操作。简言之:就是绑定一个计数器,有了加,没了减,是0说明没人用,就可以进行删除。

优点:实现简单,判定效率高

缺点:主流的java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是他很难解决对象之间的相互循环引用问题

2.标记-清除算法

为每个对象存储一个标记位,记录对象的状态(活着还是死了)分为两个阶段,一个是标记阶段,这个阶段内,每个对象更新标记位,检查对象是否死亡,第二个阶段是清楚阶段,该阶段对死亡的对象进行清楚,执行GC操作

优点:

  • 可以解决循环引用的问题

  • 必要时才回收

缺点

  • 回收时,应用需要挂起

  • 标记和清楚的效率不高,尤其是要扫描的对象比较多的时候

  • 会造成内存碎片,会导致明明有内存空间,但是由于不连续,申请稍微大一些的对象无法做到

  • 一般用于老年代,因为老年代啊的对象声明周期时间比较长

3.标记整理算法

标记整理算法和标记压缩算法非常相同,但是标记压缩算法在标记清除算法之上解决内存碎片化。

4.复制算法

该算法将内存平均分成两部分,然后每次只使用其中一部分,当这本分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去,这个算法和标记整理算法的区别在于,该算法不是在同一区域复制,而是将所有存活的对象复制到另一个区域内。

  • 优点:在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法中导致的引用更新问题

  • 缺点:内存浪费

5,分代算法

这种算法,根据对象的存活周期的不同将内存划分成几块,新生代和老年代,这样就可以根据各个年代的特点采用最合适的收集算法,可以用抓重点的思路来理解这个算法,新生代对象的朝生夕死,对象数量多,只要重点扫描这个区域,那么就可以大大提高垃圾收集的效率,另外老年代对象存储久,无需经常扫描老年代,避免扫描导致的开销

新生代

  • 在新生代,每次垃圾收集器都发现有大批对象死去,只有少量存活,采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集

  • 在老年代,而老年代中因为对象存活率高,没有额外空间对他进行分配担保,就必须标记清除法或者标记整理算法进行回收

3.垃圾收集器

垃圾收集器是垃圾回收算法(引用计数法,标记清除算法,标记整理算法,复制算法)的整体实现,不同的收集器,不同版本的jvm所提供的垃圾收集器可能会有差别

  • 新生代收集器

  • 老年代收集器

  • 整堆收集器

4.JVM参数配置

常用的设置-Xms 初始堆大小,jvm启动时,给定堆空间大小-Xmx 最大堆大小  jvm运行过程中,如果初始堆空间不足时候,最大可以扩展到多少-Xmn 设置堆中年轻代大小,整个堆大小=年轻代大小+年老代大小+持久代大小-XX:NewSize = n 设置年轻代初始化大小-XX:MaxNewSize 设置年轻代最大值-XX:NewRatio=n 设置年轻代和老年代的比值-XX:ThreadStackSize=n 线程堆栈大小-XXPermSize=n 设置持久代初始值​Xms512M -Xmx1024M 初始堆大小512  这些jvm参数​

调优总结(特别重要)

  • 在实际工作中,我们可以直接将初始堆大小与最大堆大小相等,这样的好处是减少程序运行时垃圾回收次数,从而提高效率

  • 初始堆值和最大堆内存内存越大,吞吐量就越高,但是也要根据自己电脑的实际内存来比较

  • 最好使用并行收集器,因为并行收集器速度比串行吞吐量高,速度快,当然服务器一定是要是多线程的

  • 设置堆内存新生代的比例和老年代的比例最好是1:2或者1:3,默认的是1:2

  • 减少GC对老年代的回收,设置值新生代垃圾对象的最大年龄,尽量不要有大量连续内存空间的java对象,因为会直接到老年代,内存不够就执行GC

  • 其实最主要还是服务器要好,你硬件跟不上,软件在好也没用,老年代GC很慢,新生代没啥事,默认的JVM堆大小好像是电脑实际内存的四分之一左右

5.类加载器

类加载机制及过程

程序主动使用某个类,如果类还未被架子啊到内存中,jvm会通过加载连接初始化三个步骤来对该类进行初始化,如果没有意外,jvm将会联系完成3个步骤,所以有时也把这3个步骤统称为类加载或类初始化

jvm执行class文件

加载---连接--初始化

加载

  • 加载指的是将类的class文件读入到内存,并将这些静态数据转换成方法区中的运行时数据结构,并在堆中生成一个代表这个类的class对象,作为方法区类数据的访问入口。这个过程需要类加载器参与

  • java类加载器由jvm提供,是所有程序运行的基础,jvm提供的这些类加载器通常称为系统类加载器,除此之外,开发者可以通过集成ClassLoader基类来创建自己的类加载器

  • 类加载器,可以从不同来源加载类的二进制数据

  • 类加载的最终产物就是位于堆中的class对象

连接

  • 当类加载之后,系统为之生成一个对应的class对象,接着会进入连接阶段,连接阶段负责把类的二进制数据合并到jre中

    • 验证,确保符合JVM规范

    • 准备,正式为变量分配内存并设置类变量初始值得阶段

    • 解析,虚拟机常量池的符号引用替换为字节引用过程

初始化

  • 初始化阶段是执行构造器方法的过程,类构造器方法是由编译器自动收藏类中的

  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发父类的初始化

  • 虚拟机会保证一个类的方法在多线程环境中被正确加锁和同步

  • 总结就是,初始化是为类的静态变量赋予正确的初始值

双亲委派机制

双亲委派机制,其工作原理是,如果一个类加载器收到了类加载的请求,他并不会自己先去加载,而是把这个请求委托给父类加载器去执行,如果父类加载器还存在父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载的任务,则成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去记载,这就是双亲委派机制,即,每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说我这件事我也干不了时,儿子才自己想办法去完成

优点:优先级层次关系,通过这种层次关系可以避免类的重复加载,当父亲已经加载该类时,就没有必要让子类再加载一次。

6.jvm可视化工具

jconsole