序章 JVM探究
对JVM的理解?
java文件经过javac的编译形成class文件,进入JVM虚拟机运行
运用条件模式
java8虚拟机和之前的变化?
OOM?
栈溢出StackOverFlowError?怎么分析
JVM常用调优参数有哪些?
扩大运行时内存
内存快照如何抓取,怎么分析Dump文件?
JVM中,类加载器你的认识?
rt-jar ext application
问题:
JVM的位置
JVM的体系结构
类加载器
双亲委派机制
沙箱安全机制
Native
PC寄存器
方法区
栈
三种JVM
堆
新生区、老年区
永久区
堆内存调优
GC 垃圾回收器
常用算法:复制、标记、标记压缩、标记清除
JMM
总结
一、JVM的位置
JVM(由C编写)运行在操作系统之上,互相交互
java编译后运行在JVM上

二、JVM的体系结构

三、类加载器

ClassLoader作用:加载Class文件
new 对象:将抽象类变为具体实例;
实例引用在栈中(数据存放地址);数据(具体信息)放在堆中
类是模板,抽象的;对象是具体的
不同对象,哈希值一定要不一样;同一对象,哈希值一定一样;哈希值一样,不一定是同一对象(哈希值通过计算,存在巧合)
虚拟机自带加载器;
启动类(根)加载器BootStrap;
扩展类加载器 ExtClassLoader;是AppClassLoader的父类,在扩展包 \jre\lib\ext
ExtClassLoader的父类java获取不到,为rt.jar
应用程序(系统)加载器 AppClassLoader;
四、双亲委派机制
安全机制(防止故意修改上层内容,例:java.lang):从下层向上查找,最上层没有,再向下,直到找到最开始出现的位置
AppClassLoader--》ExtClassLoader--》BootStrap
类加载器收到类加载的请求;该请求委托给父类加载器完成,一直向上委托,直到启动类加载器;启动加载器检查是否能加载当前类,能,结束,使用当前加载器;否则,抛出异常,通知子加载器加载,重复该步骤;
经典错误:Class Not Found
Null:java调用不到,底层由C和C++编写
java=C++-- 由C++去掉繁琐东西,指针和内存管理
五、沙箱安全机制(了解)
java安全模型核心就是java沙箱(sandbox),沙箱是一个限制程序运行的环境。沙箱机制将java代码限定在虚拟机的指定运行范围中,严格限制代码对本地资源访问,保证对代码的有效隔离,防止对本地系统的破坏。
沙箱主要限制系统资源访问!包括CPU、内存、系统文件、网络,根据对资源访问的限制,有不同的沙箱级别。所有java程序运行都可以指定沙箱,定制安全策略,
JDK1.0安全模型,java将执行程序分成本地代码和远程代码;本地代码默认可信任,访问一切本地资源;远程代码,不受信任,安全依赖沙箱机制。
JDK1.1安全模型,1.0严格的安全机制为程序功能扩展带来障碍,如远程代码访问本地文件无法实现;因此1.1改进安全机制,增加了安全策略,允许用户指定代码对本地资源的访问权限。
JDK1.2安全模型,再次改进,增加了代码签名,不论本地代码还是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间(不同的权限组和沙箱),实现差异化代码执行权限控制。
JDK1.6安全模型,最新的安全机制实现,引入域(Domain)。JVM将所有代码加载到不同的系统和应用域,系统域部分专门负责与关键资源交互;各个应用域部分通过系统域部分代理来对各种需要的资源进行访问。不同的受保护域(Protected Domain),对应不一样的权限(Permission),对于不同域中的类文件,就具有了当前域的全部权限。(windows系统)(jvm内存溢出行为会被限制进行)
组成沙箱的基本组件:
字节码校验器(bytecode verifier):确保java类文件遵循java语言规范。帮助java程序实现内存保护。并不是所有类文件都会经过字节码校验,如核心类(java、javax)
类装载器(class loader):在三个方面对java沙箱起作用,
防止恶意代码干涉善意代码
守护被信任的类库边界
将代码归入保护域,确定代码可以进行的操作(如外部代码不可以调c)
采用双亲委派机制:严格通过包来区分访问区域,外层恶意类通过内置代码也无法获得权限访问到内层类,无法破坏代码
存取控制器(access controller):可以控制核心API对操作系统的存取权限,该控制策略设定可以由用户指定
安全管理器(security manager):核心API和操作系统间主要接口,实现权限控制,比存取控制器优先级高
安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:
安全提供者
消息摘要
数字签名 keytools
加密
鉴别
六、Native(掌握)
native:凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层C语言的库!
进入本地方法栈
调用本地方法接口:JNI---- Java Native Interface
JNI作用:扩展java的使用,融合不同的编程语言为java所用。最初目的:融合C、C++(当初二者横行)
内存区域中专门开辟了一块标记区域:Native Method Stack,登记native方法
最终执行的时候,加载本地方法库中的方法需要JNI
调用其他语言的方法:
通过接口调用方法:Socket..WebService..http
Native应用实例:(企业级应用中较少)
java程序驱动打印机
管理系统
Robot
混合语言实例:
球球爱心网:--》输入(PHP)--》NodeJS--》Socket--》C++--》刷爱心
七、PC寄存器
程序计数器:Program Counter Register
每个线程都有一个程序计数器,是线程私有的,本质是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,即将要执行的指令代码),执行引擎读取下一条指令是一个非常小的内存空间,可忽略不计
八、方法区
Method Area :方法区
方法区被所有线程共享,所有字段和方法字节码,以及一些特殊的方法,如构造函数,接口代码也在此定义,所有定义的方法的信息都保存在此区域,属于共享空间
静态变量static、常量final、类信息Class模板(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关
public class Test{
//运行时的常量池存在方法区中
private int a;
private String name = "qing";
public static void main(String[] args){
Test test = new Test();
//实例变量存在堆内存中
test.a=1;
test.name="hihihi";
}
}
九、栈:数据结构
程序=数据结构+算法 (持续学习)
程序=框架+业务逻辑(仅吃饭)
栈:先进后出、后进先出,如:弹夹压子弹;
栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存释放,对于栈类来说,不存在垃圾回收问题;
一旦线程结束,栈便Over;
栈存放内容:
8大基本类型+对象引用+实例方法
队列:先进先出(FIFO:First Input First Output)
喝多了吐,栈;吃多了拉,队列;
1.为什么main()方法先执行,最后结束
main方法执行后最先被压入栈内,随后是其他方法,栈是先进后出,当其他方法弹出栈后,main才会弹出,程序执行结束。
2.栈内存溢出:StackOverflowError
死循环中,方法被无限的压入栈中,无法弹出,会超出栈的存储极限,造成错误。
例:A调B,B调A;A和B轮番被压入栈中ABABAB;该情况无法跳出结束;
3.栈运行原理:栈帧

栈满了:StackOverflowError
4.栈+堆+方法区 的交互关系
java本质是值传递

5.栈中存的内容,怎么存?
待完善
6.画出一个对象实例化的过程在内存中:
待完善
十、三种JVM
sun公司: HotSpot(学习此版本) Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)
BEA JRockit
IBM J9 VM:平台硬件系统绑定,有局限
十一、堆
Heap:一个JVM只有一个堆内存,堆内存的大小是可调节。
程序运行后,可以调节堆内存参数;
类加载器读取类文件后,堆中存放类、方法、常量、变量,保存所有引用对象的真实对象;
堆内存中细分三个区域:
新生区(伊甸园区) Young/New
养老区 old
永久区 Perm

