Java底层--JVM与GC

Java底层–JVM与GC

一. JAVA的理解

1.1 谈谈你对Java的理解?

  • 平台无关性 [一次编译,到处运行]
  • GC
  • 语言特性
  • 面向对象
  • 类库
  • 异常处理

可以针对这几个方面去谈

1.2 Compile Once,Run Anywhere如何实现?

在这里插入图片描述

1.3 javac是编译,javap可以反编译,其API如下:

在这里插入图片描述

Linux下连接到远程:ssh work@115.28.159.6
本地文件上传到远程:scp xxx work@115.28.159.6:~
Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同平台上运行不需要进行重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令;

1.4 为什么JVM不直接将源码解析成机器码去执行

  • 准备工作: 每次执行都需要各种检查
  • 兼容性: 也可以将别的语言解析成字节码

二.JVM如何加载.class文件

2.1 Java虚拟机

  • 只要这个平台有对应的Java虚拟机,就可以在这个平台上运行Java程序
  • 加载流程图示:
    在这里插入图片描述
  • 结构解析:
    • Class Loader: 依据特定格式,加载class文件到内存
    • Execution Engine: 对命令进行解析
    • Native Interface: 融合不同开发语言的原生库为Java所用
    • Runtime Data Area:JVM内存空间结构模型

2.2 什么是反射

  • 谈谈反射:

    • JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制;
  • 写一个反射的例子:

//反射执行Robbot类中的throwHello方法
public class ReflectSample{
	public static void main(String[] args) throws ClassNotFounException ,IllegalAccessException{
		Class rc=Class.forName("com.interview.javabasic.reflect.Robot");
		Robot r=(Robot)rc.newInstance();		//创建一个实例
		System.out.println("Class name is"+ rc.getName());
		Method getHello=rc.getDeclaredMethod("throwHello",String.class);	//能获取包括私有方法在内的所有方法,但是不能获取继承以及实现的一些方法
		getHello.setAccessible(true);		//设置为true才能访问私有
		Object str=getHello.invoke(r,"Bob");
		System.out.println("getHello result is "+ str);
		//---------------另外一种Method方法---------------
		Method sayHi=rc.getMethod("sayHi",String.class);
		sayHi.invoke(r,"welcome");			//不能获取私有方法,但是可以获取到其他的方法以及继承方法和实现等;

}}

反射就是将java中的各种成分映射成一个个java对象

三. ClassLoader

3.1 类从编译到执行的过程

  • 编译期将Robot.java源文件编译为Robot.class字节码文件
  • ClassLoader将字节码转换为JVM重的Class对象
  • JVM利用Class对象实例化为Robot对象

3.2 谈谈ClassLoader

  • ClassLoader在Java中有着非常重要的作用,它主要工作在Class装载的加载阶段,其主要作用是从系统外部获取Class二进制数据流,它是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作;

3.3 ClassLoader的种类

  • BootStrapClassLoader :C++编写,加载核心库java.*
  • ExtClassLoader:java编写,加载扩展库javax.*
  • AppClassLoader:java编写,加载程序所在目录
  • 自定义ClassLoader:java编写,定制化加载

ClassLoader有很多方法,其中一个比较重要的方法就是loadClass方法;

public Class<?> loadClass(String name) throws ClassNotFoundException{
	return loadClass(name,false);		//默认是false 
}

3.4 自定义ClassLoader

  • ClassLoader代码如下:
