JVM
jvm是什么
JVM 是可运行 Java 代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、 一个垃圾回收,堆 和 一个存储方法域。JVM 是运行在操作系统之上的,它与硬件没有直接 的交互。
为什么说java是跨平台语言
这个夸平台是中间语言(JVM)实现的夸平台
Java有JVM从软件层面屏蔽了底层硬件、指令层面的细节让他兼容各种系统
难道C和C++不能夸平台吗其实也可以,C和C++需要在编译器层面去兼容不同操作系统的不同层面,写过C,和C++的就知道不同操作系统的有些代码是不一样
新生代和老年代的区别
算法实现的,新生代又分类了Eden和survivor(幸存区 幸存0区 幸存1区)两个区,数据会先分配到Eden区(伊甸园区)中去,如果Eden没有足够的空间,那么JVM就触发一次Minor GC(垃圾回收)操作,如果对象经过一次minor gc操作还存活在Eden中,并且可以被Survivor空间接收,那么就将这个对象移到Survivor中去,将年龄设置为1,在Survivor中的对象每经过一次垃圾回收还存活的话,就年龄加1(每经历一次垃圾回收就年龄+1),当对象年龄加到15的时候,就把这个对象放入老年区中去。晋升到老年区的时候次对象的年龄是可以自己设置的,如果说老年带满了会进行一次 Full GC操作,因为Full GC不常用,所以采用Mark-compact算法去清理老年代中的对象。
为什么元空间替换了永久代(元空间的好处)
区别:1.元空间不在虚拟机中,而是使用本地内存
2.默认情况下,元空间大小仅受本地内存限制。
永久代会造成一个内存溢出的问题,永久代的调优很困难
永久代中存放的是:字符串常量,类的元数据。
元空间中存放的是类的元数据,将字符串常量存放在了堆中。
说一下 jvm 运行时数据区
- 程序计数器 正在执行的字节码指令 线程私有
- 虚拟机栈 所有的执行方法都会进入 在执行代码的时候,只要创建对象就会将栈帧入栈,返回结果后弹出栈
- 本地方法栈 数据类型 对象的引用
- 堆 新创建的数组和对象 对象的实例
- 方法区 静态变量 、常量、常量池、类的信息
JVM的主要流程
流程:.java文件编译成.class文件,由类加载器去将java代码转换成字节码形式,通过运行时数据区将字节码存入内存中去,由于系统操作层 没有办法识别字节码,在通过解析器执行引擎编译成操作系统认识的命令,再将这个命令交给CPU,由于jvm底层是C编写的所以通过本地库接口调用,就完成了操作。
JMM Java内存模型
java在运行中,分为主内存和工作内存,所有的变量都是在主内存中的,每个线程都有自己的工作内存,而各个线程之间是无法进行直接访问工作内存的,不能直接对主内存进行读写操作。
内存间交互操作(内存屏障)
- lock(锁定):作用在主内存中的变量,将要操作的变量进行锁定
- unlock(解锁):作用在主内存中的变量,等待锁定的变量释放之后,其他线程可以再去获取次变量
- read(读取):作用在主内存中的变量,将主内存的中的变量读取到工作内存中的副本中去
- load(载入):作用在工作内存中的变量,工作内存接收主内存发送的内容
- use(使用):作用在工作内存中的变量,工作内存通过执行引擎对发送过来的内容进行一个转换
- assign(赋值):作用在工作内存中的变量,将执行引擎之后的数据赋值给工作内存中的对应变量中
- store(存储):作用在工作内存中的变量,将工作内存中的变量存入主内存中去
- write(写入):作用在工作内存中的变量,将store操作的变量写入主内存中去
栈
栈中主要存储的是基本数据类型,对象的引用
如果说一个方法带了native关键字,说明java是没有办法去进行编译的,这个时候就会去调用底层的c语言
在调用的时候,会进入本地方法栈中去找对应的接口(JNI)。
JNI (java native interface)作用:扩展java的使用,融合不同的编程语言为java所用
在java之前c语言比较火爆,想要超过c就出现了JNI,一些java无法编译的去调用c、c++的程序,这个时候就出现了本地方法栈。
堆主要划分
堆主要划分为:新生代(eden和幸存区)、老年代、元空间(逻辑上是存在的,物理上是不存在的)
在jdk1.6之前叫做永久代,常量池是在方法区中的
在jdk1. 7 永久代,但是慢慢退化了,在jdk1.7时要去永久代,此时常量池是在堆中的
在jdk1.8之后:无永久代,常量池在元空间
永久代和元空间最大的区别:永久代会造成内存不足,内存溢出的情况
元空间是受限于本地内存的(要多大有多大,不会造成浪费)
判断对象是否要回收的方法
1.引用计数器,也就是为每一个对象添加一个引用计数器,用来统计指向当前对象的引用次数,如果当前对象存在应用的更新,那么就对这个引用计数器进行增加,一旦这个引用计数器变成0,就意味着它可以被回收了。
这种方法需要额外的空间来存储引用计数器,但是它的实现很简单,而且效率也比较高
不过主流的JVM都没有采用这种方式,因为引用计数器在处理一些复杂的循环引用或者相互依赖的情况时,可能会出现一些不再使用但是又无法回收的内存,造成内存泄露的问题
可达性分析,它的主要思想是,首先确定一系列肯定不能回收的对象作为GC root,比如虚拟机栈里面的引用对象、本地方法栈引用的对象等,然后以GC ROOT作为起始节点,从这些节点开始向下搜索,去寻找它的直接和间接引用的对象,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收
在垃圾回收的时候,JVM会首先找到所有的GC root,这个过程会暂停所有用户线程,也就是stop the world,然后再从GC Roots这些根节点向下搜索,可达的对象保留,不可达的就会回收掉
gc roots对象是哪些
虚拟机栈 -----栈帧中的本地 变量表中引用的对象
本地方法栈 -----即一般说的 Native方法引用的对象
方法区中 类静态属性引用的对象
方法区中 常量引用的对象
常见的垃圾回收算法
- 复制清除算法: 为了解决性能的问题,复制算法应运而生。它将内存分为大小相等的两块区域,每次使用其中的一块。当一块内存使用完之后,将还存活的对象拷贝到另外一块内存区域中,然后把当前内存清空。这样性能和内存碎片的问题得以解决。但是同时带来了另外一个问题,可使用的内存空间缩小了一半!
优点:不容易产生内存碎片,可用内存空间少
缺点:存活对象多的话,效率低下
- 标记-清除算法:清除算法分为两个阶段,标记和清除。标记是将要回收的对象做个标记,清除就是清除被标记的对象
优点:实现简单
缺点:容易产生内存碎片
-
标记-整理算法(优化了清除算法):因为在清除算法中会有内存碎片的产生,所以整理算法是将清除算法中的被移除对象的位置像一端进行移动,对象对象之间不留空位
优点:不容易产生碎片
缺点:移动次数多,效率低
-
分代收集算法:主要是分为新生代和老年代。在新生代中主要是采用复制清除算法,老年代中主要是采用整理算法。分开去做了处理。
GC使用时对程序的影响
垃圾回收是会影响性能的,主要是吧不用的对象进行一个回收,释放,这个过程消耗处理器的时间
发现虚拟机频繁full GC时应该怎么办
full GC是清理整个堆空间的
首先用命令查看触发GC的原因是什么 jstat –gccause 进程id
如果是System.gc(),则看下代码哪里调用了这个方法
如果是heap inspection(内存检查),可能是哪里执行jmap –histo[:live]命令
如果是GC locker,可能是程序依赖的JNI库的原因
几种不同的垃圾回收类型
- Minor GC:从年轻代(包括Eden、Survivor区)回收内存。
- Major GC :清理整个老年代,当eden去内存不足时触发
- Full GC:清理堆中的所有的,包括年轻代和老年代
堆栈的区别
- 栈内存存储的是局部变量,堆内存存储的是实体
- 栈内存的更新速度要快于堆内存,局部变量的生命周期是很短的
- 栈内存存放的变量生命周期一旦结束就释放,对内存放的实例会被垃圾回收机制不定时的回收
队列和栈是什么?有什么区别
- 队列是先进先出原则,但也有例外的情况,Deque [dek] 接口允许从两端检索元素,栈是先进后出原则
- 队列和栈都是被用来预存储数据的
什么是双亲委派模型
如果一个类加载器收到了类加载的请求,不会自己去加载此类,把这个类交给父类去加载,每一层都交给父类去加载,这样这个类就被传送到启动加载器中去了,如果启动类加载起无法完成这个请求,那么子类加载器才可以进行这个类的加载。
双亲委派的意义
- 提高系统的安全性
- 避免类库被篡改
- 避免重复加载
类加载器分类
- 启动类加载器 (boots) 顶级加载器,负责加载JVM基础核心类库主要是jre/lib/.rt.jar下的字节码文件
- 扩展类加载器(exen.) 负责加载java.ext.dirs下的所有的类库
- 应用程序类加载器 (App) 默认的加载器 负责加载用户类路径(classpath)上的指定类库
类加载的执行过程
当虚拟机遇见 new 关键字时候,实现判断当前类是否已经加载。如果类没有加载,首先执行类的加载机制,加载完成后再为对象分配空间、初始化等。
- 首先校验当前类是否被加载,如果没有加载,执行类加载机制;
- 加载:就是从字节码加载成二进制流的过程;
- 验证:当然加载完成之后,当然需要校验 class 文件是否符合虚拟机规范,跟我们接口请求一样,第一件事情当然是先做个参数校验了;
- 准备:为静态变量、常量赋默认值;
- 解析:把常量池中符号引用(以符号描述引用的目标)替换为直接引用(指向目标的指针或者句柄等)的过程;
- 初始化:执行 static 代码块 (cinit) 进行初始化,如果存在父类,先对父类进行初始化。
当类加载完成之后,紧接着就是对象分配内存空间和初始化的过程:
- 首先为对象分配合适大小的内存空间;
- 接着为实例变量赋默认值;
- 设置对象的头信息,对象 hashcode、GC 分代年龄、元数据信息等;
- 执行构造函数 (init) 初始化。
java 中都有哪些引用类型?**
- 强引用:发生 gc 的时候不会被回收。
- 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
- 弱引用:有用但不是必须的对象,在下一次GC时会被回收。
- 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知)
jvm 有哪些垃圾回收器
Serial:单线程版本收集器,进行垃圾回收的时候会 STW(Stop The World),也就是进行垃圾回收的时候其他的工作线程都必须暂停。
ParNew:Serial 的多线程版本,用于和 CMS 配合使用。
Parallel Scavenge:可以并行收集的多线程垃圾收集器
Serial Old:Serial 的老年代版本,也是单线程。
Parallel Old:Parallel Scavenge 的老年代版本。
CMS收集器
以获取最短回收停顿时间为目标的垃圾回收器(注重服务的响应,希望系统在最短时间段内给用户做出响应),采用标记-清除算法实现的
运行的过程:
- 初始标记:标记 GC ROOT 能关联到的对象,需要 STW;
- 并发标记:从 GCRoots 的直接关联对象开始遍历整个对象图的过程,不需要 STW;
- 重新标记:为了修正并发标记期间,因用户程序继续运作而导致标记产生改变的标记,需要 STW;
- 并发清除:清理删除掉标记阶段判断的已经死亡的对象,不需要 STW。
从整个过程来看,并发标记和并发清除的耗时最长,但是不需要停止用户线程。而初始标记和重新标记的耗时较短,但是需要停止用户线程。总体而言,整个过程造成的停顿时间较短,大部分时候是可以和用户线程一起工作的。
G1 收集器是 JDK9 的默认垃圾收集器,不再区分年轻代和老年代进行回收。
CMS收集器的内存回收过程是与用户线程一起并发执行的。
G1的原理
G1 作为 JDK9 之后的服务端默认收集器,不再区分年轻代和老年代进行垃圾回收。
把内存划分为多个 Region,每个 Region 的大小可以通过 -XX:G1HeapRegionSize 设置,大小为1~32M
对于大对象的存储则衍生出 Humongous 的概念。超过 Region 大小一半的对象会被认为是大对象,而超过整个 Region 大小的对象被认为是超级大对象,将会被存储在连续的 N 个 Humongous Region 中。
G1 在进行回收的时候会在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间优先回收收益最大的 Region。
G1 的回收过程分为以下四个步骤 :
- 初始标记:标记 GC ROOT 能关联到的对象,需要 STW;
- 并发标记:从 GCRoots 的直接关联对象开始遍历整个对象图的过程,扫描完成后还会重新处理并发标记过程中产生变动的对象;
- 最终标记:短暂暂停用户线程,再处理一次,需要 STW;
- 筛选回收:更新 Region 的统计数据,对每个 Region 的回收价值和成本排序,根据用户设置的停顿时间制定回收计划。再把需要回收的 Region 中存活对象复制到空的 Region,同时清理旧的 Region。需要 STW。
总的来说除了并发标记之外,其他几个过程也还是需要短暂的 STW。G1 的目标是在停顿和延迟可控的情况下尽可能提高吞吐量。
怎么选择垃圾回收器
优先调整堆的大小让JVM自适应完成。 如果内存小于100M,使用串行收集器 如果是单核、单机程序,并且没有停顿时间的要求,串行收集器 如果是多CPU、需要高吞吐量、允许停顿时间超过1秒,选择并行或者JVM自己选择 如果是多CPU、追求低停顿时间,需快速响应(比如延迟不能超过1秒,如互联网应用),使用并发收集器 官方推荐G1,性能高。现在互联网的项目,基本都是使用G1。
新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:CMS、Serial Old、Parallel Old
整堆收集器: G1
区别:在新生代中一般采用复制算法,复制算法效率比较高,但是内存利用率低
在老年代中采用的是标记-整理算法
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3
JVM性能调优监控工具
现实企业级Java应用开发、维护中,有时候我们会碰到下面这些问题:
- OutOfMemoryError,内存不足
- 内存泄露
- 线程死锁
- 锁争用(Lock Contention)
- Java进程消耗CPU过高 ......
调优工具
一、jps:虚拟机进程状况工具
二、jstat:虚拟机统计信息监视工具 查看堆内存
三、jmap:Java内存印象工具 堆内存信息 将信息导成dump快照 分析dump用mat
四、jhat:虚拟机堆转储快照分析工具
五、jstack:Java堆栈跟踪工具 top命令可以展示所有的信息
六、jinfo:Java配置信息工具
DK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
jconsole:用于对 JVM 中的内存、线程和类等进行监控;
jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。
深拷贝和浅拷贝
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新
的内存,
浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的
改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
GC是什么?为什么要GC
GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或
者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象
是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方
法。
垃圾回收的优点和缺点
优点:JVM的垃圾回收器都不需要我们手动处理无引用的对象了,这个就是最大的优点
缺点:程序员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收
垃圾回收器的原理是什么?有什么办法手动进行垃圾回收?
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。
通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可
达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空
间。
可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执
行。
简述Java垃圾回收机制
在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,
有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前
堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合
中,进行回收。
Jdk和jre和JVM的区别
Jdk包括了Jre和Jvm,Jre包括了Jvm
Jdk是我们编写代码使用的开发工具包
Jre 是Java的运行时环境,他大部分都是 C 和 C++ 语言编写的,他是我们在编译java时所需要的基
础的类库
Jvm俗称Java虚拟机,他是java运行环境的一部分,它虚构出来的一台计算机,在通过在实际的计
算机上仿真模拟各种计算机功能来实现Java应用程序
看Java官方的图片,Jdk中包括了Jre,Jre中包括了JVM
详细的介绍下程序计数器
1.程序计数器是一块较小的内存空间,它可以看作是:保存当前线程所正在执行的字节码指令的地址
2.由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器,都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为“线程私有”的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。
例子:在java中最小的执行单位是线程,线程是要执行指令的,执行的指令最终操作的就是我们的电脑,就是 CPU。在CPU上面去运行,有个非常不稳定的因素,叫做调度策略,这个调度策略是时基于时间片的,也就是当前的这一纳秒是分配给那个指令的。
详细介绍下Java虚拟机栈
1.Java虚拟机是线程私有的,它的生命周期和线程相同。
2.虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(StackFrame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息
解释:虚拟机栈中是有单位的,单位就是栈帧,一个方法一个栈帧。一个栈帧中他又要存储,局部
变量,操作数栈,动态链接,出口等。
解析栈帧
1.局部变量表:是用来存储我们临时8个基本数据类型、对象引用地址、returnAddress类型。(returnAddress中保存的是return后要执行的字节码的指令地址。)
2.操作数栈:操作数栈就是用来操作的,例如代码中有个 i = 6*6,他在一开始的时候就会进行操作,读取我们的代码,进行计算后再放入局部变量表中去
3.动态链接:假如我方法中,有个 service.add()方法,要链接到别的方法中去,这就是动态链接存储链接的地方。
4.出口:出口是什呢,出口正常的话就是return 不正常的话就是抛出异常落
一个方法调用另一个方法,会创建很多栈帧吗
会创建。如果一个栈中有动态链接调用别的方法,就会去创建新的栈帧,栈中是由顺序的,一个栈帧调用另一个栈帧,另一个栈帧就会排在调用者下面
JVM 调优的参数可以在那设置参数值
可以在IDEA,Eclipse,工具里设置如果上线了是WAR包的话可以在Tomcat设置 如果是Jar包直接:java -jar 是直接插入JVM命令就好了
java-Xms1024m-Xmx1024m...等等等JVM参数-jarspringboot_app.jar&
常用的JVM 调优的参数都有哪些
#常用的设置
- -Xms 设置初始堆的大小,-Xmx 设置最大堆的大小;
- -XX:NewSize 年轻代大小,-XX:MaxNewSize 年轻代最大值,-Xmn 则是相当于同时配置 -XX:NewSize 和 -XX:MaxNewSize 为一样的值;
- -XX:NewRatio 设置年轻代和年老代的比值。如果为3,表示年轻代与老年代比值为 1:3,默认值为2;
- -XX:SurvivorRatio 年轻代和两个 Survivor 的比值。默认值为8,代表比值为 8:1:1;
- -XX:PretenureSizeThreshold 当创建的对象超过指定大小时,直接把对象分配在老年代;
- -XX:MaxTenuringThreshold 设定对象在 Survivor 复制的最大年龄阈值,超过阈值转移到老年代;
- -XX:MaxDirectMemorySize 当 Direct ByteBuffer 分配的堆外内存到达指定大小后,即触发 Full GC。
调优
- 了打印日志方便排查问题最好开启GC日志。开启GC日志对性能影响微乎其微,但是能帮助我们快速排查定位问题。-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:gc.log
- 一般设置 -Xms=-Xmx。这样可以获得固定大小的堆内存,减少 GC 次数和耗时,可以使得堆相对稳定;
- -XX:+HeapDumpOnOutOfMemoryError 让 JVM 在发生内存溢出的时候自动生成内存快照,方便排查问题;
- -Xmn 设置新生代的大小。太小会增加 YGC,太大会减小老年代大小,一般设置为整个堆的1/4到1/3;
- 设置 -XX:+DisableExplicitGC 禁止系统 System.gc()。防止手动误触发 FGC 造成问题。
JVM的GC收集器设置
-xx:+Use xxx GC
xxx 代表垃圾收集器名称
-XX:+UseSerialGC:设置串行收集器,年轻带收集器
-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统
配置自行设置,所以无需再设置此值。
-XX:+UseParallelGC:设置并行收集器,目标是目标是达到可控制的吞吐量
-XX:+UseParallelOldGC:设置并行年老代收集器,JDK6.0支持对年老代并行收集。
-XX:+UseConcMarkSweepGC:设置年老代并发收集器
-XX:+UseG1GC:设置G1收集器,JDK1.9默认垃圾收集器
类加载的机制及过程
程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步
骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统
称为类加载或类初始化。
类加载机制一共有五个步骤,分别是加载、链接、初始化、使用和卸载阶段,这五个阶段的顺序是确定的。
1.加载
加载指的是将类的class文件读入到内存,并将这些静态数据转换成方法区中的运行时数据结构,
并在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需
要类加载器参与。
Java类加载器由JVM提供,是所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加
载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
类加载器,可以从不同来源加载类的二进制数据,比如:本地Class文件、Jar包Class文件、网络
Class文件等等等。
类加载的最终产物就是位于堆中的Class对象(注意不是目标类对象),该对象封装了类在方法区
中的数据结构,并且向用户提供了访问方法区数据结构的接口,即Java反射的接口
2.连接过程
当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把
类的二进制数据合并到JRE中(意思就是将java类的二进制代码合并到JVM的运行状态之中)。类
连接又可分为如下3个阶段。
3.验证:确保加载的类信息符合JVM规范,没有安全方面的问题。主要验证是否符合Class文件格式规范,并且是否能被当前的虚拟机加载处理。
4.准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配 解析:虚拟机常量池的符号引用替换为字节引用过程
3.初始化阶段是执行类构造器() 方法的过程。类构造器()方法是由编译器自动
收藏类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生,代码从上往下执行。
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
虚拟机会保证一个类的() 方法在多线程环境中被正确加锁和同步
描述一下JVM加载Class文件的原理机制
Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工
作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为
这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。
类装载方式,有两种:
1.隐式装载,程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
2.显式装载,通过class.forname()等方法,显式加载需要的类Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。
收集器可以这么分配
Serial/SerialOld
Serial/CMS
ParNew/SerialOld
ParNew/CMS
ParallelScavenge/SerialOld
ParallelScavenge/ParallelOld
G1
新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别
新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
整堆回收器:G1
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回
收器一般采用的是标记-整理的算法进行垃圾回收。
Java堆老年代( Old ) 和新生代 ( Young ) 的默认比例?
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio
来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。
其中,新生代 ( Young ) 被细分为 Eden 和两个 Survivor 区域,Edem 和俩个Survivor 区域比例
是 = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),
但是JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总
是有一块 Survivor 区域是空闲着的。
为什么新生代要分Eden和两个 Survivor 区域
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被
填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选
保证,只有经历15次Minor GC还能在新生代中存活的对象,才会被送到老年代。
设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满
了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space
S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续
的内存空间,避免了碎片化的发生)
Minor GC、Major GC、Full GC区别及触发条件
Minor GC 触发条件一般为:
1.eden区满时,触发MinorGC。即申请一个对象时,发现eden区不够用,则触发一次MinorGC。
2.新创建的对象大小 > Eden所剩空间时触发Minor GCMajor GC和Full GC 触发条件一般为MajorGC通常是跟fullGC是等价的
1.每次晋升到老年代的对象平均大小>老年代剩余空间
2.MinorGC后存活的对象超过了老年代剩余空间
3.永久代空间不足
4.执行System.gc()
5.CMS GC异常
6.堆内存分配很大的对象
Minor GC、Major GC、Full GC是什么
1.. Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快。(一般采用复制算法回收垃圾)
2.Major GC是老年代GC,指的是发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要Minor GC慢的多。(可采用标记清楚法和标记整理法)
3.Full GC是清理整个堆空间,包括年轻代和老年代
讲一下新生代、老年代、永久代的区别
在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。而新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
新生代中一般保存新出现的对象,所以每次垃圾收集时都发现大批对象死去,只有少量对象存活,便采用了复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
老年代中一般保存存活了很久的对象,他们存活率高、没有额外空间对它进行分配担保,就必须采用“标记-清理”或者“标记-整理”算法。
永久代就是JVM的方法区。在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。这个区中的东西比老年代和新生代更不容易回收。
JVM中的永久代中会发生垃圾回收吗
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确
的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
对象什么时候可以被垃圾器回收
当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确
的永久代大小对避免Full GC是非常重要的原因。
Full GC是什么
清理整个堆空间—包括年轻代和老年代和永久代
因为Full GC是清理整个堆空间所以Full GC执行速度非常慢,在Java开发中最好保证少触发Full GC
Java会存在内存泄漏吗?请说明为什么
内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。 但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就java中内存泄露的发生场景。
知道垃圾收集系统吗
程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。
垃圾收集系统是Java的核心,也是不可少的,Java有一套自己进行垃圾清理的机制,开发人员无需手工清理 有一部分原因就是因为Java垃圾回收系统的强大导致Java领先市场
你听过直接内存吗
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致 OutOfMemoryError 异常出现,所以我们放到这里一起讲解。
我的理解就是直接内存是基于物理内存和Java虚拟机内存的中间内存
什么是JVM字节码执行引擎
虚拟机核心的组件就是执行引擎,它负责执行虚拟机的字节码,一般户先进行编译成机器码后执行。 “虚拟机”是一个相对于“物理机”的概念,虚拟机的字节码是不能直接在物理机上运行的,需要JVM 字节码执行引擎- 编译成机器码后才可在物理机上执行。
本地方法栈
1.本地方法栈很好理解,他很栈很像,只不过方法上带了 native 关键字的栈字
2.它是虚拟机栈为虚拟机执行Java方法(也就是字节码)的服务方法
3.native关键字的方法是看不到的,必须要去oracle官网去下载才可以看的到,而且native关键字修
饰的大部分源码都是C和C++的代码。
4.同理可得,本地方法栈中就是C和C++的代码
你能给我详细的介绍java堆吗
java堆(Java Heap)是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。 java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”。
从内存回收角度来看java堆可分为:新生代和老生代。
从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。
无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。
根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
递归的调用自己会创建很多栈帧
递归的话也会创建多个栈帧,就是在栈中一直从上往下排下去
什么是 GC ROOT?有哪些 GC ROOT
通过引用计数法,给对象设置一个引用计数器,每当有一个地方引用他,就给计数器+1,反之则计数器-1,但是这个简单的算法无法解决循环引用的问题
Java 通过可达性分析算法来达到标记存活对象的目的,定义一系列的 GC ROOT 为起点。从起点开始向下开始搜索,搜索走过的路径称为引用链。当一个对象到 GC ROOT没有任何引用链相连的话,则对象可以判定是可以被回收的
可以作为 GC ROOT 的对象包括 :
- 栈中引用的对象;
- 静态变量、常量引用的对象;
- 本地方法栈 native 方法引用的对象。
什么时候会触发 YGC 和 FGC?对象什么时候会进入老年代
当一个新的对象来申请内存空间的时候,如果 Eden 区无法满足内存分配需求,则触发 YGC。使用中的 Survivor 区和 Eden 区存活对象送到未使用的 Survivor 区
如果 YGC 之后还是没有足够空间,则直接进入老年代分配。如果老年代也无法分配空间,触发 FGC,FGC 之后还是放不下则报出 OOM 异常。
YGC 之后,存活的对象将会被复制到未使用的 Survivor 区。如果 S 区放不下,则直接晋升至老年代。
而对于那些一直在 Survivor 区来回复制的对象,通过 -XX:MaxTenuringThreshold 配置交换阈值,默认15次。如果超过次数同样进入老年代
此外,还有一种动态年龄的判断机制,不需要等到 MaxTenuringThreshold 就能晋升老年代。如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代
\