1、类加载的过程是什么?
加载
该阶段虚拟机需要完成三件事:① 通过一个类的全限定类名获取定义类的二进制字节流。② 将字节流所代表的静态存储结构转化为方法区的运行时数据区。③ 在内存中生成对应该类的 Class 实例,作为方法区这个类的数据访问入口。
验证
确保 Class 文件的字节流符合约束。如果虚拟机不检查输入的字节流,可能因为载入有错误或恶意企图的字节流而导致系统受攻击。验证主要包含四个阶段:文件格式验证、元数据验证、字节码验证、符号引用验证。
验证重要但非必需,因为只有通过与否的区别,通过后对程序运行期没有任何影响。如果代码已被反复使用和验证过,在生产环境就可以考虑关闭大部分验证缩短类加载时间。
准备
为类静态变量分配内存并设置零值,该阶段进行的内存分配仅包括类变量,不包括实例变量。如果变量被 final 修饰,编译时 Javac 会为变量生成 ConstantValue 属性,准备阶段虚拟机会将变量值设为代码值。
解析
将常量池内的符号引用替换为直接引用。
符号引用以一组符号描述引用目标,可以是任何形式的字面量,只要使用时能无歧义地定位目标即可。与虚拟机内存布局无关,引用目标不一定已经加载到虚拟机内存。
直接引用是可以直接指向目标的指针、相对偏移量或能间接定位到目标的句柄。和虚拟机的内存布局相关,引用目标必须已在虚拟机的内存中存在。
初始化
直到该阶段 JVM 才开始执行类中编写的代码。准备阶段时变量赋过零值,初始化阶段会根据程序员的编码去初始化类变量和其他资源。初始化阶段就是执行类构造方法中的 `` 方法,该方法是 Javac 自动生成的。
2、谈谈 JVM 中的常量池
JDK 1.8 开始
1、 字符串常量池:存放在堆中,包括 String 对象执行 intern() 方法后存的地方、双引号直接引用的字符串
2、 运行时常量池:存放在方法区,属于元空间,是类加载后的一些存储区域,大多数是类中 constant_pool 的内容
3、 类文件常量池:constant_pool,JVM 定义的概念
3、JVM 内存区域
JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA 堆、方法区】、直接内存。
线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 Hotspot VM 内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的生/死对应)。
线程共享区域随虚拟机的启动/关闭而创建/销毁。
直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提供了基于Channel与 Buffer的IO方式, 它可以使用Native函数库直接分配堆外内存, 然后使用DirectByteBuffer 对象作为这块内存的引用进行操作(详见: Java I/O 扩展), 这样就避免了在 Java堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。
4、G1 收集器
Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器, G1 收集器两个最突出的改进是:
1、 基于标记-整理算法,不产生内存碎片。
2、 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间, 优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率
5、堆的作用是什么?
堆是虚拟机所管理的内存中最大的一块,被所有线程共享的,在虚拟机启动时创建。堆用来存放对象实例,Java 里几乎所有对象实例都在堆分配内存。堆可以处于物理上不连续的内存空间,逻辑上应该连续,但对于例如数组这样的大对象,多数虚拟机实现出于简单、存储高效的考虑会要求连续的内存空间。
堆既可以被实现成固定大小,也可以是可扩展的,可通过 -Xms
和 -Xmx
设置堆的最小和最大容量,当前主流 JVM 都按照可扩展实现。如果堆没有内存完成实例分配也无法扩展,抛出 OutOfMemoryError。
6、如何查看 JVM 当前使用的是什么垃圾收集器?
-XX:+PrintCommandLineFlags 参数可以打印出所选垃圾收集器和堆空间大小等设置
如果开启了 GC 日志详细信息,里面也会包含各代使用的垃圾收集器的简称
7、GC的回收流程是怎样的?
GC回收流程如下:
1、 对于整个的GC流程里面,那么最需要处理的就是新生代和老年代的内存清理操作,而元空间(永久代)都不在GC范围内
2、 当现在有一个新的对象产生,那么对象一定需要内存空间,平均每个栈内存存4k,每个堆内存存8k,那么对象一定需要进行堆空间的申请
3、 首先会判断Eden区是否有内存空间,如果此时有内存空间,则直接将新对象保存在伊甸园区。
4、 但是如果此时在伊甸园区内存不足,那么会自动执行一个Minor GC 操作,将伊甸园区的无用内存空间进行清理,Minor GC的清理范围只在Eden园区,清理之后会继续判断Eden园区的内存空间是否充足?如果内存空间充足,则将新对象直接在Eden园区进行空间分配。
5、 如果执行Minor GC 之后发现伊甸园区的内存空间依然不足,那么这个时候会执行存活区的判断,如果存活区有剩余空间,则将Eden园区部分活跃对象保存在存活区,那么随后继续判断Eden园区的内存空间是否充足,如果充足怎则将新对象直接在Eden园区进行空间分配。
6、 此时如果存活区没有内存空间,则继续判断老年区。则将部分存活对象保存在老年代,而后存活区将有空余空间。
7、 如果这个时候老年代也满了,那么这个时候将产生Major GC(Full GC),那么这个时候将进行老年代的清理
8、 如果老年代执行Full GC之后,无法进行对象的保存,则会产生OOM异常,OutOfMemoryError异常
8、类的实例化顺序
1、 父类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
2、 子类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
3、 父类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
4、 父类构造方法
5、 子类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
6、 子类构造方法
检验一下是不是真懂了:
public class Base {
private String name = "博客:Soinice";
public Base() {
tellName();
printName();
}
public void tellName() {
System.out.println("Base tell name: " + name);
}
public void printName() {
System.out.println("Base print name: " + name);
}
}
public class Dervied extends Base {
private String name = "Java3y";
public Dervied() {
tellName();
printName();
}
@Override
public void tellName() {
System.out.println("Dervied tell name: " + name);
}
@Override
public void printName() {
System.out.println("Dervied print name: " + name);
}
public static void main(String[] args) {
new Dervied();
}
}
输出数据:
Dervied tell name: null
Dervied print name: null
Dervied tell name: Java3y
Dervied print name: Java3y
Process finished with exit code 0
第一次做错的同学点个赞,加个关注不过分吧(hahaha。
9、工作中常用的 JVM 配置参数有哪些?
Java 8 为例
日志
1、 -XX:+PrintFlagsFinal,打印JVM所有参数的值
2、 -XX:+PrintGC,打印GC信息
3、 -XX:+PrintGCDetails,打印GC详细信息
4、 -XX:+PrintGCTimeStamps,打印GC的时间戳
5、 -Xloggc:filename,设置GC log文件的位置
6、 -XX:+PrintTenuringDistribution,查看熬过收集后剩余对象的年龄分布信息
内存设置
1、 -Xms,设置堆的初始化内存大小
2、 -Xmx,设置堆的最大内存
3、 -Xmn,设置新生代内存大小
4、 -Xss,设置线程栈大小
5、 -XX:NewRatio,新生代与老年代比值
6、 -XX:SurvivorRatio,新生代中Eden区与两个Survivor区的比值,默认为8,即Eden:Survivor:Survivor=8:1:1
7、 -XX:MaxTenuringThreshold,从年轻代到老年代,最大晋升年龄。CMS 下默认为 6,G1 下默认为 15
8、 -XX:MetaspaceSize,设置元空间的大小,第一次超过将触发 GC
9、 -XX:MaxMetaspaceSize,元空间最大值
10、 -XX:MaxDirectMemorySize,用于设置直接内存的最大值,限制通过 DirectByteBuffer 申请的内存
11、 -XX:ReservedCodeCacheSize,用于设置 JIT 编译后的代码存放区大小,如果观察到这个值有限制,可以适当调大,一般够用即可
设置垃圾收集相关
1、 -XX:+UseSerialGC,设置串行收集器
2、 -XX:+UseParallelGC,设置并行收集器
3、 -XX:+UseConcMarkSweepGC,使用CMS收集器
4、 -XX:ParallelGCThreads,设置Parallel GC的线程数
5、 -XX:MaxGCPauseMillis,GC最大暂停时间 ms
6、 -XX:+UseG1GC,使用G1垃圾收集器
CMS 垃圾回收器相关
1、 -XX:+UseCMSInitiatingOccupancyOnly
2、 -XX:CMSInitiatingOccupancyFraction,与前者配合使用,指定MajorGC的发生时机
3、 -XX:+ExplicitGCInvokesConcurrent,代码调用 System.gc() 开始并行 FullGC,建议加上这个参数
4、 -XX:+CMSScavengeBeforeRemark,表示开启或关闭在 CMS 重新标记阶段之前的清除(YGC)尝试,它可以降低 remark 时间,建议加上
5、 -XX:+ParallelRefProcEnabled,可以用来并行处理 Reference,以加快处理速度,缩短耗时
G1 垃圾回收器相关
1、 -XX:MaxGCPauseMillis,用于设置目标停顿时间,G1 会尽力达成
2、 -XX:G1HeapRegionSize,用于设置小堆区大小,建议保持默认
3、 -XX:InitiatingHeapOccupancyPercent,表示当整个堆内存使用达到一定比例(默认是 45%),并发标记阶段就会被启动
4、 -XX:ConcGCThreads,表示并发垃圾收集器使用的线程数量,默认值随 JVM 运行的平台不同而变动,不建议修改
参数查询官网地址:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
建议面试时最好能记住 CMS 和 G1的参数,特点突出使用较多,被问的概率大
10、Java里有哪些引用类型?
1、 强引用 这种引用属于最普通最强硬的一种存在,只有在和 GC Roots 断绝关系时,才会被消灭掉。
2、 软引用 软引用用于维护一些可有可无的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。可以看到,这种特性非常适合用在缓存技术上。比如网页缓存、图片缓存等。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java 虚拟机就会把这个软引用加入到与之关联的引用队列中。
3、 弱引用 弱引用对象相比较软引用,要更加无用一些,它拥有更短的生命周期。当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。弱引用拥有更短的生命周期,在 Java 中,用 java.lang.ref.WeakReference 类来表示。它的应用场景和软引用类似,可以在一些对内存更加敏感的系统里采用。
4、 虚引用 这是一种形同虚设的引用,在现实场景中用的不是很多。虚引用必须和引用队列(ReferenceQueue)联合使用。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。实际上,虚引用的 get,总是返回 null。