public class MyClassLoader extends ClassLoader{
	private String path;
	private String classLoaderName;
	public MyClassLoader(String path,String classLoaderName){
		this.path=path;
		this.classLoaderName=classLoaderName;
}
	//用于寻找类文件
	@Override
	public class findClass(String name){
		byte[] b=loadClassDate(name);
		return defineClass(name,b,0,b.length);
}
	//用于加载类文件
	private byte[] loadClassData(String name){
		name=path+name+".class";
		InputStream in =null;
		ByteArrayOutputStream out=null;
		try{
			in =new FileInputStream(new File(name));
			out=new ByteArrayOutputStream();
			int i=0;
			while((i=in.read())!=-1){
				out.wirte(i);
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			try{
				out.close();
				in.close();
			}catch(Exception e){
				e.printStackTrace();	
			}
		}
		return out.toByteArray();
	}
}
  • 类如下:
public class Wali{
	static{
		System.out.println("Hello Wali");
	}
}
  • 测试类如下:
public class ClassLoaderChecker{
	public static void main(String[] args) throws ClassNotFoundException IllegalAccessException{
		MyClassLoader m=new MyClassLoader("/Users/baidu/Desktop/","myClassLoader");
		Class c=m.loadClass("Wali");
		System.out.println("Wali");
		c.newInstance();
	}
}
  • 显示效果如下:
    在这里插入图片描述

在自定义ClassLoader的时候,重写findClass方法时,我们不仅可以去加载二进制文件,因为defineClass传入只要是二进制流就是合法的,我们可以去远程访问一些资源,也可以对类文件的某些部分进行加密,findClass解密,也可以修改二进制代码,给类添加信息,即字节码增强机制,也可以延伸去考虑AOP的实现;

四. 类加载器的双亲委派机制

4.1 谈谈类加载器的双亲委派机制

  • 图示:
    在这里插入图片描述

解析: 有两个过程,一个过程是判断是否已经加载过,另外一个过程是进行加载;先进行判断是否已经加载过,这时是从下往上,比如最开始是CustomClassLoader有没有加载过,如果没有则交给AppClassLoader进行判断,一直到BootstrapClassLoader进行判断,如果它也没有加载过则开始进行第二个过程;第二过程是自顶向下,从BootstrapClassLoader进行判断,是否在指定的Jar包中,如果有则进行加载,如果没有则往下继续逐层判断;

4.2 为什么要使用双亲委派机制去加载类?

  • 避免多份同样字节码的加载
  • 保证java自身类的安全

4.3 类的加载方式

  • 隐式加载:new
  • 显式加载: loadClass ,forName 等

4.4 loadClass和forName的区别?

  • 区别:
    • Class.forName得到的class是已经初始化完成的
    • Classloader.loadClass得到的class是还没有链接的

forName会加载类,执行其中的static静态方法,而Classloader则不会

  • 类的装载过程:
    • 加载:
      • 通过ClassLoader加载class文件字节码,生成class对象
    • 链接:
      • 校验: 检查加载的class的正确性和安全性
      • 准备: 为类变量分配存储空间并设置类变量初始值
      • 解析: JVM将常量池内的符号引用转换为直接引用
    • 初始化:
      • 执行类变量赋值和静态代码块

五. 内存模型

5.1 内存简介

  • 图示:
    在这里插入图片描述

5.2 地址空间划分

  • 内核空间:系统调用硬件等的内存空间
  • 用户空间:Java实际使用的内存空间

5.3 JVM内存模型-JDK8

  • 图示:
    在这里插入图片描述

  • 解析:

    • 线程私有:程序计数器、虚拟机栈、本地方法栈
    • 线程共享:MetaSpace、Java堆

5.4 程序计数器(Program Counter Register)

  • 当前线程所执行的字节码行号指示器(逻辑)
  • 改变计数器的值来选取下一条需要执行的字节码指令
  • 和线程是一对一的关系即"线程私有"
  • 对Java方法计数,如果是Native方法则计数器值为Undefined
  • 不会发生内存泄露

5.5 虚拟机栈(Stack)

  • 图示:
    在这里插入图片描述

  • 解析:

    • Java方法执行的内存模型
    • 包含多个栈帧
  • 局部变量表和操作数栈:

    • 局部变量表: 包含方法执行过程中的所有变量 [为操作栈做数据支撑]
    • 操作数栈: 入栈、出栈、复制、交换、产生消费变量

5.6 常见问题

  • 递归为什么会引发java.lang.StackOverflowError异常

    • 递归过深,栈帧数超出虚拟栈深度
  • 元空间(MetaSpace)与永久代(PermGen)的区别

    • 元空间使用本地内存,而永久代使用的是jvm的内存
    • 此异常不存在了: java.lang.OutOfMemoryError:PermGen space
  • MetaSpace相比PermGen的优势

    • 字符串常量池存在永久代中,容易出现性能问题和内存溢出
    • 类和方法的信息大小难以确定,给永久代的大小指定带来困难;
    • 永久代会为GC带来不必要的复杂性
    • 方便HotSpot与其他JVM如Jrockit的集成;

5.7 Java堆(Heap)

