目录
12、如何判断对象是否死亡(哪些对象应该回收)(两种方法)。
17、年轻代的对象被老年代引用,年轻代gc时如何判断年轻代gc该不该被回收?
19、Minor Gc和Full GC 有什么不同呢?
21.4、如何替换JDK类
1、jvm的垃圾回收算法有哪些?分别解释一下?
- 复制算法
新生代,将内存一分为二,每次只使用其中一半,满了之后将存活的对象复制到另一半内存中,然后将剩下的碎片一次性擦除。缺点是需要两倍的内存空间。
- 标记-清除算法
老年代,标记无用对象,然后统一回收所有被标记的对象。缺点是会产生内存碎片。
- 标记-整理算法
老年代,标记无用对象,然后把整个堆中存活对象压缩到堆的其中一块,从而避免了内存的碎片化问题。
- 分代收集算法
新生代:Serial、ParNew、Parallel Scavenge
老年代:Serial Old、Paraller Old、CMS
2、Java的四种引用
- 强引用
在程序内存不足时,JVM宁愿抛出异常也不会回收强引用。以下,str就是强引用。
-
- 软引用
在程序内存不足时,会被回收。软引用常用于缓存中,创建缓存的时候,将创建的对象放入缓存中,内存不足时,JVM就会回收最先创建的对象。
- 弱引用
当JVM进行垃圾回收时,只要JVM发现了弱引用,不论空间是否充足,就会将其回收。
- 虚引用
如果一个对象仅持有虚引用,相当于没有引用,在任何时候都可能被垃圾回收器回收。
3、新生代为什么要设置两个survior区?
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
设置两个survivor的意义在于解决了新生代的碎片化问题。如果只有一个survivor,第一次eden满了,触发Minor GC,都会把存活对象放到survivor;第二次Eden满了,触发Minor GC,又会把新的对象放到survivor,而此时survivor区已经存在了之前的对象,可能就会导致两次放入survivor的对象内存不连续,从而导致内存碎片化。
4、新生代和老年代各区域占比
默认情况下,Eden:Survivor: Survivor = 8:1:1,年轻代:老年代 = 1:2。
可通过-XX:SurvivorRatio调整Eden和Survivor占比,默认-XX:SurvivorRatio=8;
可通过-XX:NewRatio调整新生代和老年代占比,默认-XX:NewRatio=2。
5、如何通过一个.class文件获取它的jdk版本?
将class文件用16进制的方式打开,前4个字节表示了class文件的魔数:0xCAFEBABE,在第5-8个字节代表了当前class文件的jdk版本号。
6、JVM的五大模块
类装载器子系统、运行时数据区、执行引擎、本地方法接口、垃圾收集器。
7、jvm的内存模型(运行时数据区)
7.1、程序计数器
线程私有,当前线程所执行字节码的行号指示器,记录的是虚拟机当前字节码指令的地址。
7.2、虚拟机栈
线程私有,保存线程栈帧,每进入一个方法,线程会生成一个帧,用以保存当前方法的局部变量表、操作数栈、动态链接、方法返回地址等信息,并将该帧入栈;每退出一个方法,就会将当前帧出栈。
- 局部变量表:存放方法参数和方法内定义的局部变量。
- 操作数栈:从局部变量表或实例对象字段中复制常量或变量写入到操作数栈,计算后将结果出栈到局部变量表或返回给方法调用者。
- 动态链接:将栈帧中的符号引用转换成运行时常量池中的直接引用。
- 方法返回地址:返回存放当前方法的pc寄存器的值,即调用方法的下一条指令,以保证程序能正常往下执行。
7.3、堆
线程公有,用来存放所有Java对象。
7.4、方法区(永久代)
线程公有,保存类的元数据(类的信息、常量、静态变量、方法信息等数据)。jdk8后方法区存在于元空间中,使用直接内存。
JDK7:永久代,堆内,存放类的元数据信息、静态变量、常量池。
JDK8:元空间,堆外(本地内存),存放类的元数据信息,静态变量和常量池存于堆中。
7.5、本地方法栈
线程公有,与虚拟机栈类似,用于本地方法的调用。
8、Java8为什么要将永久代换成元空间?
- 类及方法的信息等比较难以确定大小,因此难以设定永久代的大小;
使用元空间时,可以加载多少类的元数据就可由系统的实际可用空间来控制。
- 永久代使GC更复杂(过小容易引起永久代内存溢出),难以调优,且回收效率低(永久代中的对象不会经常回收)。
- 要合并HotSpot和JRockit的代码,JRockit从来没有所谓的永久代。
永久代的class metadata(类元信息)->本地内存;
永久代的运行时常量池和类静态变量->Java堆;
-XX:永久代参数(PermSize MaxPermSize)->元空间参数(MetaspaceSize MaxMetaspaceSize MinMetaspaceFreeRatio MaxMetaspaceFreeRatio)
9、常量池和运行时常量池
常量池:在字节码文件中,存放编译期间生成的各种字面量和符号引用。
运行时常量池:在方法区中,是常量池在运行时的表现形式。
可以理解为,字节码中的常量池,只是文件信息,它想要执行就必须加载要内存中。程序运行时,将被加载的字节码常量池的信息放到了方法区中的运行时常量池中。
常量池将对象引用转换成符号引用,在程序运行使用时,再加载具体的数据结构。
10、Java内存模型:JMM
10.1、Java的内存模型解决了什么问题?
解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。
CPU缓存和指令重排(编译器优化)可提升性能,但可能会导致多线程间交互时的数据一致性问题。而禁用缓存和编译优化,又对程序性能不利。所以,JMM内存模型,通过使用volatile、synchronized、final,提供了按需禁用缓存以及编译优化。
10.2、禁止指令重排
开发者层面(volatile、synchronized、final)
JMM层面(happen-before规则)
CPU层面(CPU缓存、CPU内存重排、内存屏障)
11、Java 对象的创建过程
- 类加载检查:虚拟机遇到一条new指令时,首先检查该指令的参数能否在常量池中定位到一个类的符号引用,并检查该符号引用代表的类是否已被加载、解析、初始化,如果没有,则进行相应的类加载过程(加载、验证、准备、解析、初始化)。
- 分配内存:
“指针碰撞”(内存规整):以一个指针作为已用内存和未用内存的分界线,分配与释放内存时只需要移动指针即可;
“空闲列表”(内存不规整):用一个列表保存内存的信息,记录所有内存的可用状态,分配时从列表中找一块足够大小的内存划分给对象,并更新列表。
- 初始化零值:将分配的内存空间都初始化零值。
- 对象头配置:设置类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。
- 执行init方法:执行init方法初始化。
12、如何判断对象是否死亡(哪些对象应该回收)(两种方法)。
12.1、引用计数法
给对象添加一个引用计数器,每当有一个地方引用它时,引用计数器+1;引用失效时,引用计数器-1。该算法无法解决对象间循环引用的问题。
12.2、可达性分析算法(引用链)
通过一系列的GC ROOT作为起始节点,从这些节点开始往下搜索,所走过的路径称为引用链。一个对象到GC ROOT没有任何引用链相连,则该对象是不可用的。
可达性分析算法:任何没有被废弃的对象都可以最终都能追溯到存在于栈或静态存储中的引用。
12.3、GC ROOT
- 虚拟机栈帧(本地变量表)中引用的对象;
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中引用的对象。
- 活动线程相关的各种引用。
- 类的静态变量的引用。
注意:
我们这里说的是活跃的引用,而不是对象,对象是不能作为 GC Roots 的。
GC 过程是找出所有活对象,并把其余空间认定为“无用”;而不是找出所有死掉的对象,并回收它们占用的空间。所以,哪怕 JVM 的堆非常的大,基于 tracing 的 GC 方式,回收速度也会非常快。
13、JVM会在什么时候进行回收
CPU空闲时
堆内存储满了
主动调用system.gc()尝试进行回收。
14、如何判断一个常量是废弃常量
在运行时常量池中,如果一个常量没有被任何一个对象引用,则说明该常量是废弃常量。
15、如何判断一个类是无用的类
类的实例被回收、类加载器被回收、Class对象不被引用。
- 1、该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
- 2、加载该类的 ClassLoader 已经被回收。
- 3、该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
16、HotSpot为什么要分为新生代和老年代?
新生代中的对象大部分是“朝生夕死”,所以Minor GC(清理新生代)非常频繁。
而从新生代中存活下来或者大对象则进入老年代,存活时间长,所以一般不会执行Full GC(清理新生代和老年代)。
17、年轻代的对象被老年代引用,年轻代gc时如何判断年轻代gc该不该被回收?
当老年代存活对象较多时,每次Minor GC查询老年代所有对象影响回收效率(因为GC会 stop-the-world),所以在老年代有一个write barrier(写屏障)来管理的card table(卡表),card table存放了所有老年代对象对新生代对象的引用。
18、GC类型
Minor GC:新生代收集;
Major GC:老年代收集(只有CMS会有单独收集老年代的行为);
Full GC:收集整个Java堆和方法区的垃圾收集。
19、Minor Gc和Full GC 有什么不同呢?
Minor GC:无法为一个新对象分配内存空间时,比如Eden区满了,GC新生代的内存空间;
Full GC:老年代内存过小、老年代连续内存空间过小时,GC新生代和老年代的内存空间。
20、GC的触发条件
20.1、Minor GC
Eden区满,触发Minor GC。
20.2、Full GC
- 调用System.gc()时;
- 老年代空间不足时;
- 方法区(元空间)空间不足时;
- 通过Minor GC进入老年代的对象大小大于老年代的大小时。
21、类加载机制
21.1、类加载过程
- 加载:找到外部(jar,war)的.class文件,加载类的二进制数据,并加载到Java的方法区。
- 验证:校验.class文件是否安全。比如:低版本JVM,无法加载高版本的类库。
- 准备:为类变量分配内存,并初始化为默认值。
类变量:准备阶段(初始值),初始化阶段(程序员定义的值)
局部能量:如果没有赋初始值,不能使用。
- 解析:将符号引用替换为直接引用(指向目标对象的指针)。类或接口的解析、类方法解析、接口方法解析、字段解析。
- 初始化:
初始化成员变量。
规则一:static 语句块,只能访问到定义在 static 语句块之前的变量。
规则二:JVM 会保证在子类的初始化方法执行之前,父类的初始化方法已经执行完毕。
21.2、类加载器
Bootstrap ClassLoader(启动类加载器)
负责加载<JAVA_HOME>\lib目录中或被-Xbootclasspath指定的路径中的class文件。
Extention ClassLoader(扩展类加载器)
负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所指定的路径的类库。
App ClassLoader(应用程序类加载器)
负责加载用户类路径(classPath)上的类库
Custom ClassLoader(自定义加载器)
支持一些个性化的扩展功能。
21.3、双亲委派机制
21.3.1、定义
除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都会委派给它的父加载器进行加载。
21.3.2、作用
避免类的重复加载;
保证Java的核心API不被篡改。
21.3.3、打破双亲委派机制的案例
tomcat
每个应用都有一个WebAppClassLoader类加载器;对于一些需要加载的非基础类,会由一个叫作 WebAppClassLoader 的类加载器优先加载;WebAppClassLoader加载不到的时候,再交给上层的 ClassLoader 进行加载。
WebAppClassLoader 用来隔绝不同应用的 .class 文件,比如你的两个应用,可能会依赖同一个第三方的不同版本。
SPI
通过在 META-INF/services 目录下,创建一个以接口全限定名为命名的文件(内容为实现类的全限定名),即可自动加载这一种实现,这就是 SPI。
21.4、如何替换JDK类
将自己的自定义的JDK同名类(如HashMap) 类,打包成一个 jar 包,然后放到 -Djava.endorsed.dirs 指定的目录中。
该目录下的jar包,会比rt.jar中的文件,优先级更高,可以被最先加载到。
以上内容为个人学习理解,如有问题,欢迎在评论区指出。
部分内容截取自网络,如有侵权,联系作者删除。