点击上方"程序员历小冰",选择“置顶或者星标”css
你的关注意义重大!
java
JVM都是进阶时必须迈过的坎。无论是工做仍是面试中,JVM都是必考题。
若是不懂JVM的话,薪酬会很是吃亏(近70%的面试者挂在JVM上了)。
请你谈谈你对JVM的理解?c++
JVM类加载器是怎么样的?有几种?程序员
什么是OOM,什么是StackOverFlowError? 怎么分析?web
JVM经常使用调优参数有哪写?面试
GC有几种算法?分别是怎么执行的?算法
你知道JProfiler吗,怎么分析Dump文件?编程

Java虚拟机
!!
一、什么是JVM?在哪?
JVM本质上是一个
程序
,它能识别.class
字节码文件(里面存放的是咱们对.java
编译后产生的二进制代码),而且可以解析它的指令,最终调用操做系统上的函数,完成咱们想要的操做!安全关于Java语言的
跨平台性
,就是由于JVM,咱们能够将其想象为一个抽象层,只要这个抽象层JVM正确执行了.class
文件,就能运行在各类操做系统之上了!这就是一次编译,屡次运行
微信
JVM是运行在操做系统之上的,它与硬件没有直接的交互

二、JVM、JRE、JDK 的关系
JDK = JRE + javac/java/jar 等指令工具
JRE = JVM + Java基本类库
三、JVM体系结构
类装载器子系统
运行时数据区
执行引擎
本地方法接口
垃圾收集模块
方法区是一种特殊的堆
栈里面不会有垃圾,用完就弹出了,不然阻塞了main方法
垃圾几乎都在堆里,因此JVM性能调优%99都针对于堆
四、三种JVM(了解)
HotSpot
(咱们都用的这个)

JRockit
J9 VM
五、类加载器
.Class
字节码文件
一、回顾new对象的过程
public class Student {
//私有属性
private String name;
//构造方法
public Student(String name) {
this.name = name;
}
}
//运行时,JVM将Test的信息放入方法区
public class Test{
//main方法自己放入方法区
public static void main(String[] args){
//s一、s二、s3为不一样对象
Student s1 = new Student("zsr"); //引用放在栈里,具体的实例放在堆里
Student s2 = new Student("gcc");
Student s3 = new Student("BareTH");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
//class一、class二、class3为同一个对象
Class<? extends Student> class1 = s1.getClass();
Class<? extends Student> class2 = s2.getClass();
Class<? extends Student> class3 = s3.getClass();
System.out.println(class1.hashCode());
System.out.println(class2.hashCode());
System.out.println(class3.hashCode());
}
}
s一、s二、s3的hashcode是不一样的,由于是三个不一样的对象,对象是具体的
class一、class二、class3的hashcode是相同的,由于这是类模板,模板是抽象的
首先Class Loader读取字节码
.class
文件,加载初始化生成Student模板类
经过
Student模板类
new出三个对象

那么Class Loader具体是怎么执行咱们的.class
字节码文件呢,这就引出了咱们类加载器~
二、类加载器的类别
咱们编写这样一个程序
c++
编写,加载java
核心库java.*
,构造拓展类加载器
和应用程序加载器
。根加载器
加载拓展类加载器
,而且将拓展类加载器
的父加载器设置为根加载器
,而后再加载
应用程序加载器
,应将应用程序加载器
的父加载器设置为拓展类加载器
因为引导类加载器涉及到虚拟机本地实现细节,咱们没法直接获取到启动类加载器的引用;这就是上面那个程序咱们第三个结果为
null
的缘由。加载文件存在位置

java
编写,加载扩展库,开发者能够直接使用标准扩展类加载器。java9以前为
ExtClassloader
,Java9之后更名为PlatformClassLoader
加载文件存在位置