GC垃圾回收,主要是在伊甸园区和养老区;
假设内存满了,OOM,堆内存不够(无限new新生区);
JDK8以后,永久区Perm变更为元空间
十二、新生区、老年区
新生区:
类诞生和成长的地方,甚至死亡;
伊甸园:所有对象都是在伊甸园区new出来的
每次GC后,都会将活的对象移到幸存区中;GC结束后,伊甸园是空的
幸存区(0,1):
伊甸园存储对象满,启动轻GC,存活对象(还存在引用)(包括1区中存活的)进入幸存0区
0和1区之间动态互换(from to);每次清理都会换位置;
谁空谁是to;二者必有一空;
养老区:
养老区全满后,触发重GC,新生区和养老区都进行清理,存活者进入养老区
一个对象经历15次GC(默认值)还存活,进入养老区
-XX:MaxTenuringThreshold=5
新生区和养老区都满后,触发OOM
99%对象都是临时对象;
十三、永久区
jdk1.6之前:永久代,常量池在方法区
jdk1.7:永久代,退化,去永久代,常量池在堆中
jdk1.8之后:无永久代,常量池在元空间;
该区域常驻内存,用来存放JDK自身携带的Class对象,Interface元数据,存储的是java运行时的一些环境或类信息,该区域不存在垃圾回收!关闭VM虚拟就会释放这个区域的内存。
一个启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用,大量动态生成的反射类;不断加载,直到内存满,出现OOM;

