推荐学习链接 :
点击打开链接 孙华强
JDK 发展
1.3 Hotspot 作为默认虚拟机
1.4 基本成型
1.5 加入 泛型/注解/装箱/枚举/可变参数/ForEach循环
1.6 脚本语言的支持/JDBC4.0/开放Java编译器的API
1.7 G1 垃圾回收/动态语言增强/压缩指针/MIO2.0/
1.8 Lambda表达式 (函数式编程) /语法增强/Java类型注解
HotSpot - 是JVM虚拟机的核心
将来JVM的核心会在HotSpot的基础上移植JRockit的特性
JVM 规范
整数表示方式:
原码 二进制表示 第一位为符号表示(0为正,1为负)
反码 符号位不动 原码取反
负数补码 符号位不动 反码+1
正数补码 和原码相同
补码的好处就是直接参与运算时的结果就是真实值
查看int型补码
for int i=0; i<32 i++
(a & 0x80000000>>>i)>>>(31-i)
Java 中float在内存的存储
17.625 =
0-10000011-00011010000000000000000 一共32位
1位:符号位
8位:指数位
23位:有效数位
符号位 * 2*2(指数位次方) * 有效数位
每次方法调用都会创建一个帧 并进行压栈
//在栈上分配内存
-server -Xmx10m -Xms10m
-XX:+DoEscapeAnalysis -XX:+PrintGC
//在对堆上分配内存
-XX:+DoEsca.....+改为-
volatile-起到的作用就是将变量的信息同步带JVM内存
但是 volatile 不能代替锁
一般认为比锁的性能更好
一般用在<语义是否满足条件>
JVM 指令重排
比如在一个简单赋值的过程中 JVM 会进行一定的赋值先后的调整来优化程序
JVM 配置参数
Trace跟踪参数
对GC的跟踪
-verbose:gc 打开GC跟踪日志
-XX:+printGC 打印GC日志信息
//详细信息/时间戳
-Xloggc:log/gc.log 制定位置输出日志文件
堆参数
-Xmx 最大堆
-Xms 最小堆 空间(-Xmx256m)
幸存代 --- 不易过大 --- 1/10
老年代 --- 尽量少的使用
-XX:+HeapDumpOnOutOfMemoryError -内存溢出时导出堆到文件
-XX:+HeapDumpPath -导出路径
永久区的参数分配
能够容纳多少个类类型 类太多导致内存溢出
栈大小分配
通常只有几百K
决定函数调用的深度
每一个线程创建的时候就会分配完空间
(栈空间太小递归调用时会出现栈溢出)
GC
针对堆空间和永久区进行回收
算法 (可达性分析)
引用计数算法 (没有使用)
为每一个对象标记数量 引用+1 释放-1 判断引用为0的时间进行回收
根引用对象标记
//循环引用难以清除
标记清楚算法
标记阶段:通过根节点搜索 标记可达对象与不可达对象
清除阶段:可达不清楚/不可达清除
//清除后碎片太多
标记压缩 (老年代)
标记 - 存活的对象进行复制到一定区域 清除边界以外的内存
复制算法 (老年代)
内存空间分为相等两块 标记 复制到空闲空间 清理所有对象
//浪费内存空间
(老年代做担保空间)
新生空间/复制空间变小/大对象放到老年代 (几次在复制空间中都没有被回收转移到老年代)
分代思想
新生代 (对象存活少)周期特别短的对象 适合复制算法
老年代 (对象存活多)周期特别长的对象 适合标记清楚 / 标记压缩算法
怎么判断垃圾对象
可触及:根节点可以连接到这个对象
可复活:在锁中被引用
不可触及:要回收的对象
Stop-The-Word
全局停顿现象
native代码可以执行但不能与JVM交互
三种情况产生:
Dump线程
死锁检查
堆Dump
1)指针碰撞
将内存逻辑上分为两边,一边是空闲,一边是在用的,指针指向分界点,当需要分配内存时只要一定指针即可
一般用在Serial,PaeNew等垃圾回收器中即堆中的新生代中
2)空闲列表
在内存不规律的情况下,虚拟机必须维护一个列表,用于记录那些内存时可用的,在需要进行分配的时候就从列表中找到足够大小的空间进行分配,并且更新列表
一般用在CMS这种基于Mark-Sweep的垃圾回收器即对中的年老区
2:年轻代与年老代
1)年轻代
区域被分为一个Eden(伊甸园)区和Survivor(存活区)空间大小为 8:1
新对象都会在Eden区创建,每次只使用Eden区和一个Survivor区,当这两个区满了之后就会将还存活的对象复制到另一个空白区(MINOR GC)
BEGIN->将可回收内存复制到空白区(存活区)->清理刚才区域
2)老年代
每当进行一次复制回收的时候,还在年轻代中存活的对象就会加1岁,默认15岁后就到年老代
可以通过-XX:MaxTenuringThreshold=15来设置多少岁后进入年老区
3:标记-清除-整理算法
年老和永久区垃圾收集的方法都是标记-清除-整理算法
当年老代内存不足的话就会触发垃圾收集,这个回收叫做FULL GC.默认是占用了68%后收集
可用参数-XX:CMSInitiatingOccupancyFraction=68自行设置。
4:类加载机制
1)加载 --> 验证/准备/解析 --> 初始化 --> 使用 --> 卸载
获得二进制字节流(从CLASS文件获得)
当一个类加载器收到了类加载的请求,他首先不会尝试自己去加载这个类,而是将这次的请求委派给自己的父类加载器去加载
如果父类加载器依然不能加载,则继续用父加载器的父加载器去加载(有点拗口)。层层如此,如果都不能加载
则最终的结果就是到达顶层——启动类加载器Bootstrap ClassLoader。每一层的类加载器都会根据请求所要加载的类去自己应该加载的
目录中搜索有没有对应的类和查看该类是否已经被加载。如果有,那么该层加载器加载并返回
如果到达了启动类加载器后还是不能加载,那么就由最初接收到类加载请求的那个类加载器进行加载。
那么如果我们自己写一个类也叫作String,那么当加载的时候,就会先检查出该类已经被加载了
所以不再允许其他的加载器重新加载,因此我们自己写的String类也就不能用了,所以我们不能自己写一个叫做String的类。
3)准备-解析-初始化
装在之后就是校验 (确保二进制流的合法性) --> 文件格式校验/元数据校验/符号引用校验
校验之后就是
准备阶段 --> 为变量分配内存/设置初始值(仅仅针对类变量 全局变量) !设置的是初始默认值
解析阶段 --> 将符号引用替换为直接引用
初始化阶段 --> 这个阶段才会执行类中的代码(静态代码)
这就是为什么静态方法不能调用非静态方法/变量的原因 以及 main 方法为静态的原因
可以进入初始化阶段的几种情况
1:当虚拟机启动的时候,虚拟机会初始化包含main方法的那个类。
2:当初始化一个子类的时候,发现其父类还没有初始化,就会先初始化其父类。
3:当我们使用反射对类进行调用的时候,如何该类没有进行初始化,就会先初始化。在我们用JDBC的时候,我们经常会看到这样一行代码:
Class.forName("com.mysql.jdbc.Driver"); 这就是反射调用,加载该类并且初始化。
5:CLASS - 文件
.class文件是一个由8位二进制构成一个字节的字节码文件,里边的格式都是按照规定好的顺序紧凑的排列在文件中
在.class文件中,它的数据都是以无符号数和表的形式存储的
无符号用来描述一些东西-字符串值/索引/数字/数量值
- 使用u1 u2 u4 u8 来表示1 2 4 8 个字节
表就是有多个无符号数或者其他的表构成的复合型数据结构
JDK 发展
1.3 Hotspot 作为默认虚拟机
1.4 基本成型
1.5 加入 泛型/注解/装箱/枚举/可变参数/ForEach循环
1.6 脚本语言的支持/JDBC4.0/开放Java编译器的API
1.7 G1 垃圾回收/动态语言增强/压缩指针/MIO2.0/
1.8 Lambda表达式 (函数式编程) /语法增强/Java类型注解
HotSpot - 是JVM虚拟机的核心
将来JVM的核心会在HotSpot的基础上移植JRockit的特性
JVM 规范
整数表示方式:
原码 二进制表示 第一位为符号表示(0为正,1为负)
反码 符号位不动 原码取反
负数补码 符号位不动 反码+1
正数补码 和原码相同
补码的好处就是直接参与运算时的结果就是真实值
查看int型补码
for int i=0; i<32 i++
(a & 0x80000000>>>i)>>>(31-i)
Java 中float在内存的存储
17.625 =
0-10000011-00011010000000000000000 一共32位
1位:符号位
8位:指数位
23位:有效数位
符号位 * 2*2(指数位次方) * 有效数位
Java 栈
线程独有 先进后出
保存的内容是一个方法的局部变量/操作数栈/常量池指针每次方法调用都会创建一个帧 并进行压栈
//在栈上分配内存
-server -Xmx10m -Xms10m
-XX:+DoEscapeAnalysis -XX:+PrintGC
//在对堆上分配内存
-XX:+DoEsca.....+改为-
volatile-起到的作用就是将变量的信息同步带JVM内存
但是 volatile 不能代替锁
一般认为比锁的性能更好
一般用在<语义是否满足条件>
JVM 指令重排
比如在一个简单赋值的过程中 JVM 会进行一定的赋值先后的调整来优化程序
JVM 配置参数
Trace跟踪参数
对GC的跟踪
-verbose:gc 打开GC跟踪日志
-XX:+printGC 打印GC日志信息
//详细信息/时间戳
-Xloggc:log/gc.log 制定位置输出日志文件
-XX:+TraceClassLoading 监控类的加载
(在Eclipse中Run As --> Run Configgurations... --> Arguments --> VM arguments 的文本框中填写)
堆参数
-Xmx 最大堆
-Xms 最小堆 空间(-Xmx256m)
-内存区---作用---推荐所占比例-
新生代 --- 处理 --- 3/8幸存代 --- 不易过大 --- 1/10
老年代 --- 尽量少的使用
-XX:+HeapDumpOnOutOfMemoryError -内存溢出时导出堆到文件
-XX:+HeapDumpPath -导出路径
永久区的参数分配
能够容纳多少个类类型 类太多导致内存溢出
栈大小分配
通常只有几百K
决定函数调用的深度
每一个线程创建的时候就会分配完空间
(栈空间太小递归调用时会出现栈溢出)
GC
针对堆空间和永久区进行回收
算法 (可达性分析)
引用计数算法 (没有使用)
为每一个对象标记数量 引用+1 释放-1 判断引用为0的时间进行回收
根引用对象标记
//循环引用难以清除
标记清楚算法
标记阶段:通过根节点搜索 标记可达对象与不可达对象
清除阶段:可达不清楚/不可达清除
//清除后碎片太多
标记压缩 (老年代)
标记 - 存活的对象进行复制到一定区域 清除边界以外的内存
复制算法 (老年代)
内存空间分为相等两块 标记 复制到空闲空间 清理所有对象
//浪费内存空间
(老年代做担保空间)
新生空间/复制空间变小/大对象放到老年代 (几次在复制空间中都没有被回收转移到老年代)
分代思想
新生代 (对象存活少)周期特别短的对象 适合复制算法
老年代 (对象存活多)周期特别长的对象 适合标记清楚 / 标记压缩算法
怎么判断垃圾对象
可触及:根节点可以连接到这个对象
可复活:在锁中被引用
不可触及:要回收的对象
Stop-The-Word
全局停顿现象
native代码可以执行但不能与JVM交互
三种情况产生:
Dump线程
死锁检查
堆Dump
1)指针碰撞
将内存逻辑上分为两边,一边是空闲,一边是在用的,指针指向分界点,当需要分配内存时只要一定指针即可
一般用在Serial,PaeNew等垃圾回收器中即堆中的新生代中
2)空闲列表
在内存不规律的情况下,虚拟机必须维护一个列表,用于记录那些内存时可用的,在需要进行分配的时候就从列表中找到足够大小的空间进行分配,并且更新列表
一般用在CMS这种基于Mark-Sweep的垃圾回收器即对中的年老区
2:年轻代与年老代
1)年轻代
区域被分为一个Eden(伊甸园)区和Survivor(存活区)空间大小为 8:1
新对象都会在Eden区创建,每次只使用Eden区和一个Survivor区,当这两个区满了之后就会将还存活的对象复制到另一个空白区(MINOR GC)
BEGIN->将可回收内存复制到空白区(存活区)->清理刚才区域
2)老年代
每当进行一次复制回收的时候,还在年轻代中存活的对象就会加1岁,默认15岁后就到年老代
可以通过-XX:MaxTenuringThreshold=15来设置多少岁后进入年老区
3:标记-清除-整理算法
年老和永久区垃圾收集的方法都是标记-清除-整理算法
当年老代内存不足的话就会触发垃圾收集,这个回收叫做FULL GC.默认是占用了68%后收集
可用参数-XX:CMSInitiatingOccupancyFraction=68自行设置。
4:类加载机制
1)加载 --> 验证/准备/解析 --> 初始化 --> 使用 --> 卸载
解析阶段不一定会在准备阶段之后就执行,也有可能会在初始化阶段之后,这是为了支持JAVA的动态绑定的特性;(多态)
获得二进制字节流(从CLASS文件获得)
当一个类加载器收到了类加载的请求,他首先不会尝试自己去加载这个类,而是将这次的请求委派给自己的父类加载器去加载
如果父类加载器依然不能加载,则继续用父加载器的父加载器去加载(有点拗口)。层层如此,如果都不能加载
则最终的结果就是到达顶层——启动类加载器Bootstrap ClassLoader。每一层的类加载器都会根据请求所要加载的类去自己应该加载的
目录中搜索有没有对应的类和查看该类是否已经被加载。如果有,那么该层加载器加载并返回
如果到达了启动类加载器后还是不能加载,那么就由最初接收到类加载请求的那个类加载器进行加载。
那么如果我们自己写一个类也叫作String,那么当加载的时候,就会先检查出该类已经被加载了
所以不再允许其他的加载器重新加载,因此我们自己写的String类也就不能用了,所以我们不能自己写一个叫做String的类。
3)准备-解析-初始化
装在之后就是校验 (确保二进制流的合法性) --> 文件格式校验/元数据校验/符号引用校验
校验之后就是
准备阶段 --> 为变量分配内存/设置初始值(仅仅针对类变量 全局变量) !设置的是初始默认值
解析阶段 --> 将符号引用替换为直接引用
初始化阶段 --> 这个阶段才会执行类中的代码(静态代码)
这就是为什么静态方法不能调用非静态方法/变量的原因 以及 main 方法为静态的原因
可以进入初始化阶段的几种情况
1:当虚拟机启动的时候,虚拟机会初始化包含main方法的那个类。
2:当初始化一个子类的时候,发现其父类还没有初始化,就会先初始化其父类。
3:当我们使用反射对类进行调用的时候,如何该类没有进行初始化,就会先初始化。在我们用JDBC的时候,我们经常会看到这样一行代码:
Class.forName("com.mysql.jdbc.Driver"); 这就是反射调用,加载该类并且初始化。
5:CLASS - 文件
.class文件是一个由8位二进制构成一个字节的字节码文件,里边的格式都是按照规定好的顺序紧凑的排列在文件中
在.class文件中,它的数据都是以无符号数和表的形式存储的
无符号用来描述一些东西-字符串值/索引/数字/数量值
- 使用u1 u2 u4 u8 来表示1 2 4 8 个字节
表就是有多个无符号数或者其他的表构成的复合型数据结构