java虚拟机 函数表_Java虚拟机基础知识点整理

前言:最近看了一些Java虚拟机方面的知识,在此做一下整理工作。

一、简单聊聊jvm

1.1我们先来看一个简单的Java程序

现在我有一个JavaBean

1 public class chy{2 privateString name;3 private intage;4 //...各种get set方法和tostring

5 }

一个测试类:

public classchytest{public static voidmain(String args[]){

chy c= newchy();

c.setName("chy");

System.out.println(c);

}

}

我们初学Java的时候都学过Javac命令,就是将一个.java文件编译成.class文件

然后用Java命令执行.class文件

4327301b92af75be693428f88db5eccb.png

Java源文件

ecd37d06191c6d329cc86ca564eaacf9.png

在IDE中点击运行的时候其实也是将这两个命令结合起来了(编译并运行)方便我们开发

e040f93a5d430e4b1238e68afbbbf2ba.png

生成class文件

3fc36e58db9d0e6abd6fa01f0b8cb22a.png

解析class文件得到的结果

42dc9eeecb25a6810c131ad12313f185.png

1.2 编译过程

Java文件时由Java源码编译器,也就是上述的Javac.exe完成的,流程图如下

d8f08204afd1b974adaa5fbd0413b588.png

词法分析是将源代码的字符流转换为标记集合(Token流),单个字符是编写程序过程的最小元素,而标记是编译过程的最小元素,关键字、变量名等等都可以称为一个标记。

int a = b+2;

这句代码包含了6个标记,分别是int、a、=、b、+、2    虽然关键字int有三个字母,但他们属于一个Token,不可再拆分。在Javac源码中由com.sun.tools.javac.parser.Scanner类来实现。

语法分析是根据Token序列来构造抽象语法树的过程,抽象语法树是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码的一个语法结构。例如包、类型、修饰符、接口、返回值乃至注释等都可以是一个语法结构。

在javac源码中语法分析由com.sun.tools.javac.parser.Parser类实现,经过这个步骤之后,编译器就不会再对源码进行操作了,后续都是直接操作抽象语法树。

jdk1.5之后添加了对注解的支持,这些注解与普通代码一样,是在运行期间发挥作用的。提供了一组插入式注解处理器的标准API在编译期间对注解进行处理。我们可以把它们看作是一组编译器的插件。这些插件里面可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行修改,编译器将回到解析以及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止。每一次循环称为一个Round。如下图

cb774e96e26ef7fc048fd3e06e3df70b.png

1.2.1 编译时期-语法糖

语法糖可以看作编译器使用的一些小把戏,却大大提高了效率。

最值得说明的就是泛型了。这个是我们经常用到的语法糖。

泛型只会在Java源码中存在,编译过后就会被替换成原来的原生类型(Raw Type,也称为裸类型)

这个过程也被称为泛型擦除。

泛型仅存在于编码阶段,编码阶段会进行检测,编译阶段进行泛型擦除,把类型还原成对应的原始类型。

有了泛型这个语法糖之后,代码会更加简洁,不需要进行类型转换。程序更加健壮,只要编译时期没有警告,那么运行时期不会有ClassCastException异常。可读性和稳定性,在编写集合的时候就限定了类型。

1.3 JVM实现跨平台

至此我们通过javac.exe编译出了class文件,这些class文件明显是不能直接运行的。

这些class文件时交给jvm解析运行的。

JVM是运行在操作系统上的,每个操作系统的指令是不同的,而JDK是区分操作系统的,只要你本地系统装了JDK,这个JDK就是能够与当前系统兼容的。

而class字节码运行在JVM之上,所以不用关系class字节码是在哪个操作系统编译的,只要符合JVM规范,那么这个字节码文件就是可运行的。

所以Java就做到了一次编译,到处运行。

jdk是区分平台的,class文件会被翻译成不同平台的机器编码。

1.4 class文件和jvm

1.4.1 类的加载机制

现在我们例子里的两个class文件都会直接加载进jvm中吗?

虚拟机规范严格规定了只要五种情况必须对类进行初始化(class文件加载到jvm中)

1、创建类的实例(new)方式。访问某个类或者接口的静态变量,或者对该静态变量赋值,调用类的静态方法。

2、反射的方式

3、初始化某个类的子类,其父类也会被初始化

