JVM入门
jvm入门
jvm的位置
jre包含jvm
jvm在操作系统上(Windos/Linux/Mac)
操作系统在硬件上(Intel/Spac...)
jvm的体系架构
类加载器和双亲委派
类加载器有哪些?
- 虚拟机自带的加载器
- 启动类(根)加载器:加载jre\lib下的rt包
- 扩展类加载器:加载jre\lib\ext下的包
- 应用程序加载器:加载程序员写的java文件
双亲委派机制:安全
- 类加载器收到类加载的请求
- 将这个请求委托给父类加载器去完成,一直向上委托,直到启动类(根)加载器
- 类加载器检查能否加载这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子类加载器进行加载
- 重复步骤3
Native和方法区(Method Area)和程序计数器(Program Counter Register)
Native
package com.edgar;
public class Demo {
public static void main(String[] args) {
new Thread(()->{
},"my thread name").start();
}
//native:凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层C语言的库!
//会进入本地方法栈
//调用本地方法本地接口 JNI--java native interface
//JNI的作用:扩展java的使用,融合不同的编程语言为Java所用! 最初:C、C++
//Java诞生的时候C、C++横行,想要立足,必须要有调用C、C++的程序
//它在内存区域中专门开辟了一块标记区域:Native Method Stack,登记native方法
//在最终执行的时候,加载本地方法库中本地方法,通过JNI
public native void start0();
}
程序计数器(PC寄存器)
程序计数器(Program Counter Register)是一块较小的内存空间,标记当前线程所在的行号。
特点:
- 线程私有
- JVM规范中唯一没有规定OutOfMemoryError情况的区域
- 如果正在执行的是Native 方法,则这个计数器值为空
方法区
线程共享
静态变量(static)、常量(final)、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关
栈(Stack)
1.栈:数据结构
程序:数据结构+算法:持续学习~
程序:框架+业务逻辑:吃饭~
栈:先进后出、后进先出:水桶效应
水桶有底部,A要出来,B必须先弹出
队列:先进先出(FIFO:First Input First Output)
队列类似管道,底部是空的,A要出来,直接就出来了
栈:栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存就释放了,对于栈来说,不存在垃圾回收问题
一旦线程结束,栈就Over!
栈:8大基本类型(局部变量)+对象引用+实例的方法
栈溢出:StackOverFlowError
java中的基本数据类型一定存储在栈中吗?
一:在方法中声明的变量,即该变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就局部变量只能在方法中有效的原因
在方法中声明的变量可以是基本类型的变量,也可以是引用类型的变量。
(1)当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在JAVA虚拟机栈中
(2)当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在JAVA虚拟机的栈中,该变量所指向的对象是放在堆类存中的。
二:在类中声明的变量是成员变量,也叫全局变量,放在堆中的(因为全局变量不会随着某个方法执行结束而销毁)。
同样在类中声明的变量即可是基本类型的变量 也可是引用类型的变量
(1)当声明的是基本类型的变量其变量名及其值放在堆内存中的
(2)引用类型时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。引用变量名和对应的对象仍然存储在相应的堆中
三种jvm
-
Sun公司HotSpot
Java HotSpot(TM) Client VM (build 25.65-b01, mixed mode)
-
BEA
JRockit
-
IBM
J9 VM
堆(Heap)
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的
类加载器读取了类文件后,一般会把什么文件放在堆中?类,方法,常量,变量~,保存我们所有引用类型的真
实对象;
堆内存中还要细分为三个区域:
- 新生区(伊甸园区,两个幸存区)yooung/new
- 养老区 old
- 永久区 perm
GC垃圾回收,主要在伊甸园区快满了和养老区快满了的情况下触发
假设内存满了,报OOM(OutOfMemoryError),堆内存不够!
在JDK8以后,永久存储区改了个名字(元空间);
新生区
- 类:诞生和成长的地方,甚至死亡
- 伊甸园区:所有对象都是在伊甸园区new出来的
- 幸存区(0,1)
养老区
幸存区的对象经过多次gc之后始终存活,进入养老区
永久区
这个区域常驻内存的。用来存放jdk自身携带的Class对象。interface元数据,存储的是java运行时的一些环境或
类信息,这个区域不存在垃圾回收!关闭VM虚拟机就是释放这个区域的内存
一个启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用。大量动态生成的反射类不断被加载。直到
内存满,就会出现OOM;
- jdk1.6之前:永久代,常量池在方法区
- jdk1.7:永久代,但是慢慢的退化了,
去永久代
,常量池在堆中 - jdk1.8之后:无永久代,常量池在元空间。
堆内存调优
package com.edgar;
public class Demo02 {
public static void main(String[] args) {
//返回虚拟机试图使用的最大内存
long max = Runtime.getRuntime().maxMemory();
//返回jvm的初始化总内存
long total = Runtime.getRuntime().totalMemory();
System.out.println("max="+max+"字节"+(max/(double)1024/1024)+"MB");
System.out.println("total="+total+"字节"+(total/(double)1024/1024)+"MB");
//默认情况下:分配的总内存 是电脑内存的1/4,而初始内存为1/64
//OOM如何处理
//1.尝试扩大堆内存看结果 -Xms10000m -Xmx10000m -XX:+PrintGCDetails
//2.分析内存,看一下哪个地方出现了问题(专业工具)
}
}
在一个项目中,突然出现了OOM故障,那么该如何排除~研究为什么出错
- 能够看到代码第几行报错:内存快照分析工具,MAT,Jprofile
- Debug,一行行分析代码(不推荐)
MAT,Jprofile的作用:
- 分析Dump内存空间,快速定位内存泄漏
- 获得堆中的数据
- 获得大的对象~
注意:控制台其实有OOM报错信息,线上项目可以看日志
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.edgar.Demo03.<init>(Demo03.java:7)
at com.edgar.Demo03.main(Demo03.java:14)
GC
jvm在进行GC时,并不是对这三个区域统一回收。大部分时候,回收的是新生代~
- 新生代
- 幸存区(from,to)
- 老年区
GC两种类:轻GC(普通的GC)、重GC(全局的GC)
轻GC:主要清理新生代,偶而当幸存区满了,会去清理幸存区
重GC:全部清理(新生代、幸存区、老年区)
GC题目:
- JVM的内存模型和分区~详细到每个区放什么?
- 堆里面的分区有哪些?Eden,from,to,老年区,说说他们的特点!
- GC的算法有哪些?标记清楚法,标记压缩,复制算法,引用计数器,怎么用的
- 轻GC和重GC分别在什么时候发生?
GC常用算法:
引用计数法
C对象被用到的次数为0,被清除
复制算法
- 好处:没有内存碎片
- 坏处:浪费了空间~:多了一半空间永远是空to。假设对象100% 存活(极端情况),复制消耗资源大
复制算法最佳使用场景:对象存活度较低的时候;新生区~
标记清除压缩算法
总结
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记清除算法=标记压缩算法>复制算法
思考一个问题:难道没有最优算法嘛?
答案:没有,没有最好的算法----->GC:分代收集算法
年轻代:
- 存活率低
- 复制算法!
老年代:
- 区域大,存活率高
- 标记清除+标记压缩算法混合实现 (内存碎片不多,可多次清除,再压缩)
深入理解JVM可去看书<<深入理解java虚拟机>>--周志明