Java JVM 相关基础知识

1.JMM Java内存模型

每条线程都有自己的工作内存[Working Memory]

线程的工作内存保存了被该线程使用的变量的主内存副本拷贝

线程对变量的所有线程之间也无法直接访问对方工作内存的变量,线程间变量值的传递均需要通过主内存来完成。

2.java的堆和栈

1)堆 heap:可动态申请的内存空间(其记录空闲内存空间的链表由操作系统维护);

    其中的内存在不需要时可以回收,以分配给新的内存请求,其内存中的数据是无序的;

    一般由使用者自由分配,malloc 分配的就是堆,需要手动释放;

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

2)栈 stack:先进后出的数据结构,通常用于保存方法(函数)中的参数,局部变量.;

    在 java 中,所有基本类型和引用类型都在栈中存储.栈中数据的生存空间一般在当前 scopes 内(就是由{...}括起来的区域);

    先分配的内存必定后释放;

    一般由系统自动分配,存放函数的参数值,局部变量等,自动清除

3.方法区 本地方法栈

1)方法区 Method Area:和Java 堆一样,别名叫做 Non-Heap(非堆),是各个线程共享的内存区域;

          它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

  运行时常量池 Runtime Constant Pool:方法区的一部分;         

          Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中

2)本地方法栈 Native Method Stacks:是为虚拟机使用到的 Native 方法服务

注意:JVM 组成

4.JVM 运行时数据区

程序计数器:是一小块内存,记录着当前程序运行到哪了;

      字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程回复等都需要依赖这个计数器来完成;

      如果一个线程执行一个主要方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;

      如果正在执行的是一个本地方法,这个计数器的值则为空;

      唯一一个在Java的虚拟机规范中没有规定任何OutOfMemoryError异常情况的区域

其余参考 2和3

4.GC

GC Garbage Collection:能可以自动监测对象是否超过作用域从而达到自动回收内存的目的

5.如何判断回收此对象

1) 根索算法:每个对象看做根节点,GC roots ,也就是根对象,如果从一个对象没有到达根对象的路径,或者说从根对象开始无法引用到该对象,该对象就是不可达的

GC Roots 对象 :

    虚拟机栈(栈帧中的本地变量表)中引用的对象,每个方法执行的时候,jvm都会创建一个相应的栈帧(栈帧中包括操作数栈、局部变量表、运行时常量池的引用);

  方法区中类静态属性引用的对象(使用static关键字),此类不属于任何实例;

     方法区常量引用的对象(使用static final关键字);

本地方法栈(Native Stack)引用的对象,JNI技术  使用Native方法

2)引用计数法(目前未使用)

java在运行时,当有一个地方引用该对象实例,会将这个对象实例加1,引用失效时就减1,jvm在扫描内存时,发现引用计数值为0的则是垃圾对象,计数值大于0的则为活跃对象

6.Java 内存泄漏

实际开发中,可能会存在无用但可达的对象,这些对象不能被 GC 回收,因此也会导致内存泄露的发生(OutOfMemoryError)

常见的内存泄漏现象:

1)长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露

public class Test {
    Object object;
    public void test(){
        object = new Object();
        //...其他代码
    }
}

 2)静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着

3)当集合里面的对象属性被修改后,再调用remove()方法时不起作用;

4)各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,(Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL);

注意:尽量减少使用静态变量,类的静态变量的生命周期和类同步的;

    明确内存对象的有效作用域;

    各种连接(数据库连接,网络连接,IO连接)操作,务必显示调用close关闭;

    对于不需要使用的对象手动设置null值,不管GC何时会开始清理,我们都应及时的将无用的对象标记为可被清理的对象

7.JVM 垃圾回收算法

Java堆中的对象是分为新生代和老年代

1)标记-清除算法  Mark-Sweep:首先标记出所有需要回收的对象,在标记完成之后统一回收所有标记的对象,但标记和清除过程的效率都不高,标记清除之后会产生大量不连续的内存碎片

2)标记-整理算法 Mark-Compact:先标记所有可回收对象,让存活的对象向一端移动,然后直接清理掉端边界以外的内存