4、Java虚拟机启动时被标明为启动类的类,直接使用Java.exe命令来运行某个主类(包含main方法的那个类)

5、当使用jdk1.7的动态语言支持时

所以说:

Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

1.4.2 如何将类加载到jvm

class文件是通过类加载器加载到jvm中的。

Java默认有三种类加载器

c37f72ee13c9a90cd018230e9b872518.png

1、Bootstrap ClassLoader:负责加载$JAVA_HOME中jre/lib/rt.jar里面所有的class,由c++实现,不是classLoader子类

000d3ad75cb3e7dcf1e7724b748fa619.png

2、ExtensionClassLoader:负责加载Java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext/*.jar或-D java.ext.dirs指定目录下的jar包

3、App ClassLoader:负责记载classpath中指定的jar包以及目录中class

工作过程:

1、当AppClassLoader加载一个class时,它首先不会自己尝试加载这个类,而是把类加载请求委托给父类加载器ExtentionClassLoader来尝试加载。

2、当ExtensionClassLoader加载一个ClassLoader加载一个class时,它首先也不会自己尝试加载这个类,而是把类加载请求委托给BootStrapClassLoader去完成

3、如果BootScrapClassLoader加载失败,会尝试用ExtensionClassLoader加载。

4、若ExtensionClassLoader也加载失败,则会使用AppClassLoader来加载

5、如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException

其实这就是所谓的双亲委派模型。简单的说,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。

好处:

防止内存中出现多份同样的字节码(安全性角度)

特别说明

类加载器在成功加载某个类之后,会把得到的java.lang.Class类的实例缓存起来,下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会再次尝试加载。

1.4.2 类加载详细过程

加载器加载到jvm中,接下来其实又分了好几个步骤:

1、加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象。

2、连接,连接又包含三块内容:验证、准备、初始化

验证,文件格式、元数据、字节码、符号引用验证

准备,为类的静态变量分配内容,并将其初始化为默认值

解析,把类中的符号引用转换为直接引用

初始化,为类的静态变量赋予正确的初始值

a4b37816ea796749ad2487c228a4ea4f.png

1.4.3 JIT即时编辑器

一般我们可能会想:JVM在加载了这些class文件以后,针对这些字节码,逐条取出,逐条执行--》解析器解析

但如果是这样的话,那就太慢了。

我们的JVM是这样实现的。

就是把这些Java字节码重新编译优化,生成机器码让CPU直接执行,这样编出来的代码效率会更高,

编译也要花费时间的,我们一般对热点代码做编译,非热点代码直接解析就好了

热点代码:一、多次调用的方法 二、多次执行的循环体

使用热点探测来检测是否为热点代码,热点探测有两种方式:

采样

计数器

目前HotSpot使用的是计数器的方式,它为每个方法准备了两类计数器:

方法调用计数器(Invocation Counter)

回边计数器(Back EdgeCounter)

在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了就会触发JIT编译

5c09ba713bef253374835f7246130c52.png

1.4.4 回到例子中

按我们的程序走,我们的chytest.class文件会被AppClassLoader加载器加载,因为ExtensionClassLoader和BootStrapClass Loader都不会加载它【双亲委派模型】到JVM中。

随后发现了要使用chy这个类,我们的chy.class文件会被加载了AppClassLoader加载器(因为ExtensionClassLoader和BootStrapClassLoader都不会【双亲委派模型】加载到JVM中)

3c7ce974881cc0be085862e7f63bb0cb.png

1.5类加载完以后JVM干什么?

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。

1.5.1 JVM的内存结构

首先我们来了解一下JVM的内存结构是怎么样的

1、堆:存放对象实例,几乎所有的对象实例都在这里分配内存

2、虚拟机栈:虚拟机栈描述的是Java方法执行的内存结构:每个方法被执行的时候都会创建一个栈帧(Stack Frame)用于存储局部变量表,操作栈,动态链接,方法出口等信息

3、本地方法栈:呢目的方法栈则是为虚拟机使用到的Native方法服务

4、方法区:存储已被虚拟机加载的类元数据信息(元空间)

5、程序计数器:当前线程所执行的字节码的行号指示器

1.5.2 例子中的流程

0c6478be0022e575dc9327190aabc531.png

简单描述一下例子中的工作流程

1、通过java.exe运行chytest.class,随后被加载到JVM,元空间存储着类的信息(包括类的名称、方法信息、字段信息)

2、然后JVM找到chytest主函数入口(main),为main函数创建栈帧,开始执行main函数

3、main函数的第一条命令是chy c = new chy();就是让JVM创建一个chy对象,但是这时候就是让JVM创建一个chy对象,但这时候方法区没有chy类的信息,所以JVM马上加载了chy类,把chy类的信息放到方法区中(元空间)

4、加载完chy类之后,Java虚拟机做的第一件事就是在堆中为chy实例分配内存,然后调用构造函数初始化chy实例,这个chy实例持有着指向方法区的chy类的类型信息(其中包含有方法表,Java动态绑定的底层实现)的引用

5、当使用chy.setName("chy")的时候,jvm根据c的引用找到chy对象,然后根据chy对象持有的引用定位到方法区中chy类的类型信息的方法表,获得setName()函数的字节码地址

6、为setName()函数创建栈帧,开始运行setName()函数

从微观上其实还做了很多东西的,正如上面所说的类加载过程,在类加载完成之后JVM为其分配内存(分配内存中也做了非常多的事)。由于这些步骤并不是一步步往下走,会有很多的混沌bootstrap的过程,所以很难描述清楚。

1.6 简单聊聊各种常量池

1.6.1各个常量池的情况

jdk1.7之后

运行时常量池位于堆中

字符串常量池位于堆中

常量池存储的是:

字面量(literal):文本字符串等---->用双引号引起来的字符串字面量都会进这里面

符号引用(Symbolic References)

类和接口的全限定名(Full Qualified Name)

字段的名称和描述符(Descriptor)

方法的名称和描述符

常量池(Constant Pool Table)用于存放编译期生成的字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。现在我们的运行时常量池只是换了一个位置,原本在方法区,现在在堆中,但是可以明确的是:类加载后,常量池中的数据会运行时常量池中存放。

常量池是class文件中的内容,还不是运行时的内容,不要理解它是个池子,其实就是class文件中的字节码指令

字符串常量池:

HotSpotVM里记录interned string的一个全局表叫做StringTable,它本质上就是个HashSet。注意它只存储java.lang.String实例的引用,而不存储String对象的内容。

字符串常量池只存储引用,不存储内容

我们看一下intern方法

如果常量池中存在当前字符串,那么直接返回字符串中它的引用。

如果常量池中没有此字符串,会将此字符串引用保存到常量池中后,再直接返回字符串引用

1.6.2 看几个题目

1 public static voidmain(String[] args) {2 String s = new String("1");3 s.intern();4 String s2 = "1";5 System.out.println(s ==s2);6 System.out.println("-----------chy--------------");7 }

第一句

1 String s = new String("1");

a722a14bede932fa1244993cabb8b1b3.png

第二句s.intern();发现字符串常量池中已经存在“1”字符串对象,直接返回字符串常量池中对堆的引用但是没有接受,此时s还是指向堆中的对象。

e26a3b81855362898d098089aee48e91.png

第三句:String s2 = "1",发现字符串常量池中已经保存了该对象的引用了,直接返回字符串常量池堆堆中字符串的引用

fa1254c119289490adb9b794cf02634b.png

很容易看到两条引用时不一样的,所以返回false

问题2:

1 public static voidmain(String[] args) {2 System.out.println("----------chy----------");3 String s3 = new String("1")+new String("1");4 s3.intern();5 String s4 = "11";6 System.out.println(s3==s4);7 }

第一句

String s3 = new String("1")+new String("1");

注意此时“11”对象没有在字符串池中保存引用

facfc8224146a1f09d31513f1ba3d121.png

第二句:s3.intern()发现“11”对象并没有在字符串常量池中,于是将“11”对象在字符串常量池中保存当前字符串的引用,并返回当前字符串的引用,但是没有接收。

40bdc579aeec0250f5c310557da919fb.png

第三句:String s4="11"发现字符串常量池已经存在引用了,直接返回(拿到的也是与s3相同的指向引用)

3ceb59ca90c5b4a1af63e5821d095bf5.png

最后可以返回true

再看看两题

1 public static voidmain(String[] args) {2

3 String s = new String("1");4 String s2 = "1";5 s.intern();6 System.out.println(s==s2);//false

7

8 String s3 = new String("1")+new String("1");9 String s4 = "11";10 s3.intern();11 System.out.println(s3==s4);//false

12 }

1 public static voidmain(String[] args) {2 String s1 = new String("he")+new String("llo");3 String s2 = new String("h")+new String("ello");4 String s3 =s1.intern();5 String s4 =s2.intern();6 System.out.println(s1 == s3);//true

7 System.out.println(s1 == s4);//true

8

9 }

1.7 GC垃圾回收

可以说垃圾回收是JVM中非常重要的知识点了,这里也就简单说一下

1.7.1 jvm垃圾回收简单介绍

在C++中,我们知道创建出的对象是需要动手去delete掉的。我们Java程序运行在JVM中,JVM可以帮我们自动回收不需要的对象,对我们来说是十分方便的。

虽然说是自动回收了我们不需要的对象,但如果我们想变强,就要研究一下它究竟是怎么干的,理论知识有哪些。

首先,JVM回收的垃圾,垃圾就是我们程序中已经不需要的了。垃圾收集器对堆进行回收前,第一件事情就是要确定对象之中哪些还存活着,哪些已经死去。判断哪些对象死去常用有两种方式:

引用计数法-->这种难以解决对象之间的循环引用问题

可达性分析算法-->主流的JVM采用的是这种方式

be71e8fa016dd8d534cdb76712098435.png

现在已经可以判断哪些对象已经死去了,我们现在要对这些死去的对象进行回收,回收也有好几种算法

1、标记清除算法

2、复制算法

3、标记整理算法

4、分代收集算法

无论是可达性分析法还是垃圾回收算法JVM都是使用的准确式GC。JVM是使用一组称为OopMap的数据结构,来存储所有的对象引用(这样就不用遍历整个内存去查找了,空间换时间)并且不会将所有的指令都生成OopMap,只会在安全点上生成OopMap,在安全区域开始GC。

在OopMap的协助下,HotSpot可以快速且准确地完成GC Roots枚举(可达性分析)

OopMap记录了栈上本地变量到堆上对象的引用关系。作用是:垃圾收集时,收集线程会对栈的内存进行扫描,看看哪些位置存储了Reference类型,如果发现了某个位置确实存储了Reference类型,那就意味着它所引用的对象这次不能回收,但问题是,栈上的本地变量表里只有一部分数据是Reference类型的,但我们还是不得不将整个栈扫描一遍,这对时间和资源是一种浪费。

一个很自然的想法就是用空间换时间,在某个时间把栈上代表引用的位置记录下来,这样到真正gc的时候可以直接读取,而不用一点点扫描了。大部分主流虚拟机都是这么干的,使用OopMap的数据结构来记录这些信息。

我们知道,一个线程意味着一个栈,一个栈由多个栈帧组成,一个栈帧对应着一个方法,一个方法里面可能有多个安全点,gc发生时,程序首先运行到最近的安全点停下来,然后更新自己的OopMap记录下栈上哪些位置代表着引用。枚举根节点时,遍历每个栈帧的Oopmap,通过栈中记录的被引用对象的内存地址,即可以找到这些对象。

通过上述描述,我们可以清楚的看到OopMap可以避免全栈扫描,加快枚举根节点速度,但这并不是它的全部用意,还可以帮助HotSpot实现精确式GC(也就是说给定某个位置上的某块数据,要能知道它的准确类型是什么)

上述所讲的垃圾收集算法只能算是方法论,实现落地的是垃圾收集器

Serial收集器

ParNew收集器

Parallel Scavenge收集器

Serial Old收集器

Parallel Old收集器

CMS收集器

G1收集器

上面这些收集器大部分是可以互相组合使用的。

886a6f2344e8d67c822eb2d7271e3ef2.png

拿些常见的JVM面试题来做做,加深一下理解和查漏补缺

2.1 详细的jvm内存结构

根据JVM规范,JVM内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

716d9881b286be0aaf41d49f381020f8.png

具体可能会聊jdk1.7以前的PermGen(永久代),替换成Metaspace(元空间)

原本永久代存储的数据:符号引用(Symbols)转移到了native heap,字面量(interned strings)转移到了Java heap,类的静态变量(class static)转移到了Java heap

Metaspace(元空间)存储的是类的元数据信息(metadata)

元空间本质和永久代类似,都是对JVM规范中方法区的实现。不够元空间和永久代的最大区别就是在于:元空间并不在虚拟机中,而是使用本地内存。

替换的好处:一、字符串存在永久代中,容易出现性能问题和内存溢出。二、永久代会为GC带来不必要的复杂度,并且回收效率偏低。

4f87e5e47dbdb4bebf5386b29c5b4c49.png

2.2 讲讲什么情况下会出现内存溢出,内存泄漏

内存泄漏的原因很简单

对象是可达的(一直被引用)

但是对象不会使用

常见的内存泄漏例子

1 public static voidmain(String[] args) {2 Set set = newHashSet();3 for (int i = 0; i < 10; i++) {4 Object object = newObject();5 set.add(object);6 //设置为空,这对象我不再用了

7 object = null;8 }9 //但是set集合中还维护这obj的引用,gc不会回收object对象

10 System.out.println(set);11 }

解决这个内存泄漏问题也很简单,将set设置为null,那就可以避免内存泄漏问题。其他内存泄漏得一步一步分析了。

内存溢出得原因:

内存泄漏导致堆栈内存不断增大,从而引发内存溢出。

大量得jar和class文件加载,装载类得空间不够,溢出

操作大量得对象导致堆内存空间已经满了,溢出

nio直接操作内存,内存过大导致溢出

解决:

查看程序是否存在内存泄漏得问题

设置参数加大空间

代码中是否存在死循环产生过多重复的对象实体

查看是否使用nio直接操作内存

2.3 说说线程栈

这里的线程栈应该指的是虚拟机栈。

JVM规范让每个线程都拥有自己独立的JVM栈,也就是Java方法的调用栈

当方法调用的时候,会生成一个栈帧。栈帧是保存再虚拟机栈中的,栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。

线程运行过程中,只有一个栈帧是处于活跃状态,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素。

通过jstack工具查看线程状态。

2.4 JVM年轻代到老年代的晋升过程的判断条件是什么?

1、部分对象会在From和To区域中复制来复制去,如此交换15次,最终如果还是存活,就存入到老年代。

2、如果对象的大小大于Eden的二分之一会直接分配在old,如果old也分配不下,会做一次majorGC,如果小于Eden的一半,但是没有足够的空间,就进行minorgc也就是新生代GC

3、minor gc后,survivor仍然放不下,则放到老年代。

4、动态年龄判断,大于等于某个年龄的对象超过survivor空间的一半,大于等于 某个年龄直接进入老年代。

老年代:在年轻代经历了15次垃圾回收后仍然存活的对象,就会被放到老年代中。因此,可以认为老年代中存放的都是一些生命周期较长的对象。

年轻代:年轻代有三个部分:1个Eden区和2个Survivor区(分别叫From和To),每次创建对象都会分配到Eden区,当Eden中没有足够的空间进行分配时,虚拟机将发动一次MinorGc,这些对象经过第一次MinorGc后如果还存活,将会被移到Survivor区的from区,在MinorGc开始时,对象只会存在于Eden区和From区里,To区是空的,然后开始进行GC,Eden区中所有存活对象都会复制到To中,From区中的老头对象会去old区,孩子对象去To区,经过这次GC后,Eden区和From区已经被清空了。这个时候,From和To会交换他们的角色。

2.5 JVM出现fullGC频繁,怎么去线上排查问题。

这题就根据full GC的触发条件来做:

1、如果有perm gen的话(jdk1.8就没了),要给perm gen分配空间,但没有足够的空间时,就会发动full gc    所以可以看看是不是perm gen区的值设置得太小了。

2、System.gc()方法调用

3、当统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间,则会触发full gc

fullgc频繁说明old区很快满了。

如果是一次fullgc后,剩余对象不多。那么说明你的eden设置太小,导致短生命周期的对象进入了old区。

如果一次fullgc后,old区回收率不大,那么说明old区太小。

2.6 类加载为什么要使用双亲委派模型,有没有什么场景打破了这个模式?

双亲委托模型的重要用途是为了解决类载入过程中的安全性问题

假设有一个开发者自己编写了一个名为java.lang.Object的类,想借此欺骗JVM。现在它要使用自己定义的ClassLoader来加载自己编写的java.lang.Object类

然而幸运的是,双亲委托模型不会让他成功,因为JVM会优先在BootStrap ClassLoader的路径下找到java.lang.Object类并载入它。

那,Java类加载是否一定遵循双亲委托模型?

在实际开发中,我们可以通过自定义ClassLoader,并重写父类的loadClass方法,来打破这一机制。

SPI就是打破了双亲委托机制的。

2.7 类的实例化顺序

1、父类静态成员和静态初始化块,按照在代码中出现的顺序依次执行

2、子类静态成员和静态初始化块,按照在代码中出现的顺序依次执行

3、父类实例成员和实例初始化块,按在代码中出现的顺序依次执行

4、父类构造方法

5、子类实例成员和实例初始化块,按在代码中出现的顺序依次执行

6、子类的构造方法

检验一下是不是真的懂了

1 class D extendsBase{2 private String name="java";3 publicD(){4 tellName();5 printName();6 }7 public voidtellName(){8 System.out.println("Dtell name"+name);9

10 }11

12 public voidprintName(){13 System.out.println("Dprint name"+name);14 }15

16

17 }18 public classBase {19 private String name="哈哈哈";20 publicBase(){21 tellName();22 printName();23 }24 public voidtellName(){25 System.out.println("tell name"+name);26

27 }28

29 public voidprintName(){30 System.out.println("print name"+name);31 }32 public static voidmain(String[] args) {33 newD();34 }35 }

输出数据

Dtell namenull

Dprint namenull

Dtell namejava

Dprint namejava

如果父类构造器调用了被子类重写的方法,就会导致父类在构造时实际上调用的是子类覆盖的方法

父类在构造时实际上调用的是子类覆盖的方法

2.8 JVM垃圾回收机制,何时触发MinorGC等操作

当年轻代中的eden区分配满的时候触发MinorGC(新生代的空间不够放的时候)

2.9 JVM依次完整的GC流程是怎样的

YGC和FGC是什么?

YGC:对新生代堆进行gc,频率比较高,因为大部分对象的存活寿命比较短,在新生代里被回收,性能耗费比较小。

FGC:全堆范围的gc。默认堆空间使用达到80%的时候会触发fgc

什么时候执行YGC和FGC

Eden空间不足,执行young gc

Old空间不足,perm空间不足,调用方法System.gc(),ygc时的悲观策略,dump live的内存信息时,都会执行fullgc

2.10 各种回收算法

GC的最基础的算法有三种

标记清除算法

复制算法

标记压缩算法

我们常用的垃圾回收器算法一般都采用分代收集算法,不同的区域使用不同的算法,其实就是组合上面的算法。

具体:

标记清除算法,标记清除算法,如它的名字,算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

复制算法,复制的收集算法,它将内存按容量分为大小相等的两块,每次只使用其中的一块,这一块内存用完了,就将还活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

标记压缩算法,标记过程仍然与标记清除算法一样,但后续步骤不是堆可回收对象进行清理,而是让所有存活的对象都向一端移动,然后清理掉端边界意外的内存。

分代收集算法,把Java堆分成新生代和老年代,不同的年代用最适用的收集算法。

2.11 各种回收器,各自优点,重点是CMS、G1

39826c4b40959ac4b8e395ff563270b4.png

1、Serial收集器,串行收集器是最古老的,最稳定以及效率高的收集器,但可能会产生较长的停顿,只使用一个线程去回收

2、ParNew收集器,其实就是Serial收集器的多线程版本

3、Parallel收集器,Parallel Scavenge收集器类似于ParNew收集器,Parallel收集器更加关注系统的吞吐量。

4、Parallel Old收集器,其实就是Parallel收集器的老年代版本,使用多线程标记整理算法

5、CMS收集器,多线程标记清除算法,收集器是一种以获取最短回收停顿实际为目标的收集器,它需要消耗额外的CPU和内存资源,在CPU和内存资源紧张,CPU较少时,会加重系统负担。CMS无法处理浮动垃圾,CMS的标记清除算法,会导致大量的空间碎片产生。

5、G1收集器,主要是一款面向服务器的垃圾收集器,主要针对装备多个处理器和大容量内存的机器,以极高的概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。

2.12 stackoverflow错误,permgen space错误

stackoverflow错误主要出现

在虚拟机栈中(线程请求栈深度大于虚拟机栈锁允许的最大深度)

permgen space错误(针对jdk1.7之前的版本)

大量加载class文件

常量池内存溢出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值