java
编写,加载程序所在的目录
2. 是Java默认
的类加载器
java
编写,用户自定义的类加载器,可加载指定路径的
class
文件
六、双亲委派机制
一、什么是双亲委派机制
类加载器收到类加载的请求
将这个请求向上委托给父类加载器去完成,一直向上委托,直到根加载器
BootstrapClassLoader
根加载器检查是否可以加载当前类,能加载就结束,使用当前的加载器;不然就抛出异常,通知子加载器进行加载;自加载器重复该步骤。
二、做用
举个例子:咱们重写如下java.lang包下的String类
双亲委派机制
起的做用,当类加载器委托到
根加载器
的时候,
String类
已经被
根加载器
加载过一遍了,因此不会再加载,从必定程度上防止了危险代码的植入!!
-
1. 防止重复加载同一个
.class
。经过不断委托父加载器直到根加载器,若是父加载器加载过了,就不用再加载一遍。保证数据安全。
2. 保证系统核心.class
,如上述的String类
不能被篡改。经过委托方式,不会去篡改核心.class
,即便篡改也不会去加载,即便加载也不会是同一个.class
对象了。不一样的加载器加载同一个.class
也不是同一个class
对象。这样保证了class
执行安全。
七、沙箱安全机制
什么是沙箱?
沙箱
(sandbox)
1. 沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,而且严格限制代码对本地系统资源访问,经过这样的措施来保证对代码的有效隔离,防止对本地系统形成破坏。
沙箱主要限制系统资源访问,系统资源包括CPU、内存、文件系统、网络。不一样级别的沙箱对这些资源访问的限制也能够不同。
java中的安全模型演进
本地代码
和
远程代码
两种
本地代码
可信任
,能够访问一切本地资源。远程代码
不可信信
在早期的Java实现中,安全依赖于沙箱 (Sandbox) 机制。
Java1.1
版本中,针对安全机制作了改进,增长了
安全策略
,容许用户指定代码对本地资源的访问权限。

Java1.2
版本中,再次改进了安全机制,增长了
代码签名
。
不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不一样的运行空间,来实现差别化的代码执行权限控制。

域 (Domain)
的概念。
虚拟机会把全部代码加载到不一样的
系统域
和应用域
系统域
部分专门负责与关键资源进行交互应用域
部分则经过系统域的部分代理来对各类须要的资源进行访问。虚拟机中不一样的受保护域 (Protected Domain),对应不同的权限 (Permission)。存在于不一样域中的类文件就具备了当前域的所有权限,以下图所示

组成沙箱的基本组件
1. 字节码校验器
(bytecode verifier)
2. 类装载器
(class loader)
它防止恶意代码去干涉善意的代码;
它守护了被信任的类库边界;
它将代码纳入保护域,肯定了代码能够进行哪些操做。
从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而没法使用;
因为严格经过包来区分了访问域,外层恶意的类经过内置代码也没法得到权限访问到内层类,破坏代码就天然没法生效。
存取控制器
(access controller):存取控制器能够控制核心API对操做系统的存取权限,而这个控制的策略设定,能够由用户指定。安全管理器
(security manager):是核心API和操做系统之间的主要接口。实现权限控制,比存取控制器优先级高。安全软件包
(security package):java.security下的类和扩展包下的类,容许用户为本身的应用增长新的安全特性,包括:安全提供者
消息摘要
数字签名
加密
鉴别
八、Native本地方法接口
JNI:Java Native Interface
本地接口的做用是融合不一样的编程语言为Java所用,它的初衷是融合C/C++程序
native
:凡是带native关键字的,说明java的做用范围达不到了,会去调用底层c语言的库!进入本地方法栈,调用
本地方法接口JNI
,拓展Java的使用,融合不一样的语言为Java所用
Java诞生的时候C、C++横行,为了立足,必需要能调用C、C++的程序
因而在内存区域中专门开辟了一块标记区域:Native Method Stack,登记Native方法
最终在执行引擎执行的的时候经过JNI(本地方法接口)加载本地方法库的方法
九、PC寄存器
程序计数器
:Program Counter Register
每一个线程都有一个程序计数器,是
线程私有
的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个很是小的内存空间
,几乎能够忽略不计
十、方法区
方法区
:Method Area
方法区是被全部线程共享,全部字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,全部定义的方法的信息都保存在该区域,此区域属于
共享区间
;方法区与Java堆同样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java 虚拟机规范把方法区描述为堆的一个逻辑部分,可是它却有一个别名叫作
Non-Heap(非堆)
,目的应该是与Java 堆区分开来。
1. 方法区中有啥?
静态变量(static)
常量(final)
类信息(构造方法、接口定义)
运行时的
常量池
2. 建立对象内存分析