  • 图示:
    在这里插入图片描述

它是对象实例的分配区域,也是GC管理的主要区域,也被称为GC堆;

5.8 常见问题

  • JVM三大性能调优参数 -Xms -Xmx -Xss的含义

    • -Xss:规定了每个线程虚拟机栈(栈堆)的大小
    • -Xms:堆的初始值
    • -Xmx:堆能达到的最大值
  • Java内存模型中堆和栈的区别:

    • 内存分配策略
      • 静态存储:编译时确定每个数据目标在运行时的存储空间需求
      • 栈式存储: 数据区需要在编译时未知,运行时模块入口前确定
      • 堆式存储: 编译时或运行时模块入口都无法确定,动态分配
    • 管理方式:
      • 栈自动释放,堆需要GC
    • 空间大小:
      • 栈比堆小
    • 碎片相关:
      • 栈产生的碎片远小于堆
    • 分配方式:
      • 栈支持静态和动态分配,而堆仅支持动态分配
    • 效率:
      • 栈的效率比堆高
  • 不同JDK版本之间的intern()方法的区别–JDK6VS JDK6+

    • 图示:
      在这里插入图片描述

不仅能添加对象,常量池还能添加引用

六. GC-- 垃圾回收

6.1 垃圾回收–标记算法

  • 对象被判定为垃圾的标准

    • 没有被其他对象引用
  • 判断对象是否为垃圾的算法:

    • 引用计数算法
      • 概述:
        • 通过判断对象的引用数量来决定对象是否可以被回收
        • 每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1
        • 任何引用计数为0的对象实例可以被当做垃圾收集;
      • 优缺点:
        • 优点:执行效率高,程序执行受影响较小
        • 缺点: 无法检测出循环引用的情况,导致内存泄漏;
    • 可达性分析算法
      • 概述:
        • 通过判断对象的引用链是否可达来决定对象是否可以被回收;
        • 如果不可达的则可以GC回收;
  • 可以作为GC Root的对象

    • 虚拟机栈中引用的对象(栈帧中的本地变量表)
    • 方法区中的常量引用的对象
    • 方法区中的类静态属性引用的对象
    • 本地方法栈中JNI(Native方法)的引用对象
    • 活跃线程的引用对象

6.2 谈谈你了解的垃圾回收算法

  • 标记-清除算法(Mark and Sweep)

    • 概述:
      • 标记: 从根集合进行扫描,对存活的对象进行标记
      • 清除: 对堆内存从头到尾进行线性遍历,回收不可达对象内存
    • 优缺点:对象存活较多,清除操作较少 缺点:容易碎片化
  • 标记-复制算法(Copying) 年轻代 要空出50%的空间用于复制,对象存活率低于10%

    • 概述:
      • 分为对象面和空闲面
      • 对象在对象面上创建
      • 存活的对象呗从对象面复制到空闲面
      • 将对象面所有对象内存清除
    • 优缺点:
      • 解决碎片化问题
      • 顺序分配内存,简单高效
      • 适用于对象存活率低的场景 (复制会消耗性能)
  • 复制算法图示:
    在这里插入图片描述

  • 标记整理算法(Compacting) 用于老年代,对象存活率很高,不需要空出50%

    • 概述:
      • 标记: 从根集合进行扫描,对存活的对象进行标记
      • 清除: 移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收;
    • 优缺点:
      • 避免内存的不连续性
      • 不用设置两块内存互换
      • 适用于存活率高的场景
  • 图示:
    在这里插入图片描述

  • 分代收集算法(Generational Collector)

    • 垃圾回收算法的组合拳
    • 按照对象生命周期的不同划分区域以采用各不同的垃圾回收算法
    • 目的: 提高JVM的回收效率
  • GC的分类:

  • Minor GC [用于年轻代中,采用的是复制算法]

    • 年轻代: 尽可能快速地收集掉那些生命周期短的对象
      • Eden区
      • 两个Survivor区
  • 图示:
    在这里插入图片描述

Minor GC回收是直接回收Eden和from空间中的内容;Eden的空间较大,大多数的对象都死亡在这个里面

  • Full GC [用于老年代中]

JDK8以前有年轻代,老年代和永久代,而现在只有年轻代和老年代,永久代的数据被存入元空间中;

6.3 对象如何晋升到老年代

