jvm如何加载.class文件
- 首先JVM是一个虚拟的计算机,它有自己的硬件架构,如处理器,堆栈等,还有相应的指令系统.
- JVM的架构整个分为4个部分,类加载器,运行时数据区,本地接口和执行引擎
- 类加载器的主要作用就是加载class文件到内存,但是并不是自己随意创建一个文件就能加载,它是有一定的格式的,只要符合格式,就能加载
- 执行引擎的作用就是解析class文件里面的内容,提交给操作系统执行
- 本地接口是融入一个不同的开发语言为Java所用,在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载加载native libraries。
- 运行数据区,就是jvm内存结构模型,我们所写的程序都会加载到这里,之后才开始运行
谈谈反射?
- java反射机制就是在运行状态中,对于任意一个类,都能够直到这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java反射机制
- 反射编译执行的过程:
- 首先编译器将XXX,java愿望呢间编译成XXX.class字节码文件
- 类加载器将字节码转换为JVM中的Class<XXX>对象
- 随后JVM利用Class<XXX>对象实例化XXX对象
谈谈类加载?
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机类的加载机制
虚拟机里面类加载方式有两种,一种是隐式加载,一种是显示加载
隐式加载是当程序运行时遇到new来加载对象时,new支持带参数的构造方法.
显示加载,通过loadClass,forName等方法,显示加载需要的类,当我们获取到Class对象之后,需要调用class对象的instance方法,来获取对象的实例,class对象的instance方法不支持传入参数,需要通过反射调用对象的instance方法才能支持参数
类的装载过程可以分为几个阶段
第一步是加载,就是ClassLoad加载class文件字节码,加入到内存中,最后生成.class文件,作为方法区这个类的各种数据的访问入口
第二部是连接,连接它又分为三个小部分,校验,准备和解析.校验就是验证class文件正确性和安全性;第二步是准备;为类对象(static修饰的变量)分配内存并设置初始值,类变量随类型信息存储在方法区中,这个初始值通常情况下都是数据类型的零值,如果类字段属性被final修饰,则在准备阶段直接初始化为它所指定的值;第三部分是解析:jvm将常量池内的符号引用转换为直接引用
第三步是初始化,执行类变量赋值和静态代码块
Java对象头
- Java对象主要是由对象头,实例数据,对齐补充
- 对象头是由Mark word和所有的地址指针,Mark word里面主要是放垃圾回收的信息,以及锁相关(无锁,偏向锁,轻量级锁,重量级锁)
说说forname和Classload的区别?
- 首先一个类的装载过程需要3个阶段,就是上面说的那三个阶段
-
Class.forName()
和ClassLoader.loadclass()
这两种方式是类加载方式的两种显示加载方式 - 前它得到的class是已经初始化的
- 后者得到的是还没有连接的.
- 举个例子:一个类里面写一个静态代码块,当你用forname,发现静态代码块被执行,但是用Classloader没有被执行
类加载器的双亲委派机制
- 首先Java里面有不同的类加载器,他们加载类的方式和路径也不尽相同,为了实现分工,他们各自负责各自的区域快,使得逻辑更加的明确,为了使他们之间相互协作,所有就有了类加载器的双亲委派机制.
- 双亲委派机制的原理图就是一个类加载器的层次模型,自定而上分别是自定义的类加载器,应用程序类加载器,扩展类加载器,启动类加载器.他们依次是下面一层的父类加载器.
- 双亲委任模型的工作过程是:如果一个类加载器收到了类的加载的请求,它首先不会自己去尝试去加载这个类,而是由父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求都会最终传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载.
- 使用双亲委派机制的目的就是避免多份同样字节码的加载,通过一层一层向上,看看哪个加载器曾经加载过这个这个字节码
你了解Java内存模型吗?
-
从JVM架构模型来看,包括类加载器,运行时数据区,执行引擎,本地方法库,这个运行时数据区其实就是JVM内存空间结构模型
- Java程序运行在虚拟机之上,运行时需要内存空间,虚拟机执行Java程序的过程中,会把他管理的内存划分为不同的数据区,方便管理,主要有这么几个区域,程序计数器,虚拟机栈,本地方法栈,方法区以及堆
- 其中程序计数器,虚拟机栈,本地方法栈是线程私有的,方法区以及堆是线程共享的.
- 程序计数器,是一块较小的内存空间,他表示现在字节码执行的行数,通过改变这个计数器的值来选取下一条需要执行的指令.程序的一些循环跳转等功能都是依赖这个程序计数器的,因为一个处理器同一个时间只会执行同一个线程中的指令,所以一个线程中就会有一个程序计数器,各个线程之间的程序计数器相互独立,不会被影响.程序计数器它是逻辑计数器,而不是物理计数器
-
Java虚拟机栈是Java方法执行的内存模型,每次方法给执行时都会创建一个栈帧,栈帧存储了方法的局部变量表,操作数栈,动态连接以及方法返回值地址等信息,每一个方法从调用到执行完成的过程,都对应着一个栈帧在Java虚拟机栈帧里面出栈入栈的过程
- 局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和 returnAddress 类型(指向了一条字节码指令的地址)。
- Java虚拟机栈有两种异常状况:如果线程请求的栈的深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;如果扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常。所以说通常我们的递归次数不能太多,每执行依次递归都会创建一个栈帧.
- 栈的内存不需要被jvm回收,每次出栈之后就会被释放
- 本地方法栈与虚拟机栈所发挥的作用相似,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务.
- Java堆是线程共享的,它的作用就是存放对象实例,几乎所有的对象实例都在这个分配内存但也有一些其他的对象不再堆上分配内存;
- Java堆它是垃圾收集器管理的主要区域,所以他又被叫做"GC堆",从内存回收的角度看,现在手机器基本都是采用分代收集算法,所以Java堆还可以细分新生代和老年代
- 方法区也是线程共享的,他用于存储以被虚拟机加载的类的信息常量,静态变量,即编译器编译后的代码等数据
JVM分代
- JVM分为年轻代和老年代
- 年轻代又会分为一个eden和两个survivor区默认的比例是8:1:1survivor又分为from和to,刚开始的时候,对象是放在eden区和from区的,to区是空的,如果发生GC的时候,eden区中所有存活的对象放在to区,from会根据你的年龄判断去to区还是老年代,年龄默认是15.当GC完成,你的eden区和from区是空的,然后from和to区交换.
JVM三大性能调优参数-Xms,-Xmx,-Xss的含义
- Xss规定了每个线程虚拟机栈的大小
- Xms规定了初始的堆的大小,即该进程刚创建时,它的专属Java堆的大小,一旦对象容量超过这个值,就会自动-Xmx大小
- -Xmx规定了堆能够扩展的最大值
Java内存模型中堆栈的区别?----内存分配策略
- 堆和栈是完全不同的两块内存区域,栈式线程独享的,堆式线程共享的,二者的最大的不同就是存储的内容不同,堆中主要存放对象实例,而栈中主要存放的基本的数据类型,对象的引用.
- 从管理方式上看:栈自动释放,堆需要GC.jvm可以对内存栈进行管理操作,而且该内存空间的释放是编译器就可以操作的内容;而堆中,jvm不会对其进行释放操作,而是让垃圾回收器进行垃圾回收
- 从空间大小上看,栈比堆小,堆里面存储的是对象,一个程序中我们会创建很多的对象,而栈服务的是方法,只存储每个方法里面的基本数据类型
- 从产生碎片上看.栈产生的碎片远远小于堆,虽然说堆里面有垃圾回收器,但是垃圾回收器他不是实时的, 可能会使堆的垃圾逐渐累积起来,而栈它的操作时一一对应的,每执行一个方法就让他入栈,方法执行完成就出栈,不会产生碎片
- 从分配方式上看,栈支持静态分配和动态分配,而堆只支持动态分配.
- 从效率来看,栈比堆高
判断垃圾可以被回收?
- 判断一个对象是否为垃圾主要有两种算法,一种是引用计数法,一种是可达性分析法
- 引用计数器的思想是通过判断对象引用的数量来决定对象是否可以被回收,堆中的每一个对象都有一个引用计数器,被引用则+1,完成引用则-1,当引用计数器为0时,该对象就可以视为一个垃圾.
- 但是这种算法有一定的缺陷,无法检测出循环引用的情况,导致内存泄漏,如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为 0。
- 可达性分析的思想是判断对象的引用链是否可达来决定对象是否可以被回收,从GC root开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则是被认为是没有被引用的节点,就是垃圾
什么可以作为GC root?
- 虚拟机栈中引用的对象,即栈帧的本地变量表
- 方法区中常量引用的对象
- 方法区中类静态属性引用的对象
- 本地方法中Native引用的对象
内存泄漏?
垃圾回收算法
- 标记-清除算法,它的基本思想就是先标记,从根节点进行扫描,对存活的对象进行标记,然后清除,对堆内存从头到尾进行遍历,回收不可达的对象,这种算法会产生大量的空间碎片,不利于之后大对象的存储.
- 复制算法,他刚开始把堆分成一个对象面和一个空闲面,程序在对象面进行对象分配,当对象面慢了,然后复制算法开始收集对象面中活动的对象,将获得对象赋值到空闲面,这样空闲面变成了对象面,原来的对象面成了空闲面,程序会在新的空闲面上进行分配
- 标记整理算法,他先从根节点进行扫描,对存活的对象进行标记,然后移动所有存活的对象,并且按照内存地址依次排序,然后将末端地址以后的内存全部回收.
- 分代收集算法,他其实就是前面三种的组合,他将堆内存进一步的划分,按照对象生命周期的不同划分区域采用不同的垃圾收集算法,不同的区域采取的算法不一样,这样可以提高垃圾收集的效率;jdk8以后,把堆内存划分为两个区域,一个是年轻代另一个是老年代,年轻代的对象存活率低,采用的是复制算法,而老年代的对象存活率高,采用的是标记-清除算法或者标记-整理算法
Minor GC 和 Full GC
- 分代收集算法的GC分为两种,一个是Minor GC它是发生在年轻代中的垃圾收集动作所采用的是复制算法.年轻代是所有Java对象出生的地方.
- 年轻代是那些生命周期较短的对象存放的区域,它分为一个Eden区和两个Survivor区,Eden区它是对象的起源,刚创建一个对象首先他会出现在Eden区,但是当Eden放不下对象的时候,对象也有可能被直接放在Survivor区,
- 另一个是Full GC,他与老年代相关,老年代存放的是生命周期较长的对象,它使用的垃圾回收算法是标记整理算法或标记清除算法
触发Full GC的条件
- 老年代空间不足,使用的是标记清除算法,会产生大量的碎片,当空间碎片过多,会给大对象的分配带来麻烦,往往找不到符合要求的空间来分配对象,就会触发Full GC
- 永久代空间不足
- CMS GC时出现的promotion failed,concurrent mode failure
- 调用System.gc()
谈谈垃圾收集器
- Serial收集器,它是一种单线程的收集器,对于新生代的垃圾进行回收,因为单线程,所以它工作的时候,必须暂停其他所有工作的线程,直到它收集结束,对于单线程来说它确实是简单高效的,Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择,Client模式就是一种轻量级的虚拟机,Server模式是一种重量级的虚拟机
- ParNew收集器,新生代收集器,ParNew收集器是Serial收集器的多线程版本,多运行在Server模式下的虚拟机上
- Parallel Scavenge收集器,新生代收集器,运行在多线程环境下,Paralllel Scavenge收集器的特点是达到一个可控制的吞吐量,所谓吞吐量就是代码执行时间/(代码执行时间+垃圾收集时间),这个收集器更关注吞吐量,
- Serial Old收集器,老年代的收集器,单线程,它是Serial老年代版本,进行垃圾收集时,必须暂停工作线程,也是模式的Client模式下的老年代收集器
- Paralllel Old收集器,它是老年代的收集器,它使用的多线程,关注吞吐量,可以看作是Parallel Scavenge的老年代版本.
- CMS收集器,它是最常见的老年代垃圾收集器,它是基于标记清除算法实现的,它的工作过程可以分为以下4步,第一步,初始标记,标记GC root能够直接关联的对象,有暂停,第二步,并发标记,并发追溯标记,与用户进程同时进行,第三步重新标记,有暂停,修正之前并发并发标记因用户线程继续运作而导致标记产生变动的那一部分对象,第四部分,并发清理,清理垃圾;这种算法最常用但是也有一个问题,如果垃圾是在并发标记之后产生,那么这个垃圾只能等到下次才能回收,而且它使用的是标记清除算法,会产生大量的碎片,当空间碎片过多,会给大对象的分配带来麻烦,往往找不到符合要求的空间来分配对象,就会触发Full GC
- G1收集器,它即用于年轻代,又用于老年代,他有如下的特点,第一个是并发和并行,使用多个CPU来缩短stop the world的停顿时间,并且与用户线程并发执行,第二个特点是分代收集,它管理整个堆,但是它采用不同的方式去处理新创建的对象和已经存活率一段时间老年代中的对象,第三个特点是它采用的是标记整理算法,解决了CMS的内存碎片的问题,第四个特点是可预测停顿,G1收集器能够建立一个停顿时间模型;第五个特点是它采用分块的概念,把整个堆划分为多个大小相等的Region,物理上分块,逻辑上分代,G1跟踪各个块里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的,保证了G1收集器在有限时间内可以获取尽可能高的收集效率
- Epsilon GC收集器,它只分配内存,不回收垃圾,一般用于性能调优,测试等任务