一、JVM相关概念汇总
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
1、JVM内存图
2、JVM类加载器
在类加载阶段,通过一个类的全限定名来获取描述该类的二进制字节流的这个动作的“代码”被称为“类加载器”(Class Loader),这个动作是可以自定义实现的。
类加载器也是一段程序,一段代码,代码可能是c或c++、Java所编写,主要是读取磁盘下或jar包中的Class文件,装载到jvm中
虚拟机自带的类加载器
启动类加载器(rt.jar) 根加载器 C++实现的
扩展类加载器 (扩展的类)extends ClassLoader
应用程序加载器(自己写的类) extends ClassLoader
3、双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。
每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中。只有当上一层类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到这个类)时,下一层类加载器才会尝试自己去加载。
三个加载器都加载不到就会抛出 ClassNotFund异常
JDK设计双亲委派模型的好处:
确保安全,避免核心类库被修改
避免重复加载
保证类的唯一性。
双亲委派模型可以被打破,需要自定义类加载器,继承ClassLoader类,重写LoadClass方法。
native:
使用native关键字时,会去调用底层的c语言的库
进入本地方法栈,调用本地方法的本地接口 JIN
JIN作用:扩展java的使用,融合不同的语言为java所有
方法区主要存放:静态变量,常量,类信息,常量池。
4、堆
一个JVM只有一个堆内存,堆内存大小可以调节。
类加载器读取了类文件会把,类,方法,常量,变量,保存所有引用类型的真实对象。
堆内存主要分为三个区域:新生区(伊甸园)、老年区、永久区。
堆、元空间(方法区)是线程共享的;其他区域是线程私有的
特点及作用:
1、线程共享的一块区域;
2、虚拟机启动时创建;
3、虚拟机所管理的内存中最大的一块区域;
4、存放所有实例对象或数组;
5、GC垃圾收集器的主要管理区域;
6、可分为新生代、老年代;
7、新生代更细化可分为Eden、
From Survivor、To Survivor,Eden:Survivor = 8:1:1
8、可通过-Xmx、-Xms调节堆大小;
9、无法再扩展
java.lang.OutOfMemoryError: Java heap space
GC垃圾回收机制:主要回收新生区和老年区
堆内存满,抛出,OOM错误。 JDK8之前叫永久存储区,JDK8之后叫元空间。 逻辑上存在,物理上不存在
新生区:
类诞生和成长,直到死亡的地方
所有的对象都在伊甸园区new出来
老年代:
·当新生代经历15次轻GC后还存在引用的,则被转移到老年代
永久区:
永久区常驻内存,用来存放JDK自身携带的Class对象,interface元数据,存储的是Java运行的一些环境,这个区域不存在垃圾回收。关闭虚拟机时,释放这个区域的内存。
OOM:
-Xms:设置初始化分配内存大小 1/64 -Xmx:设置最大分配内存1/4
-XX:+PrintGCDetails GC清理垃圾信息 -XX:+HeapDumpOnOutOfMemoryError dumpOOM错误文件
尝试扩大内存空间看结果,如果还有错误,分析代码是否出现垃圾代码或者死循环。
内存快照分析工具,MAT,Jprofiler 作用:
①分析Dump内存文件,快速定位内存泄漏 ②获得堆中数据 ③获得最大对象
5、GC
作用区域:堆(堆+方法区(非堆)))
GC两种类型:轻GC 针对新生代和偶尔的幸存区(from,to) 重GC(全局GC)
GC的算法:标记清除法 不需要额外内存空间,两次扫描,浪费时间,产生内存碎片 、标记压缩,复制算法(新生代、伊甸园区 )伊甸园区和to区为空,引用计数器
GC算法总结:
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记压缩算法>标记清除
内存利用率:标记压缩算法=标记清除>复制算法
新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?
新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
整堆回收器:G1
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。
6、栈
特点及其作用:
1、线程私有;
2、方法执行会创建栈帧,存储局部变量表等信息;
3、方法执行入虚拟机栈,方法执行完出虚拟机栈;(先进后出)
4、栈深度大于虚拟机所允许StackOverflowError;
5、栈需扩展而无法申请空间OutOfMemoryError(比较少见);hotspot虚拟机没有;
6、栈里面运行方法,存放方法的局部变量名,变量名所指向的值(常量值、对象值等)都存放到堆上的;
7、栈一般都不设置大小,栈所占的空间其实很小,可以通过-Xss1M进行设置,如果不设置默认为1M;
8、随线程而生,随线程而灭;
9、该区域不会有GC回收。
二 、JVM面试总结
1、Java运行时,一个类是什么时候被加载的
1.1 类是通过什么加载的?
关于一个类什么时候开始被加载的问题,《Java虚拟机规范》中并没有进行强制约束,而是交给了虚拟机自己去自由实现。
1.2 有哪几种虚拟机?
1)Sun公司最早的 Classic虚拟机;
2)Sun/Oracle公司的HotSpot虚拟机;
3)BEA公司的JRockit虚拟机;
4)IBM公司的IBM J9虚拟机。
1.3.虚拟机是怎么工作的?
Java常用的虚拟机是HotSpot虚拟机,HotSpot虚拟机是按需加载(用什么类,加载什么类),在需要用到该类时会自动加载这个类。
ps:虚拟机加载类时,是按需加载,首先会加载rt.jar 包下的类,也就是 java.lang包下的类,也就是核心基础类库,然后加载main方法中所需要的类
2、JVM 一个类的加载过程
2.1 JVM一个类的加载分为几个阶段
一个类从加载到jvm内存,到从jvm内存卸载,它的整个生命周期会经历7个阶段:
1)加载(Loading)
2)验证(Verification)
3)准备(Preparation)
4)解析(Resolution)
5)初始化(Initialization)
6)使用(Using)
7)卸载(Unloading)
2.2 他们之间的关系是怎样的?
其中验证、准备、解析三个阶段统称为连接(Linking);
2.3 每个阶段是做什么工作的?
加载:classpath、jar包、网络、某个磁盘位置下的类的class二进制字节流读进来,在内存中生成一个代表这个类的java.lang.Class对象放入元空间,此阶段我们程序员可以干预,我们可以自定义类加载器来实现类的加载;
验证:验证Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证虚拟机的安全;
准备:类变量赋默认初始值,int为0,long为0L,boolean为false,引用类型为null;常量赋正式值;
解析:把符号引用翻译为直接引用;
初始化:当我们new一个类的对象,访问一个类的静态属性,修改一个类的静态属性,调用一个类的静态方法,用反射API对一个类进行调用,初始化当前类,其父类也会被初始化… 那么这些都会触发类的初始化;
使用:使用这个类;
卸载:
1.该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例;
2.加载该类的ClassLoader已经被GC;
3.该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法。
3、类被初始化的过程
1.介绍类加载的过程
2.类的初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码。
准备阶段时,变量已经赋过一次系统要求的初始零值(默认值),而在初始化阶段,才真正初始化类变量和其他资源。
继承时父子类的初始化顺序是怎样的?
父类–静态变量
父类–静态初始化块
子类–静态变量
子类–静态初始化块
父类–变量
父类–初始化块
父类–构造器
子类–变量
子类–初始化块
子类–构造器
4、JVM中不同的类加载器加载哪些文件
1.启动类加载器(Bootstrap ClassLoader):(根的类加载器)C++语言实现的
<JAVA_HOME>\jre\lib\rt.jar,resources.jar、charsets.jar被-Xbootclasspath参数所指定的路径中存放的类库;
2、扩展类加载器(Extension ClassLoader):
<JAVA_HOME>\jre\lib\ext,被java.ext.dirs系统变量所指定的路径中所有的类库;
3、应用程序类加载器(Application ClassLoader):系统的类加载器
AppClassLoader,加载用户类路径(ClassPath)上所有的类库。
5、如何自定义自己的类加载器
1)继承ClassLoader
2)覆盖findClass(String name)方法 或者 loadClass() 方法;
findClass(String name)和loadClass() 方法有什么不同?
findClass(String name)方法:不会打破双亲委派;
loadClass() 方法:可以打破双亲委派。
6、了解Tomcat 的类加载机制吗
Tomcat自定义了WebAppClassLoader类加载器,打破了双亲委派的机制,即如果收到类加载的请求,首先会尝试自己去加载,如果找不到再交给父加载器去加载,目的就是为了优先加载Web应用自己定义的类。
为什么Tomcat要破坏双亲委派模型?
Tomcat是web容器,那么一个web容器可能需要部署多个应用程序。
1、部署在同一个Tomcat上的两个Web应用所使用的Java类库要相互隔离;
2、部署在同一个Tomcat上的两个Web应用所使用的Java类库要互相共享;
3、保证Tomcat服务器自身的安全,不受部署的Web应用程序影响;
4、需要支持JSP页面的热部署和热加载。
7、ClassLoader中的loadClass()、findClass()、defineClass()的区别
loadClass():
主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中.
当我们想自定义一个类加载器并且想破坏双亲委派模型时,我们会重写loadClass()方法。
findClass():
根据名称或位置加载.class字节码。
如果你想定义一个自己的类加载器,并且要遵守双亲委派模型,那么可以继承ClassLoader,并且覆盖findClass()
defineClass()
把字节码转化java.lang.Class。
8、JVM中对象如何在堆内存分配
1、指针碰撞(Bump The Pointer):内存规整的情况下;
2、空闲列表(Free List)
3、本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)