3)复制 Copying:jvm扫描所有对象,通过根搜索算法标记被引用的对象,之后会申请新的内存空间,将标记的对象复制到新的内存空间里,存活的对象复制完,会清空原来的内存空间,将新的内存最为jvm的对象存储空间,但代价是将内存缩小为原来的一半

4)分代回收

jvm常用回收算法就是分代回收,年轻代以复制算法为主,老年代以标记整理算法为主

年轻代对象比较多,每次垃圾回收都有很多的垃圾对象回收,而且要尽可能快的减少生命周期短的对象,存活的对象较少,因此只要将有标记的对象复制到另一个内存区域,其余全部清除,并且复制的数量较少,效率较高;

老年代是年轻代筛选出来的对象,需要删除的对象比较少,显然采用标记整理效率较高

8.JVM 垃圾回收器

新生代收集器:Serial、ParNew、Parallel Scavenge

老年代收集器:CMS、Serial Old、Parallel Old

整堆收集器: G1

并行收集:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。

并发收集:指用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行)。用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。

吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 ))。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%

1)Serial 收集器

串行;

用于新生代的单线程收集器,采用复制算法进行垃圾收集;

  Serial 进行垃圾收集时,不仅只用一条线程执行垃圾收集工作,它在收集的同时,所有的用户线程必须暂停(Stop The World)

适用于 Client 模式下的虚拟机,单CPU

2)ParNew 收集器

是一个 Serial 的多线程版本,其它与Serial并无区别;

 默认开启的收集线程数与CPU的数量相同,在CPU非常多的环境中,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数;

存在Stop The World;

许多运行在Server模式下的虚拟机中首选的新生代收集器,除了Serial收集器外,目前只有它能与CMS收集器配合工作

  

3)Parallel Scavenge 收集器

  使用复制算法的收集器,又是并行的多线程收集器;

  与 ParNew 的不同之处是ParNew 的目标是尽可能缩短垃圾收集时用户线程的停顿时间,Parallel Scavenge 的目标是达到一个可控制的吞吐量;

  GC 自适应调节:-XX:+UseAdptiveSizePolicy 开启

提供两个参数用于精确控制吞吐量:XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间

                  XX:GCRatio 直接设置吞吐量的大小  计算方法是: 1 / (1 + n)

4)Serial Old

Serial 的老年代版本,同样是一个单线程收集器,采用标记-整理算法;

  用途:在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用;

     作为CMS收集器的后备方案

5)Parallel Old

Parallel Scavenge 的老年代版本,是一个多线程收集器,采用标记-整理算法;

  JDK1.6有Parallel Old收集器可搭配Parallel Scavenge收集器;

 JDK1.6及之后用来代替老年代的Serial Old收集器

6)CMS(Concurrent Mark Sweep)

以获取最短回收停顿时间为目标的收集器,基于标记-清除算法实现,并发收集、低停顿;

  JDK1.5推出;

  CMS的内存回收是与用户线程一起“并发”执行的

  运行过程:

1)初始标记:Stop The World,初始标记仅仅标记GC Roots能直接关联到的对象,速度很快;

2)并发标记:进行GC Roots Tracing的过程,同时开启GC和用户线程,用一个闭包结构去记录可达对象,找出存活对象且用户线程可并发执行;

3)重新标记:修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(采用多线程并行执行来提升效率);需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;

4)并发清除:开启用户线程,同时GC线程开始对为标记的区域做清扫,回收所有的垃圾对象

缺点:对CPU资源非常敏感;无法处理浮动垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生;因为采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC

7)G1(Garbage-First)

jdk1.7 才正式引用的商用收集器,在JDK1.9已经成为默认的收集器;

以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征;

  特点:

1)并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行;

   2)分代收集:

    能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;
    能够采用不同方式处理不同时期的对象;
    虽然保留分代概念,但Java堆的内存布局有很大差别;
    将整个堆划分为多个大小相等的独立区域(Region);
    新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合;
  3)空间整合:

从整体看,是基于标记-整理算法;

  • 从局部(两个Region间)看,是基于复制算法:不会产生内存碎片,有利于长时间运行;