十四、堆内存调优
OOM(常见问题):
尝试扩大堆内存看结果
分析内存,看问题具体位置(专业工具)
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
-Xms8m -Xmx8m -XX:+PrintGCDetails
package com.yanfeng;
/**
* OOM分析:
* 扩大堆内存
*/
public class Demo {
public static void main(String[] args) {
//返回虚拟机试图使用的最大内存
long max = Runtime.getRuntime().maxMemory();
//返回jvm的总内存
long total = Runtime.getRuntime().totalMemory();
//max=1884815360字节 1797.5MB
System.out.println("max="+max+"字节\t"+(max/(double)1024/1024)+"MB");
//total=128974848字节 123.0MB
System.out.println("total="+total+"字节\t"+(total/(double)1024/1024)+"MB");
//默认情况:分配总内存 是电脑内存的1/4,初始化内存:1/64
//-Xms1024m -Xmx1024m -XX:+PrintGCDetails
/**
max=1029177344字节 981.5MB
total=1029177344字节 981.5MB
Heap
PSYoungGen total 305664K, used 15729K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
eden space 262144K, 6% used [0x00000000eab00000,0x00000000eba5c420,0x00000000fab00000)
from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
to space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
ParOldGen total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
Metaspace used 3162K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 344K, capacity 388K, committed 512K, reserved 1048576K
*/
// 新生区305664K+ 永久区699392K = 981.5MB
}
}
十五、使用Jprofiler工具分析OOM原因
1.项目中的OOM问题,排除方式和错误原因
找到代码出错行数:内存快照分析工具,MAT(eclipce集成工具)、Jprofiler
Debug,分析代码
2.MAT,Jprofiler作用:
分析Dump内存文件,快速定位内存泄漏;
获得堆中数据;
获得大的对象;
3.Jprofiler安装
idea下载插件:Jprofiler
安装Jprofiler软件
重启idea后,在settings--》Tools--》Jprofiler
绑定E:\Program Files\jprofiler9\bin\jprofiler.exe

存在OOM问题的类,设置-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError,生成问题汇总文件(在工程项目文件夹下),通过JProfile软件进行精确的问题定位;
主要看Biggest Objects中的占用信息;快速定位问题出现处
// -Xms 设置初始化内存分配大小,1/64
// -Xmx 设置最大分配内存,默认1/4
// -XX:+PrintGCDetails //打印GC垃圾回收信息
// -XX:+HeapDumpOnOutOfMemoryError //oom DUMP
//-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
public class Demo01 {
byte[] array = new byte[1*1024*1024];//1m
public static void main(String[] args) {
ArrayList<Demo01> list = new ArrayList<>();
int count = 0;
try {
while (true){
list.add(new Demo01());//问题所在
count++;
}
} catch (Exception e) {
System.out.println("count="+count);
e.printStackTrace();
}
}
}
十六、GC 垃圾回收器
JVM进行GC时,并不是对三个区域统一回收,大部分时候,回收都是新生代。
轻GC、重GC(全局GC)
1.引用计数法(JVM中一般不采用)
每个对象分配一个计数器,计算引用次数,没有引用的对象会被清理出去;计数器本身也有消耗,不高效
2.GC之复制算法(年轻区使用)
原理:
每次GC清理eden和from,会将还存活的对象复制到to,清理eden和from,最后from和to区互换;
好处:没有内存碎片
坏处:浪费内存空间,有一般空间(to区)是空的
极端情况:100%对象存活;内存地址全部重做,消耗过大
复制算法最佳使用场景:对象存活度较低时:新生区
3.GC之标记压缩清除算法
3.1清除算法
原理:
扫描所有对象,标记存活对象;
扫描对象,清除未标记对象;
优点:
不需要额外空间;
缺点:
两次扫描,严重浪费时间(2次扫描);
会产生内存碎片,查找对象需要hash定位(存储位置零散)
3.2标记压缩
压缩:防止内存碎片产生,再次扫描,向一端移动存活的对象;

十七、总结
内存效率:复制算法(空间需求大)》标记清除算法》标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记压缩算法》标记清除算法
内存利用率:标记压缩算法=标记清除算法》复制算法
1.是否有最优算法?
否,没有最好的算法,只有最合适的---》GC:分代收集算法
年轻代:
存活率低
复制算法
老年代
区域大:存活率低
标记清除(内存碎片不是太多)+标记压缩 混合实现
按线索学习jvm
二十、GC题目
JVM的内存模型和分区,详细到每个区放什么?
堆里面分区有什么?说说他们特点!
eden
form to
老年区
GC算法有哪些?怎么用
标记清除法,标记整理(压缩),复制算法,引用计数器
轻GC和重GC分别在什么时候发生?