  • 经历一定Minor次数依然存活的对象
  • Surbibor区中存放不下的对象
  • 新生成的大对象(-XX:+PretenuerSizeThreshold)

6.4 常用的调优参数:

  • XX:SurvivorRatio: Eden和Survivor的比值,默认8:1
  • XX:NewRatio:老年代和年轻代内存大小的比例
  • XX:MaxTenuringThreshold:对象从年轻代晋升到老生代经过GC次数的最大阀值;

6.5 老年代

  • 概述:存放生命周期较长的对象

  • 使用的GC算法:

    • 标记-清理算法
    • 标记-整理算法
  • GC:

    • Full GC和Major GC
    • Full GC比Minor GC慢,但执行频率低
  • 触发Full GC的条件:

    • 老年代空间不足
    • 永久代空间不足[JDK8以前]
    • CMS GC时出现promotion failed, concurrent mode failure
    • Minor GC晋升到老年代的平均大小大于老年代的剩余空间
    • 调用System.gc(); 只是提醒回收,GC不一定会立即执行
  • Stop-the-World

    • JVM由于要执行GC而停止了应用程序的执行
    • 任何一种GC算法中都会发生
    • 多数GC优化通过减少Stop-the-world发生的时间来提高程序性能;
  • Safepoint: 【安全点】

    • 分析过程中对象引用关系不会发生变化的点
    • 产生Safepoint的地方:方法调用;循坏跳转;异常跳转等;
    • 安全点数量得适中;
  • JVM的运行模式:

    • Server
    • Client
  • JVM Server模式与client模式启动的差别?

    • Server模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升.
    • 原因是:
      • 当虚拟机运行在-client模式的时候,使用的是一个代号为C1的轻量级编译器, 而-server模式启动的虚拟机采用相对重量级,代号为C2的编译器. C2比C1编译器编译的相对彻底,服务起来之后,性能更高.
      • 所以通常用于做服务器的时候我们用服务端模式,如果你的电脑只是运行一下java程序,就客户端模式就可以了;服务器模式编译更彻底,然后垃圾回收优化更好,这当然吃的内存要多点相对于客户端模式。
    • 怎么修改JVM的启动模式呢?
      • 64位系统默认在 JAVA_HOME/jre/lib/amd64/jvm.cfg
      • 32在目录JAVA_HOME/jre/lib/i386/jvm.cfg
  • 垃圾收集器之间的关系,图示:
    在这里插入图片描述

如果两个收集器之间有连线,说明它们可以搭配使用

6.6 常见的垃圾收集器—年轻代

  • Serial收集器(-XX:+UseSerialGC,复制算法)

    • 单线程手机,进行垃圾收集时,必须暂停所有工作线程
    • 简单高效,Client模式下默认的年轻代收集器;
  • ParNew 收集器(-XX:+UseParNewGC,复制算法)

    • 多线程收集,其余的行为、特点和Serial收集器一样
    • 单核执行效率不如Serial,在多核下执行才有优势 [电脑有几个核心,就可以开几个线程]
  • Parallel Scavenge收集器(-XX:+UseParallelGC,复制算法)

    • 比起关注用户线程停顿时间,更关注系统的吞吐量
    • 在多核下执行才有优势,Server模式下默认的年轻代收集器

6.7 常见的垃圾收集器—老年代

  • Serial Old收集器(-XX:+UseSerialOldGC,标记-整理算法)

    • 单线程手机,进行垃圾收集时,必须暂停所有工作线程
    • 简单高效,Client模式下默认的老年代收集器;
  • Parallel Old收集器(-XX:+UseParallelOldGC,标记整理算法)

    • 多线程,吞吐量优先
  • CMS收集器(+XX:+UseConcMarkSweepGC,标记-清除算法) //可与其他收集器配合使用

    • 初始标记:stop-the-world
    • 并发标记:并发追溯标记,程序不会停顿
    • 并发预处理:查找执行并发标记阶段从年轻代晋升到老年代的对象
    • 重新标记:暂停虚拟机,扫描CMS堆中的剩余对象
    • 并发清理:清理垃圾对象,程序不会停顿
    • 并发重置:重置CMS收集器的数据结构

6.8 既用于年轻代又用于老年代的垃圾收集器–G1收集器