4)可预测停顿:G1除了追求低停顿外,还能建立可预测的停顿时间模型;可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒

有计划的避免在整个Java堆中进行全区域的垃圾收集;G1跟踪各个Region里面的垃圾堆积的大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region;这样就保证了在有限的时间内可以获取尽可能高的收集效率

G1如何避免整堆扫描:G1中每个Region都有一个与之对应的Remembered Set,,虚拟机发现程序在对Reference类型进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用对象是否处于多个Region中(即检查老年代中是否引用了新生代中的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆进行扫描也不会有遗漏。

运行大致过程:

           初始标记:仅标记GC Roots能直接到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。(需要线程停顿,但耗时很短。)

          并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活对象。(耗时较长,但可与用户程序并发执行)

          最终标记:为了修正在并发标记期间因用户程序执行而导致标记产生变化的那一部分标记记录。且对象的变化记录在线程Remembered Set  Logs里面,把Remembered Set  Logs里面的数据合并到Remembered Set中。(需要线程停顿,但可并行执行。)

          筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。(可并发执行)

9.堆 内存划分

不同的对象,生命周期是不一样的,因此不同生命周期的对象采用不同的收集方式,可以提高垃圾回收的效率 

串行serial, 并行parallel, 以及CMS,把堆内存划分为固定大小的三个部分: 年轻代(young generation), 年老代(old generation), 以及持久代(permanent generation)

1)年轻代 Young Generation

新生成的对象

分为三个区,一个Eden区,大部分对象在Eden区中生成,当Eden区满时,还存活的对象将被复制到 Survivor区(两个中的一个), 当这个 Survivor区满时,此区的存活对象将被复制到另外一个 Survivor区,当这个 Survivor区也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到年老区(Tenured)

注 意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来的对象,和从前一个 Survivor复制过来的对象,而复制到年老区的只有从第一个 Survivor 去过来的对象。而且,Survivor 区总有一个是空的

    同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能

2)老年代 Old Generation

生命周期较长的对象

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中

3)持久代 Permanent Generation

用于存放静态文件,如今Java类、方法等;

持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等

10.简述分代垃圾回收工作

.

1)Eden 区,新创建的对象;

2)当 Eden 满了(达到一定比例)不能创建新对象,则触发垃圾回收(minor GC),将无用对象清理掉,然后剩余对象复制到某个 Survivor 中,如 S1,同时清空 Eden 区。

3)当 Eden 区再次满了,会将 S1 中的不能清空的对象存到另外一个 Survivor 中,如 S2,同时将 Eden 区中的不能清空的对象,也复制到 S1 中,保证 Eden 和 S1,均被清空。

4)重复多次(默认 15 次)Survivor 中没有被清理的对象,则会复制到老年代 Old(Tenured)区中,

5)当 Old 区满了,则会触发一个一次完整地垃圾回收(FullGC),之前新生代的垃圾回收称为(minor GC)

原文地址:https://www.cnblogs.com/dxjx/p/12544717.html

时间: 2024-10-16 16:54:05

Java JVM 相关基础知识的相关文章

黑马程序员——Java I/O基础知识之I/O流