建立一个对象时,方法区中会生成对应类的抽象模板;还有对应的常量池、静态变量、类信息、常量
咱们经过类模板去new对象的时候
堆中存放实例对象
栈中存放对象的引用,每一个对象对应一个地址指向堆中相同地址的实例对象
十一、栈
栈内存
,主管程序的运行,生命周期和线程同步,线程结束,栈内存就释放了,
不存在垃圾回收
栈:先进后出
队列:先进先出(FIFO)
一、栈中存放啥?
8大基本类型
对象引用
实例的方法
二、栈运行原理
栈表示Java方法执行的内存模型
每调用一个方法就会为每一个方法生成一个
栈帧(Stack Frame)
,每一个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。程序正在执行的方法必定在栈的顶部

三、堆栈溢出StackOverflowError
public class Test {
public static void main(String[] args) {
new Test().a();
}
public void a() {
b();
}
public void b() {
a();
}
}
十二、堆
Heap
,一个JVM只有一个堆内存(栈是线程级的),堆内存的大小是能够调节的

一、堆中有啥?
二、堆内存详解

一、Young 年轻代
对象诞生、成长甚至死亡的区
Eden Space(伊甸园区)
:全部的对象都是在此new出来的Survivor Space(幸存区)
幸存0区
(From Space
)(动态的,From和To会互相交换)幸存1区
(To Space
)
Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1
。
二、Tenured 老年代
三、Perm 元空间
存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭虚拟机就会释放这个区域内存!
这个区域常驻内存,用来存放JDK自身携带的Class对象、Interface元数据。
jdk1.6以前:
永久代
jdk1.7:
永久代
慢慢退化,去永久代
jdk1.8以后:
永久代
更名为元空间
三、什么是OOM?
内存溢出
java.lang.OutOfMemoryError
分配的太少
用的太多
用完没释放
四、GC垃圾回收
GC垃圾回收,主要在年轻代和老年代
伊甸园区
假设
伊甸园区
只能存必定数量的对象,则每当存满时就会触发一次轻GC(Minor GC)
轻GC
清理后,有的对象可能还存在引用,就活下来了,活下来的对象就进入幸存区
;有的对象没用了,就被GC清理掉了;每次轻GC
都会使得伊甸园区
为空若是
幸存区
和伊甸园
都满了,则会进入老年代
,若是老年代
满了,就会触发一次重GC(FullGC)
,年轻代+老年代
的对象都会清理一次,活下的对象就进入老年代
若是
新生代
和老年代
都满了,则OOM
OOM
1三、堆内存调优
一、查看并设置JVM堆内存
堆内存
public class Test {
public static void main(String[] args) {
//返回jvm试图使用的最大内存
long max = Runtime.getRuntime().maxMemory();
//返回jvm的初始化内存
long total = Runtime.getRuntime().totalMemory();
//默认状况下:分配的总内存为电脑内存的1/4,初始化内存为电脑内存的1/64
System.out.println("max=" + max / (double) 1024 / 1024 / 1024 + "G");
System.out.println("total=" + total / (double) 1024 / 1024 / 1024 + "G");
}
}
JVM最大分配内存为电脑内存的1/4
JVM初始化内存为电脑内存的1/64
VM options
中能够指定
jvm试图使用的最大内存
和
jvm初始化内存
大小
-Xms1024m -Xmx1024m -Xlog:gc*
-Xmx
用来设置jvm试图使用的最大内存
,默认为1/4-Xms
用来设置jvm初始化内存
,默认为1/64-Xlog:gc*
用来打印GC垃圾回收信息