  • G1 收集器(-XX:+UseG1GC, 复制+标记-整理算法)
    • Garbage First收集器的特点:

      • 并行和并发
      • 分代收集
      • 空间整合
      • 可预测的停顿
    • 与其他收集器的不同地方

      • 将整个Java堆内存划分成多个大小相等的Region
      • 年轻代和老年代不再物理隔离

JDK11 出的垃圾收集器还有: Epsilon GC 和 ZGC

6.9 GC相关的面试题

  • Java中的强引用,软引用,弱引用,虚引用有什么用?

    • 强引用(Strong Reference)

      • 最普遍的引用:Object obj=new Object();
      • 抛出OutOfMemoryError终止程序也不会回收具有强引用的对象
      • 通过将对象设置为null来弱化引用,使其被回收
    • 软引用(Soft Reference)

      • 对象出在有用但非必须的状态
      • 只有当内存空间不足时,GC会回收该引用的对象的内存
      • 可以用来实现高速缓存;
      String str=new String("abc");		//强引用
      SoftReference<String> softRef=new SoftReference<String>(str);		//软引用
      
    • 弱引用(Weak Reference)

      • 非必须的对象,比软引用更弱一些
      • GC时会被收回
      • 被回收的概率不大,因为GC线程优先级比较低
      • 适用于引用偶尔被使用且不影响垃圾手机的对象
    • 虚引用(PhantomReference)

      • 不会决定对象的生命周期
      • 任何时候都可能被垃圾收集器回收
      • 跟踪对象被垃圾收集器回收的活动,起哨兵作用
      • 必须和引用队列ReferenceQueue联合使用
  • 图示:
    在这里插入图片描述

  • 级别:

    • 强引用> 软引用> 弱引用>虚引用
  • 引用队列(ReferenceQueue)

    • 概述:引用队列可以配合软引用、弱引用及幽灵引用使用,当引用的对象将要被JVM回收时,会将其加入到引用队列中。

      • 无实际存储结构,存储逻辑依赖于内部节点之间的关系来表达
      • 存储关联的且被GC的软引用,弱引用以及虚引用;
    • 应用:通过引用队列可以了解JVM垃圾回收情况。

    • 代码示例:

// 引用队列
ReferenceQueue<String> rq = new ReferenceQueue<String>();
 
// 软引用
SoftReference<String> sr = new SoftReference<String>(new String("Soft"), rq);
// 弱引用
WeakReference<String> wr = new WeakReference<String>(new String("Weak"), rq);
// 幽灵引用
PhantomReference<String> pr = new PhantomReference<String>(new String("Phantom"), rq);
 
// 从引用队列中弹出一个对象引用
Reference<? extends String> ref = rq.poll();

彩蛋一. --找工作的最佳时机

  • 金三银四时找工作的最佳时期吗?

    • 优势:
      • 供选择的公司多,机会多
    • 劣势:
      • 人才供应量旺盛
      • 成为备胎的概率大增,获取offer时间较慢
      • 若无明显竞争力,薪资涨幅相对不会很高
  • 相对容易找到工作的时期

    • 临近年末的时候:
      • 大多数人不愿意在这个时候跳槽,导致远低于求
      • 门槛变低,通过率变高
      • 拿到offer的时间会相对变短
  • 年末跳槽的优劣势

    • 优势:
      • 薪水涨幅空间可能会大
      • 能去心仪的公司的概率相对较大
    • 劣势:
      • 充当“救火英雄”的面大

彩蛋二. 找工作的渠道分析

  • 同事朋友的内推 [最为推荐的一种]

    • 简历直达团队,避免被非团队的人员筛选掉
    • 知己知彼
    • 避免简历被锁
    • 注意: 碍于情面,错过后面的好公司
  • 招聘网站的投递 [最不推荐的一种]

    • 简历会被投递
    • 太多的虚假信息
  • 猎头招聘 [人性最真实的一面]

    • 猎头对特定的公司、职位比较熟悉
    • 找干练的猎头:是否做到精准推送,建议是否足够好
    • 找多个猎头,多方面了解市场
    • 做好恶战的准备
    • 搭线练手公司,查漏补缺
  • 总结:

    • 利用猎头对公司进行分类:练手公司和心仪公司
    • 竭尽全力挖掘目标公司的内部员工
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暗余

码字来之不易,您的鼓励我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值