I/O流基础知识--字节流和字符流 文件存储在硬盘中,是以二进制表示的,只有内存中才能形成字符.数据的来源可以有硬盘,内存,控制台,网络,Java把数据从一个地方转到另一个地方的现象称为流,用InputStream和OutputStream接口来表示,这两个流里面的都是以字节为单位的,后来加入了Reader和Writer,里面操作的是字符,是两个字节为单位的. 字节流 字节流将数据写入文件 try { File file =new File("d:" +File .separator+

网络连接相关基础知识笔记

一.常说的TCP/IP的含义 TCP/IP协议簇并不仅仅指TCP协议和IP协议,实际它包括了一系列协议组成的集合,如:TCP,IP,UDP,FTP,SMTP,DNS,ARP,PPP等 TCP与UDP协议都属于传输层协议,但有很大不同,TCP是面向连接的协议,提供的是可靠的数据流服务,TCP采用"带重传的肯定确认"机制来实现传输的可靠性,实现了一种"虚电路",因为从物理上来说,并不是真正在两台主机间建立了连接,这种连接只是存在于逻辑上的.最大的开销出现在通信前建立连接

【RAC】RAC相关基础知识

[RAC]RAC相关基础知识 1.CRS简介    从Oracle 10G开始,oracle引进一套完整的集群管理解决方案—-Cluster-Ready Services,它包括集群连通性.消息和锁.负载管理等框架.从而使得RAC可以脱离第三方集群件,当然,CRS与第三方集群件可以共同使用. (1).CRS进程 CRS主要由三部分组成,三部分都作为守护进程出现 <1>CRSD:资源可用性维护的主要引擎.它用来执行高可用性恢复及管理操作,诸如维护OCR及管理应用资源,它保存着集群的信息状态和OC

Java的入门基础知识

https://course.tianmaying.com/java-basic%2Bjava-environment#0 作者:David链接:https://www.zhihu.com/question/25255189/answer/86898400来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 这个教程将Java的入门基础知识贯穿在一个实例中,逐步深入,可以帮助你快速进入Java编程的世界.万事开头难,逐步跟着这个教程走一遍,对Java应该就会有一种入门的

Java语言的基础知识

第三章 1.在java源文件编辑器中,选择某个成员变量,然后按住shift+alt+j,Eclipse会自动添加JavaDoc文档注释结构,如果选择的是方法,还会自动添加参数名称. 2.Java语言规定标示符是由任意的字母.下划线.美元符号和数字组成,并且第一个字符不能使数字,标示符不能使java中的保留关键字. 3.在Java语言中允许使用汉字或其他语言文字作为变量名,如int 年龄 =21;在程序运行时不会报错,但建议尽量不要使用这些语言作为变量. 4.java用关键字final来声明常量,

Java并发(基础知识)—— Executor框架及线程池

在Java并发(基础知识)—— 创建.运行以及停止一个线程中讲解了两种创建线程的方式:直接继承Thread类以及实现Runnable接口并赋给Thread,这两种创建线程的方式在线程比较少的时候是没有问题的,但是当需要创建大量线程时就会出现问题,因为这种使用方法把线程创建语句随意地散落在代码中,无法统一管理线程,我们将无法管理创建线程的数量,而过量的线程创建将直接使系统崩溃. 从高内聚角度讲,我们应该创建一个统一的创建以及运行接口,为我们管理这些线程,这个统一的创建与运行接口就是JDK 5的Ex

Java语言的基础知识4

第五章(数组) 1.在Java中可以将数组看做是一个对象虽然基本数据类型不是对象但有基本数据类型组成的数组是对象. 2.对于二维数组求第二维就用array[0].length, array.length就是默认的是第一维的长度. 3.foreach并不是一个新的语法它是for的循环的格式化主要执行遍历功能的循环,example: int arry ={1,2,3,4,5}; for(int i :array){ system.out.println(): } 4.数组元素定义完以后可通过Arra

深入理解mysql之BDB系列(1)---BDB相关基础知识

    深入理解mysql之BDB系列(1) ---BDB相关基础知识 作者:杨万富 一:BDB体系结构 1.1.BDB体系结构 BDB总体的体系结构如图1.1所看到的,包括五个子系统(见图1.1中相关数).1)数据存取子系统,2)事务子系统,3)锁子系统,4)内存池管理子系统,5)日志子系统. 在一个应用程序中,并不一定须要全然具备这5大子系统. 假设程序仅仅使用了数据存取子系统,它的体系结构如图1.2.在图1.2中,我们仅仅使了两个子系统:数据存取以及内存池子系统.(备注:另外三个子系统在B

Java语言的基础知识12

第十四章(使用集合类保存对象) 1.java中得集合对象就像是一个容器,它用来存放Java类的对象.Java中的集合类有些方便存入和取出,有些则方便查找.集合类和数组的区别是,数组的长度是固定的,集合的长度是可变的,数组用来存放基本类型,集合用来存放对象的引用.常用的集合类有List集合,Set集合,和Map集合. 2.List集合包括List接口以及List接口的所有实现类.List集合中的元素许重复,个元素的顺序就是对象插入的顺序.类似java中的数组.List类继承了Collection接