二、怎么排除OOM错误?
1. 尝试扩大堆内存看结果
jvm试图使用的最大内存
和jvm初始化内存
大小
2. 利用内存快照工具JProfiler
MAT(Eclipse)
JProfiler
分析Dump内存文件,快速定位内存泄漏
得到堆中的文件
得到大的对象
…
3. 什么是Dump文件?如何分析?
进程
的内存镜像
,能够把程序的执行状态
经过调试器保存到dump文件中
import java.util.ArrayList;
public class Test {
byte[] array = new byte[1024 * 1024];//1M
public static void main(String[] args) {
ArrayList<Test> list = new ArrayList<>();
int count = 0;
try {
while (true) {
list.add(new Test());
count++;
}
} catch (Exception e) {
System.out.println("count=" + count);
e.printStackTrace();
}
}
}
OOM
接下来咱们设置如下堆内存,并附加生成对应的dump文件
的指令
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
-XX:+HeapDumpOnOutOfMemoryError
表示当JVM发生OOM时,自动生成DUMP文件。
再次点击运行,下载了对应的Dump
文件

Show in Explorer
一直点击上级目录,直到找到.hprof
文件,与src
同级目录下
咱们双击打开,能够看到每块所占的大小,便于分析问题
点击Thread Dump
,里面是全部的线程,点击对应的线程能够看到相应的错误,反馈到具体的行,便于排错
每次打开Dump文件查看完后,建议删除
,能够在idea中看到,打开文件后生成了不少内容,占内存,建议删除
附:安装Jprofiler教程

2.下载客户端 https://www.ej-technologies.com/download/jprofiler/files




打开IDEA的设置,找到Tools里面的JProfiler,没有设置位置则设置位置
此时则所有安装完成!
1四、GC垃圾回收
一、回顾
Garbage Collection:垃圾回收
JVM在进行GC时,并非对
年轻代
、老年代
统一回收;大部分时候,回收都是在年轻代
GC分为两种:
轻GC(清理年轻代)
重GC(清理年轻代+老年代)
二、GC算法
一、引用计数算法(不多使用)
每一个对象在建立的时候,就给这个对象绑定一个计数器。
每当有一个引用指向该对象时,计数器加一;每当有一个指向它的引用被删除时,计数器减一。
这样,当没有引用指向该对象时,该对象死亡,计数器为0,这时就应该对这个对象进行垃圾回收操做。

二、复制算法
年轻代
(
幸存0区
和
幸存1区
)
当Eden区满的时候,会触发
轻GC
,每触发一次,活的对象就被转移到幸存区,死的就被GC清理掉了,因此每触发轻GC
时,Eden区就会清空;对象被转移到了幸存区,幸存区又分为
From Space
和To Space
,这两块区域是动态交换的,谁是空的谁就是To Space,而后From Space
就会把所有对象转移到To Space
去;那若是两块区域都不为空呢?这就用到了
复制算法
,其中一个区域会将存活的对象转移到令一个区域去,而后将本身区域的内存空间清空,这样该区域为空,又成为了To Space
;因此每次触发
轻GC
后,Eden区清空,同时To区也清空了,全部的对象都在From区
这也就是幸存0区
和幸存1区
总有一块为空的缘由
浪费了内存空间(浪费了幸存区一半空间)
对象存活率较高的场景下(好比老年代那样的环境),须要复制的东西太多,效率会降低。
年轻代
三、标记–清除算法
标记阶段:这个阶段内,为每一个对象更新标记位,检查对象是否死亡;
清除阶段:该阶段对死亡的对象进行清除,执行 GC 操做。

四、标记–整理算法
标记-整理法
是
标记-清除法
的一个改进版。
标记-清楚-压缩法
标记阶段,该算法也将全部对象标记为存活和死亡两种状态;
不一样的是,在第二个阶段,该算法并无直接对死亡的对象进行清理,而是将全部存活的对象整理一下,放到另外一处空间,而后把剩下的全部对象所有清除。

标记清除
,到达必定量的时候再压缩.
总结
思考:有没有最优的算法?
分代收集算法
对象存活率低
用复制算法
区域大,对象存活率高
用
标记清除+标记压缩
混合实现
结束!
做者: Baret H
原文连接:http://i8n.cn/iWLG4r
-关注我
本文分享自微信公众号 - 程序员历小冰(gh_a1d0b50d8f0a)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。