本系列相关链接
尚硅谷 宋红康 JVM教程_01_内存与垃圾回收篇——01 (20210103-20210110)
https://blog.csdn.net/wei198621/article/details/112128852
尚硅谷 宋红康 JVM教程_01_内存与垃圾回收篇——02 (20210111-20210117)
https://blog.csdn.net/wei198621/article/details/112389917
尚硅谷 宋红康 JVM教程_02_字节码与类的加载篇 (20210118~ )
https://blog.csdn.net/wei198621/article/details/112760463
todo 3 , 4
JVM参数列表:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
08 堆
P66-堆空间的概述_进程中堆的唯一性 15:28
方法区与堆,都是进程共享的,一个进程对应一个 JVM 实例
一个进程中的多个线程共享 堆及方法区,
每个线程,有一组 虚拟机栈、本地方法栈、程序计数器
Java堆区在JVM启动的时候就创建了,其空间大小就确定了。是JVM管理的最大内存空间。堆内存大小是可以调节的。
C:\developer_tools\Java\jdk1.8.0_45\bin
本机比宋老师少了几个tab,原因 需要手动安装 visual gc 插件,
VisualVM安装VisualGC插件 过程介绍文章:
https://blog.csdn.net/weixin_45759791/article/details/107332860
P67-堆空间关于对象创建和和GC的概述 17:38
《Java虚拟机规范》中对java堆的描述,所有的对象实例以及数组都应当在运行时分配在堆上面。
数组和对象,可能永远都不会存储在栈上,因为栈中保存引用,这个引用执行对象或者数组在堆中的位置。
方法结束后,堆中的数据不会马上被移除,在垃圾回收的时候才会被回收。
P68-堆的细分内存结构 12:59
现代垃圾收集器,大部分都是基于分带收集理论设计的,堆空间细分为:
JDK7: 1. 新生区 (Yong/New) 2.养老区 (Old/Tenure) 3.永久区 (Perm)
JDK8: 1. 新生区 (Yong/New) 2.养老区 (Old/Tenure) 3.元空间 (Meta)
新生区: 分为 Eden + Survivor
堆中包含: 新生区 + 养老区 + 元空间 虽然元空间在堆里面,但实际是method area 管理,
中国包含: 大陆 + 香港、澳门 + 台——湾 (同理 台——湾)
jdk 1.8 配置jvm 后效果
- -Xms10m -Xmx10m -XX:+PrintGCDetails
-
打印垃圾回收细节
Heap
PSYoungGen total 2560K, used 2031K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 99% used [0x00000000ffd00000,0x00000000ffefbe58,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 0K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffd00000)
Metaspace used 3332K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 358K, capacity 388K, committed 512K, reserved 1048576K
Metaspace
运行时环境改为jdk 1.7 后的效果
- -Xms10m -Xmx10m -XX:+PrintGCDetails
-
打印垃圾回收细节
Heap
PSYoungGen total 3072K, used 1343K [0x00000000ffc80000, 0x0000000100000000, 0x0000000100000000)
eden space 2560K, 52% used [0x00000000ffc80000,0x00000000ffdcfc48,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 0K [0x00000000ff580000, 0x00000000ffc80000, 0x00000000ffc80000)
object space 7168K, 0% used [0x00000000ff580000,0x00000000ff580000,0x00000000ffc80000)
PSPermGen total 21504K, used 2939K [0x00000000fa380000, 0x00000000fb880000, 0x00000000ff580000)
object space 21504K, 13% used [0x00000000fa380000,0x00000000fa65eed8,0x00000000fb880000)
PSPermGen
P69-堆空间大小的设置和查看 21:29
-Xms: 用于设置堆区(新生代+老年代)的起始内存,等价于 -XX:InitialHeapSize
-X : jvm的运行参数
ms: memory start
-Xmx: 最大内存
此页面有相应的参数
https://docs.oracle.com/en/java/javase/11/tools/java.html
默认堆空间大小
默认物理内存的 1/64
默认物理内存的 1/4
package com.tiza.jvm.chapter08;
/**
* @author leowei
* @date 2021/1/9 - 14:08
* 默认物理内存的 1/64
默认物理内存的 1/4
*
*/
public class HeapSpaceInitial {
public static void main(String[] args) {
long initialMemory = Runtime.getRuntime().totalMemory()/1024/1024 ;
long maxMemory = Runtime.getRuntime().maxMemory()/1024/1024;
System.out.println("-Xms:" + initialMemory+ "M");
System.out.println("-Xmx:" + maxMemory+ "M");
System.out.println("系统内存大小为: " + initialMemory * 64.0 /1024 + "G");
System.out.println("系统内存大小为: " + maxMemory * 4.0 /1024 + "G");
}
}
本机示例
-Xms:243M
-Xmx:3611M
系统内存大小为: 15.1875G
系统内存大小为: 14.10546875G
Process finished with exit code 0
手动设置示例: -Xms600m -Xmx600m
开发中建议 将初始与最大设置为相同的值,原因是
jps
jstat -gc *** (进程ID) 查看进程中内存使用情况
S0C S1C S0U S1U EC EU OC OU
------------ 新生区------------------》
S0C: survive Count 幸存区 0 总数
S0U: s0 Used
S1C: survive Count 幸存区 1总数
S1U: s1 Used
EC: Eden Count 伊甸园区
EU: Eden used
------------ 老年区 ------------------》
OC :old count 老年区
OU: old used
-XX:+PrintGCDetails --查看运行时堆参数
-XX:+PrintGCDetails
package com.tiza.jvm.chapter08;
/**
* @author leowei
* @date 2021/1/9 - 14:08
* 默认物理内存的 1/64
默认物理内存的 1/4
*
*/
public class HeapSpaceInitial {
public static void main(String[] args) {
long initialMemory = Runtime.getRuntime().totalMemory()/1024/1024 ;
long maxMemory = Runtime.getRuntime().maxMemory()/1024/1024;
System.out.println("-Xms:" + initialMemory+ "M");
System.out.println("-Xmx:" + maxMemory+ "M");
System.out.println("系统内存大小为: " + initialMemory * 64.0 /1024 + "G");
System.out.println("系统内存大小为: " + maxMemory * 4.0 /1024 + "G");
}
}
P70-OOM的说明与举例 09:40
Throwable
Error
Exception
平时说的异常包括 error + Exception ( 百知教育 胡大大 java基础中有介绍 )
package com.tiza.jvm.chapter08;
import java.util.ArrayList;
import java.util.Random;
/**
* -Xms600m -Xmx600m
* @author leowei
* @date 2021/1/9 - 15:58
*/
public class OOMtest {
public static void main(String[] args) {
ArrayList<Picture> list = new ArrayList<Picture>();
while (true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new Picture(new Random().nextInt(1024*1024)));
}
}
}
class Picture{
private byte[] pixels;
public Picture(int length){
this.pixels =new byte[length];
}
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.tiza.jvm.chapter08.Picture.<init>(OOMtest.java:34)
at com.tiza.jvm.chapter08.OOMtest.main(OOMtest.java:21)
P71-新生代与老年代中相关参数的设置 20:37
生命周期比较短:YoungGen ( Eden ,Survivor0 , Survivor1 )
生命周期比较长: Old Gen
-XX:NewRatio=2, 默认,表示新生代占1,老年代栈2,新生代占整个堆的1/3 ;
修改示例:
-xx:NewRatio=4 , 1, 4, 1/5 ;
默认情况下: Eden:Survivor0:Survivor1 == 8:1:1
-XX:SurvivorRatio=8 —手动指定新生代各块比例为8:1:1
-Xms600m -Xmx600m -XX:SurvivorRatio=8
-xx:-UseAdaptiveSizePolicy —关闭 自适用内存分配策略
-xx:+UseAdaptiveSizePolicy —开启 自适用内存分配策略
jps
jinfo -flag NewRatio 进程ID号 — 查看 老年代/ 新生代 的比例
jinfo -flag SurvivorRatio 进程ID号 – 新生代 各块比例
jstat -gc 4988
C:\Users\wei19>jps
1952 Launcher
1988 Main
4988 OOMtest
6572 Jps
C:\Users\wei19>jinfo -flag NewRatio 4988
-XX:NewRatio=2
C:\Users\wei19>jinfo -flag SurvivorRatio 4988
-XX:SurvivorRatio=8
C:\Users\wei19>jstat -gc 4988
Warning: Unresolved Symbol: sun.gc.metaspace.capacity substituted NaN
Warning: Unresolved Symbol: sun.gc.metaspace.used substituted NaN
Warning: Unresolved Symbol: sun.gc.compressedclassspace.capacity substituted NaN
Warning: Unresolved Symbol: sun.gc.compressedclassspace.used substituted NaN
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
25600.0 25600.0 0.0 0.0 153600.0 81136.7 409600.0 0.0 - - - - 0 0.000 0 0.000 0.000
几乎所有的对象都是在Eden中被New 出来的。
绝大部分的对象都在新生代进行销毁。—80%的对象都是“朝生夕死”
-Xmn 设置新生代最大内存
P72-图解对象分配的一般过程 18:25
tenured [ˈtenjərd] 终身的;长期保有的;
promotion 美 [prəˈmoʊʃn] 促进,增进; 提升,升级;
-XX:MaxTenuringThreshold=15 —晋升的age值,默认15,
总结:
针对幸存者s0,s1 区的总结,复制后有交换,谁空谁是to.
关于垃圾回收:频繁在新生区收集,很少在养老区收集,几乎不再永久区/元空间收集
P73-对象分配的特殊情况 06:38
P74-代码举例与JVisualVM演示对象的分配过程 05:38
major gc (full gc ) : 老年代的垃圾回收
package com.tiza.jvm.chapter08;
import java.util.ArrayList;
import java.util.Random;
/**
* @author leowei
* @date 2021/1/9 - 20:22
*/
public class HeapInstanceTest {
byte[] buffer =new byte[new Random().nextInt(1024*200)];
public static void main(String[] args) {
ArrayList<HeapInstanceTest> list=new ArrayList<HeapInstanceTest>();
while (true){
list.add(new HeapInstanceTest());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
P75-常用优工具概述与Jprofiler的演示 04:01
常用调优工具
JDK命令行 : (jinfo jstat javap jmap )
Eclipse:Memory Analyzer Tool
Jconsole
VisualVM
Jprofiler (客户端,及IDEA 中的插件)
Java Flight Recorder (JMC 中的 )
GCViewer
GC Easy
P76-MinorGC、MajorGC和FullGC的对比 17:26
MinorGC: YoungGC 等同于 MinorGC ,知识新生代(Eden,s0,s1)的垃圾收集
MajorGC: Old GC 等同于 MajorGC, 只是老年代的垃圾收集
FullGC: 收集整个java堆 和 方法区的垃圾收集。
GC 线程, 用户线程, STW (stop the word)
垃圾回收越少越好, GC线程调用的时候会影响用户线程的执行,具体的方式是STW (stop the word) ,调优的工作,具体也是让垃圾少,垃圾回收调用次数少。
妈妈是 GC ,垃圾回收线程
自己是用户线程,
所以收拾屋子的时候,要用户线程停止,妈妈给手机好屋子后,用户线程再执行。
Major GC:
Major GC的速度一般比Minor GC 慢10倍以上,STW 的时间更长
Full GC:
触发FullGC 的原因
老年代空间不足
方法区空间不足
系统调用 System.gc()
…
full GC 在开发中尽量避免。
P77-GC举例与日志分析 09:28
-Xms9m -Xmx9m -XX:+PrintGCDetails
package com.tiza.jvm.chapter08;
import java.util.ArrayList;
/**
*
* -Xms10m -Xmx10m -XX:+PrintGCDetails
*
* @author leowei
* @date 2021/1/9 - 21:20
*/
public class GCTest {
public static void main(String[] args) {
int i=0;
try {
ArrayList<String> list = new ArrayList<>();
String a="leavint tiza .com ";
while (true){
list.add(a);
a=a+a;
i++;
}
} catch (Exception e) {
e.printStackTrace();
System.out.println(" 遍历次数: "+ i );
}
}
}
P78-体会堆空间分代的思想 05:09
P79-总结内存分配策略 12:56
默认有个年龄阈值,15,过15放到 老年代
-XX:MaxTenuringThreshold — 对象晋升老年代的年龄阈值
优先分配到Eden
大对象直接分配到老年代
长期存活的对象分配到老年代
只要有GC ,就会有STW ,
-Xms60m -Xmx60m -XX:+PrintGCDetails -XX:NewRatio=2 -XX:SurvivorRatio=8
package com.tiza.jvm.chapter08;
/**
* 大对象直接进入老年代
* -Xms60m -Xmx60m -XX:+PrintGCDetails -XX:NewRatio=2 -XX:SurvivorRatio=8
*
* 经过上述参数配置后
* E S0 S1 O
* 16 2 2 40
* @author leowei
* @date 2021/1/10 - 9:45
*/
public class YoungOldAreaTest {
public static void main(String[] args) {
byte[] bytes = new byte[1024 * 1024 * 20];
}
}
P80-堆空间为每个线程分配的TLAB 09:55
TLAB: Thread Local Allocation Buffer
堆是线程共享区域,任何线程都可以访问堆中的共享数据
为了避免多个线程操作同一地址,需要使用加锁机制,进而影响分配速度
为应对上面的问题
Eden区域为每个线程分配一个单独的私有缓冲区域,就避免了线程安全问题,此为快速分配策略。
TLBA,仅占 Eden 1% .
-xx:TLABWasteTargetPersent 设置TLAB 大小。
jinfo -flag UseTLAB 6276
package com.tiza.jvm.chapter08;
/**
*
* -XX:UseTLAB
*
C:\Users\wei19>jps
2464
5904 Jps
96 RemoteMavenServer
10088 TLABArgsTest
14360 Launcher
C:\Users\wei19>jinfo -flag UseTLAB 10088
-XX:+UseTLAB --- 表示使用了 TLAB
*
*
* @author leowei
* @date 2021/1/10 - 10:05
*/
public class TLABArgsTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("111");
Thread.sleep(1000000);
}
}
P81-小结堆空间的常用参数设置 18:45
- 官网说明:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
堆空间中常用参数
-XX:+PrintFlagsInitial 查看所有参数的默认初始值
-XX:+PrintFlagsFinal 查看所有参数的最终值具体查看某个参数的指令 1. jps: 查看当前运行的进程 2. 如: jinfo -flag SurvivorRatio 进程ID
-Xms:初始堆空间内存 物理内存的 1/64
-Xmx:最大堆空间内存 物理内存的 1/4
-Xmn:设置新生代内存大小
-XX:NewRatio: 配置新生代与老年代在堆结构中的比例
-XX:SurvivorRatio: 设置新生代Eden vs S0/s1 空间的比例
-XX:MaxTenuringThreshold 设置新生代垃圾的最大年龄
-XX:+PrintGCDetails: 输出详细GC处理日志
打印GC 简要信息 -XX:+PrintGC -verbose:gc
-XX:HandlePromotionFailure: 是否设置空间分配担保 JDK7 以后,此参数一直是true
P82-通过逃逸分析看堆空间的对象分配策略 18:43
堆是分配对象存储的唯一选择吗?
逃逸分析: Escape Analysis
逃逸: 7.9 KM/S 逃离地球 第一宇宙速度
判断是否发生逃逸,看New的对象是否有可能在方法外被调用。
JDK7以后,HotSpot中默认开启了逃逸分析
-xx:+DoEscapeAnalysis 显式开启逃逸分析
-XX:+PrintEscapeAnalysis 查看逃逸分析的筛选结果
结论: 开发中能使用局部变量的,就不要在方法外定义。
package com.tiza.jvm.chapter08;
/**
* @author leowei
* @date 2021/1/10 - 11:16
*
* 判断逃逸分析的发生: new 的对象实体,是否在方法外被调用
*
*/
public class EscapeAnalysis {
public EscapeAnalysis obj;
/*
此方法返回的Obj实体,会在方法外部被调用
*/
public EscapeAnalysis getInstance(){
return obj ==null ? new EscapeAnalysis() :obj;
}
/*
为成员变量赋值,发生逃逸
*/
public void setObj(){
this.obj =new EscapeAnalysis();
}
/*
对象esc 的作用域尽在当前方法,没有发生逃逸
*/
public void useEscapeAnalysis(){
EscapeAnalysis esc = new EscapeAnalysis();
}
/*
引用成员变量发生逃逸
*/
public void useEscapeAnalysis2(){
EscapeAnalysis esc2 = getInstance();
}
}
P83-代码优化之栈上分配 07:46
代码优化
栈上分配
同步省略
标量替换
栈上分配:如果没有逃逸,在栈上分配此对象,
-
-DoEscapeAnalysis 不开启逃逸分析
-
+DoEscapeAnalysis 开启逃逸分析
- -Xmx1g -Xms1g -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -----花费时间为:83ms
- -Xmx1g -Xms1g -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -----花费时间为:4ms
- -Xmx256m -Xms256m -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -----花费时间为:67ms (执行了垃圾回收)
- -Xmx256m -Xms256m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -----花费时间为:4ms
开启与关闭逃逸分析,创建对象用时对比分析,开启逃逸分析后,用时明显减少。
package com.tiza.jvm.chapter08;
/**
* @author leowei
* @date 2021/1/10 - 12:34
* -DoEscapeAnalysis 不开启逃逸分析
* +DoEscapeAnalysis 开启逃逸分析
* -Xmx1g -Xms1g -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -----花费时间为:83ms
* -Xmx1g -Xms1g -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -----花费时间为:4ms
* -Xmx256m -Xms256m -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -----花费时间为:67ms (执行了垃圾回收)
* -Xmx256m -Xms256m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -----花费时间为:4ms
*
*/
public class StackAllocation {
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) { //1000 万次
alloc();
}
long end =System.currentTimeMillis();
System.out.println("花费时间为:"+ (end-start)+ "ms");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void alloc(){
User user = new User(); // 此对象未发生逃逸 ,所以在栈中分配 ,不用垃圾回收
}
static class User{
}
}
P84-代码优化之同步省略 04:58
同步省略: 如果一个对象只能从一个线程被访问到,对于此对象的操作可以不考虑同步。
线程同步的代价是相当高的,同步的后果是降低并发性和性能。
P85-代码优化之标量替换 06:49
分离对象 或者 标量替换 :对象可以不存储在内存,而是存储在CPU寄存器中。(听不懂)
标量(Scalar):一个无法再分析成更小的数据。
聚合量(Aggregate): 可以再分析的叫做聚合量
开启标量替换
-XX:+EliminateAllocations 默认开启,允许将对象打散分配在栈上
package com.tiza.jvm.chapter08;
import javax.swing.text.SimpleAttributeSet;
/**
* @author leowei
* @date 2021/1/10 - 13:23
* -XX:+EliminateAllocations: 开启标量替换,默认是开启的,允许将对象打散分配在栈上。
* -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
* ------------------------------[GC (Allocation Failure) 25600K->1032K(98304K), 0.0007479 secs]
* ------------------------------花费时间为19 ms
* -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
* ----------------------------- 花费时间为8 ms
*/
public class ScalarReplace {
public static class User{
public int id;
public String name;
}
public static void alloc(){
User u =new User(); // 未发生逃逸
u.id =5;
u.name ="www.atguigu.com";
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
alloc();
}
long end = System.currentTimeMillis();
System.out.println("花费时间为" + (end - start) + " ms");
}
}
P86-代码优化及堆的小结 06:31
代码优化
栈上分配
同步省略
标量替换
逃逸分析并不是十分成熟
对象实例十分分配在堆空间的。
09 方法区
P87-方法区概述_栈堆方法区间的交互关系 11:42
方法区: 永久代 (1.7 -) / 元空间 (1.8+)
栈、堆、方法区之间的配合关系:
程序计数器: 不会报 StackOverflowError ,
下图非常牛逼:包含信息能看懂,要有些功底 。
java栈中本地变量的每个占用是一个slot, long , double 占用两个单位的slot .
对象类型数据:方法区
对象实例数据:堆
P88-方法区的基本理解 17:27
/**
* @author leowei
* @date 2021/1/10 - 14:11
*
* 其他操作都不做,就打开jkd bin 下面的Visual VM ,
* 查看 监视 选项卡 看 实体个数
*
*/
public class MethodAreaDemo {
public static void main(String[] args) {
System.out.println("start ... ");
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end ...");
}
}
P89-Hotspot中方法区的演进 09:37
jdk7之前,方法区叫做永久代
jdk8之后,方法区叫做元空间
本质上,方法区和永久代并不等价(HotSpot虚拟机中可以认为方法区=永久代,比如 广东人 把旺财 == 狗 ), BEA JRockit / IBM J9 中并不存在永久代的概念。
永久代 与 元空间 本质上的区别
永久代:用的是java虚拟机的内存 实现方法区:
元空间:用的是本地内存实现 方法区
-XX:MaxPermSize
P90-设置方法区大小的参数 14:52
jdk7
-XX:PermSize ---------20.75M
-XX:MaxPermSize ------ 64M(32位机器) 82M(64位机器)
jps
jinfo -flag PermSize 进程ID
jinfo -flag MaxPermSize 进程ID
jdk8
-XX:MetaspaceSize ---- 21m
-XX:MaxMetaspaceSize — 由于使用本机内存,此值没有限制
jps
jinfo -flag MetaspaceSize 进程ID
jinfo -flag MaxMetaspaceSize 进程ID
C:\Users\wei19>jps
17768 MethodAreaDemo
C:\Users\wei19>jinfo -flag PermSize 17076
no such flag 'PermSize' ---- jdk 1.8 运行环境
C:\Users\wei19>jinfo -flag MetaspaceSize 7672
-XX:MetaspaceSize=21807104
C:\Users\wei19>jinfo -flag MaxMetaspaceSize 7672
-XX:MaxMetaspaceSize=18446744073709486080
----------------------------------------------------
C:\Users\wei19>jinfo -flag PermSize 17768
-XX:PermSize=21757952 ---- jdk 1.7 运行环境
在jdk8环境中配置 jdk7时候的参数
-XX:PermSize=100m -XX:MaxPermSize=100m
会提示如下错误
start ...
end ...
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=100m; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=100m; support was removed in 8.0
P91-OOM:PermGen和OOM:Metaspace举例 09:58
内存泄漏: ???
P92-方法区的内部结构1 21:14
方法区中存放大信息有: 类型信息、域信息、方法信息、常量(运行时常量池)、静态变量、JIT、
类型信息:
域信息(Field):
方法信息(Method)
C:\workspace\workspace_idea\jvmByAtguigu\chapter09\target\classes\com\tiza\jvm\chapter09>javap -v -p MethodInnerStructTest.class > MethodInnerStructTxt.txt
MethodInnerStructTxt.txt 介绍
Compiled from "MethodInnerStructTest.java"
//01: 类型信息
public class com.tiza.jvm.chapter09.MethodInnerStructTest extends java.lang.Object implements java.lang.Comparable<java.lang.String>, java.io.Serializable
minor version: 0
// 02 域信息
public int num;
descriptor: I
flags: ACC_PUBLIC
private static java.lang.String str;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC
//03 方法信息 (包含构造器及方法 )
public com.tiza.jvm.chapter09.MethodInnerStructTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
...
public void test1();
descriptor: ()V // V =void
flags: ACC_PUBLIC
Code: // stack=3 操作数栈的深度=3 局部变量表的长度=2 args_size=1 就是本身 this
stack=3, locals=2, args_size=1
...
public static int test2(int);
descriptor: (I)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
...
P93-方法区的内部结构2 08:13
静态变量和类关联在一起,随着类的加载而加载,它们成为类数据在逻辑上的一部分。
类变量被所有类的实例所共享,即使没有类实例时你也可以访问它。
全局常量: static final ,每个全局常量在编译的时候就被分配了。
C:\workspace\workspace_idea\jvmByAtguigu\chapter09\target\classes\com\tiza\jvm\chapter09>javap -v -p Order.class > Order.txt
-------Order.java
class Order{
// 查看 Order.class 文件中下面两个域 编译后的效果
public static int count=1;
public static final int number=2;
。。。
}
-------Order.class
public static int count;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC
public static final int number;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 2 // 声明为final static的变量在此时就已经赋值了
对比static vs static final
P94-class文件中常量池的理解 18:12
运行时常量池:
Constant Pool :常量池,可以看做一张表,虚拟机指令根据这张常量表,找到要执行的类名,方法名,参数类型,字面量等类型。
P95-运行时常量池的理解 06:38
P96-图示举例方法区的使用 —<综合知识点需要多看> 16:45
系统讲解简单的方法加数据中涉及到的栈 、 程序计数器、 方法区 中具体的操作
P97-方法区在jdk6、jdk7、jdk8中的演进细节 25:21
只有HotSpot才有永久代。
jdk6 : 静态变量放在永久代上
jdk7 : 静态变量放在堆上, 字符串常量池放在堆上 (仍然有永久代的概念)
jdk8 : 静态变量放在堆上, 字符串常量池放在堆上; 类型信息、字段、方法、常量保存在本地内存元空间 (没有永久代,用元空间替换它)
heap: static variable 静态变量; StringTable 字符串常量池
永久代为什么要被元空间替换?
P98-StringTable为什么要调整位置 05:27
StringTable (Interned String) : 字符串常量池
P99-如何证明静态变量存在哪 11:15
package com.tiza.jvm.chapter09;
/**
* @author leowei
* @date 2021/1/11 - 23:19
*
* jdk6 jdk7
* -Xms200m -Xmx200m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:+PrintGCDetails
*
* jdk8
* -Xms200m -Xmx200m -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails
*
* 这块只能证明new 的 大数据放在了堆空间的老年代
*/
public class StaticFieldTest {
private static byte[] arr=new byte[1024*1024*100]; // 100MB
public static void main(String[] args) {
System.out.println( StaticFieldTest.arr);
}
/* JDK 6 7 打印结果 ----数据放在了 ParOldGen 这个空间内
Heap
PSYoungGen total 59712K, used 3072K [0x00000000fbd60000, 0x0000000100000000, 0x0000000100000000)
eden space 51200K, 6% used [0x00000000fbd60000,0x00000000fc0600a0,0x00000000fef60000)
from space 8512K, 0% used [0x00000000ff7b0000,0x00000000ff7b0000,0x0000000100000000)
to space 8512K, 0% used [0x00000000fef60000,0x00000000fef60000,0x00000000ff7b0000)
PSOldGen total 136576K, used 102400K [0x00000000f3800000, 0x00000000fbd60000, 0x00000000fbd60000)
object space 136576K, 74% used [0x00000000f3800000,0x00000000f9c00010,0x00000000fbd60000)
PSPermGen total 307200K, used 3814K [0x00000000e0c00000, 0x00000000f3800000, 0x00000000f3800000)
object space 307200K, 1% used [0x00000000e0c00000,0x00000000e0fb98e0,0x00000000f3800000)
*/
/* JDK 8 打印结果 ----数据放在了 ParOldGen 这个空间内
Heap
PSYoungGen total 59904K, used 6206K [0x00000000fbd80000, 0x0000000100000000, 0x0000000100000000)
eden space 51712K, 12% used [0x00000000fbd80000,0x00000000fc38f8c8,0x00000000ff000000)
from space 8192K, 0% used [0x00000000ff800000,0x00000000ff800000,0x0000000100000000)
to space 8192K, 0% used [0x00000000ff000000,0x00000000ff000000,0x00000000ff800000)
ParOldGen total 136704K, used 102400K [0x00000000f3800000, 0x00000000fbd80000, 0x00000000fbd80000)
object space 136704K, 74% used [0x00000000f3800000,0x00000000f9c00010,0x00000000fbd80000)
Metaspace used 3333K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 358K, capacity 388K, committed 512K, reserved 1048576K
*/
}
P100-方法区的垃圾回收行为 11:10
《Java虚拟机规范》 中没有明确要求方法区进行垃圾回收。
一句话,方法区的垃圾回收很难做
P101-运行时数据区的总结与常见大厂面试题说明 06:25
10 对象的实例化内存布局与访问定位
P102-对象实例化的几种方式 10:05
P103-字节码角度看对象的创建过程 06:12
package com.tiza.jvm.chapter10;
/**
* @author leowei
* @date 2021/1/12 - 0:04
*/
public class ObjectTest {
public static void main(String[] args) {
Object obj = new Object();
}
}
javap -v -p ObjectTest.class
P104-对象创建的六个步骤 22:07
P105-对象的内存布局 —<综合知识点需要多看> 11:00
package com.tiza.jvm.chapter10;
/**
* @author leowei
* @date 2021/1/12 - 20:36
*/
public class CustomerTest {
public static void main(String[] args) {
Customer cust = new Customer();
}
}
package com.tiza.jvm.chapter10;
/**
* @author leowei
* @date 2021/1/12 - 20:37
*/
public class Customer {
int id=1001;
String name; // 字符串常量 jdk7 以后 ,放在 堆中
Account acct;
{
name ="匿名客户";
}
public Customer(){
acct=new Account();
}
}
class Account{
}
对象在内存中的布局:
对象头 (Header) ;
实例数据(Instance Data);
对齐填充 (Padding);
P106-对象访问定位 07:48
对象访问方式有两种:
- 句柄访问
- 直接指针访问(Hotspot采用)
-
句柄访问
缺点: 堆空间中的句柄池要占用空间
-
直接指针访问(Hotspot采用)
11 直接内存 Direct Memory
P107-直接内存的简单体验 07:53
直接内存,不是JVM的一部分。也不是《java虚拟机规范》中的具体要求
package com.tiza.jvm.chapter11;
import java.nio.ByteBuffer;
import java.util.Scanner;
/**
* @author leowei
* @date 2021/1/12 - 21:05
*/
public class BufferTest {
private static final int BUFFER = 1024*1024*1024; // 1GB
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
System.out.println("直接内存分配完毕,请求指示!");
Scanner scanner = new Scanner(System.in);
scanner.next();
System.out.println("直接内存开始释放");
byteBuffer=null;
System.gc();
scanner.next();
}
}
直接内存分配完毕,请求指示! 的时候看13864 占用空间 1G 空间
system.in 后 ,再次看 13864 占用的大小
P108-使用本地内存读写数据的测试 07:49
通常,访问直接内存的速度会优于Java堆,所以出于性能的考虑,读写频繁的场合会考虑使用直接内存。
java的NIO 库,允许JAVA程序使用直接内存,用于数据缓冲
视频中介绍了,使用直接内存,使用传统IO 两种方式 复制 一步 1G 的电影,比较其速度。
P109-直接内存的00M与内存大小的设置 10:44
直接内存可以通过MaxDirectMemorySize设置
直接内存的缺点:
分配回收成本较高
不受JVM内存回收管理
`package com.tiza.jvm.chapter11;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/**
- @author leowei
- @date 2021/1/12 - 21:44
- 本机测试 180 次
- java.lang.OutOfMemoryError: Direct buffer memory
/
public class BufferTestOutOfMemory {
private static final int BUFFER=10241024*20; //20M
public static void main(String[] args) {
ArrayList list = new ArrayList();
int count=0;
while(true){
//不断的将20M 的数据往 list 中加 看oom 时候的报错,及count
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
list.add(byteBuffer);
count++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(count);
}
}
}
}
`
package com.tiza.jvm.chapter11;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* -Xmx20m -XX:MaxDirectMemorySize=10m
*
* @author leowei
* @date 2021/1/12 - 21:49
*
* java.lang.OutOfMemoryError
*/
public class MaxDirectMemorySizeTest {
private static final long _1MB = 1024 * 1024;
public static void main(String[] args) throws IllegalAccessException{
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe)unsafeField.get(null);
while(true){
unsafe.allocateMemory(_1MB);
}
}
}
12 执行引擎
P110-执行引擎的作用及工作过程概述 18:47
执行引擎的作用: 将字节码指令解释/编译为对应平台上的本地机器指令。也就是说,执行引擎充当了将高级语言翻译为机器语言的译者的角色。
P111-Java程序的编译和解释运行的理解 10:11
对应上图第一部分灰色部分
JAVA是半编译半解释型的语言
解释器:Interpreter 对字节码采用逐行解释的方式执行
JIT编译器: Just In Time Compiler .
P112-机器码_指令_汇编_高级语言理解与执行过程 15:40
机器码:二进制编码方式表示的指令。
指令: mov,inc等指令
指令集:x86指令集; ARM指令集;
汇编语言: 由于指令的可读性太差,汇编语言使用助记符带起机器的操作码,用地址符号代替指令的地址
高级语言:更接近人类的语言。
字节码: 比机器码更加抽象的一种中间状态的二进制码,字节码为了实现特定软件运行和硬件环境无关。
P113-解释器的使用 11:00
解释器:充当翻译者的角色。现在使用解释器的方式比较低效,JVM现在增加即时编译器
JAVA中的解释器分两种
字节码解释器
模板解释器
P114-HotspotVM为何解释器与JIT编译器并存 17:32
JIT编译器:Just In Time
HotSpot 采用 解释器与JIT编译器 混合执行。
JIT特点是比解释器速度快。
解释器保留的原因是: 当程序启动后,解释器立即执行,省去了编译的环节,响应速度快。
运行一下代码,打开jvisualvm jconsole 查看效果
package com.tiza.jvm.chapter12;
import java.util.ArrayList;
/**
* @author leowei
* @date 2021/1/12 - 23:35
*/
public class JITTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < 1000; i++) {
list.add(" 狗升科技 ");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
jvisualvm
C:\Users\wei19>jvisualvm
jconsole
C:\Users\wei19>jconsole
P115-热点代码探测确定何时JIT 16:53
编译器:
前端编译器: 将.java文件转换为.class文件 (Sun 的Javac ;Eclipse JDT )
后端编译器:JIT编译器 Just In Time Compiler (HotSpot VM )
解释器: — 凉菜
JIT编译器:— 热菜(大菜)
热点代码: 一个被多次调用的方法,或者一个方法体内部循环次数较多的循环体。
hotSpot VM 采用热点探测的方式是基于计数器的热点探测。
针对热点代码,JIT编译器会启用。
client模式下:1500 次
server模式下: 10000 次 默认都是server模式 -----64-Bit Server
通过-XX:CompileThreshold 来认为设定
C:\Users\wei19>java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)
方法调用计数器
热度衰减
-XX:-UseCounterDecay
-XX:CounterHalfLifeTime
回边计数器
循环体代码执行的次数
P116-Hotspot设置模式_C1与C2编译器 15:20
java -Xint -version
int=interpreted
java -Xcomp -version
comp=compiled
java -Xmixed -version
C:\Users\wei19>java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)
C:\Users\wei19>java -Xint -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, interpreted mode)
C:\Users\wei19>java -Xcomp -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, compiled mode)
C:\Users\wei19>java -Xmixed -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)
测试三种方式的代码我就不写了,详见:
https://www.bilibili.com/video/BV1PJ411n7xZ?p=116
client server
hotSpot 默认有两个JIT编译器
-client
-server ---- 64 位的系统默认就是server版的,设置client也会被忽略掉。
请客的例子
解释器 ----- 凉菜
JIT编译器 ----- 热菜 (如烤鸭)client模式 ----- 热菜 烤鸭 快速模式 快一些
server模式 ----- 热菜 烤鸭 经典模式 慢一些吃起来效果更好
P117-Graal编译器与AOT编译器 07:41
Graal编译器
JDK10 后 Hotspot加入了全新的即时编译器 Graal编译器
编译效果等同于C2编译器
c1
c2
Graal
AOT编译器
AOT: Ahead Of Time Compile
JDK9中引入,
解释器
JIT编译器
AOT编译器
13 StringTable
P118-String的不可变性 21:34
20210113
String 实例化方式两种:
String s1=“tiza”;
String s2=new String(“tiza”);
jdk9 及以后有个比较大的变化是,String的构成由 char[] 变为 byte[] ,jeps254是对此项变化的说明。 http://openjdk.java.net/jeps/254
jdk8 –
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
jdk9 +
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final byte[] value;
}
1.字符串重新赋值
2.字符串加值
3.字符串替换值 三个都会重新指定内存区域赋值
@Test
public void test(){
// 通过字面量的方式定义,如下:s1,s2 都执行字符串常量池的"abc"(jdk7 8是在堆空间 ; jdk 6 在方法区)
String s1="abc";
String s2 ="abc";
System.out.println(s1==s2); //true
// 通过 new ,放在不同区域
String str1=new String("abc");
String str2=new String("abc");
System.out.println(str1==str2); //false
}
P119-String底层Hashtable结构的说明 15:57
字符串常量池中是不会存储相同内容的字符串的。
jps
jinfo -flag StringTableSize **
-XX:StringTableSize=10
jdk 6
C:\Users\wei19>jps
11232 Jps
18644 RemoteMavenServer
12168 StringTest2
13256 Launcher
11324
C:\Users\wei19>jinfo -flag StringTableSize 12168
-XX:StringTableSize=1009
jdk 7 8
C:\Users\wei19>jps
8032 Launcher
18644 RemoteMavenServer
3924 Jps
3208 StringTest2
11324
C:\Users\wei19>jinfo -flag StringTableSize 3208
-XX:StringTableSize=60013
-XX:StringTableSize=10
C:\Users\wei19>jps
17760 StringTest2
18644 RemoteMavenServer
7464 Launcher
10124 Jps
11324
C:\Users\wei19>jinfo -flag StringTableSize 17760
-XX:StringTableSize=10
P120-String内存结构的分配位置 09:46
String的内存分配
8种基本类型及String,系统提供了常量池。访问速度更快,更节省内存。
package com.tiza.jvm.chapter13;
import java.util.HashSet;
import java.util.Set;
/**
* jdk6中
* -XX:PermSize=6m -XX:MaxPermSize=6m -Xms6m -Xmx6m
* java.lang.OutOfMemoryError: PermGen space
*
* jdk7 8 中
* -XX:MetaspaceSize=6m -XX:MaxMetaspaceSize=6m -Xms6m -Xmx6m
*java.lang.OutOfMemoryError: GC overhead limit exceeded
*
* @author leowei
* @date 2021/1/13 - 22:39
*/
public class StringTest3 {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
short i=0;
while (true){
set.add(String.valueOf(i++).intern()); //intern() 在字符串常量池中分配
}
}
}
P121-两个案例熟悉String的基本操作 11:20
package com.tiza.jvm.chapter13;
/**
* @author leowei
* @date 2021/1/13 - 23:09
*/
public class Memory {
public static void main(String[] args) {
int i=1;
Object obj = new Object();
Memory mem = new Memory();
mem.foo(obj);
}
private void foo(Object param){
String str = param.toString();
System.out.println(str);
}
}
P122-字符串拼接操作的面试题讲解 14:01
package com.tiza.jvm.chapter13;
import org.junit.Test;
/**
* @author leowei
* @date 2021/1/13 - 23:28
*/
public class StringTest5 {
@Test
public void test2(){
String s1="javaEE";
String s2="hadoop";
String s3="javaEEhadoop";
String s4="javaEE"+ "hadoop"; // 编译期 优化
String s5=s1+"hadoop"; // 如果拼接字符串出现变量 ,相当于在堆空间New String()
String s6="javaEE"+ s2;
String s7=s1+s2;
System.out.println(s3==s4); // true
System.out.println(s3==s5); // 只要有变量就放在(非字符串常量池的)堆中 所以 false
System.out.println(s3==s6); // false 同上
System.out.println(s3==s7); // false 同上
System.out.println(s5==s6); // false 同上
System.out.println(s5==s7); // false 同上
System.out.println(s6==s7); // false 同上
System.out.println("----------------------");
// intern 判断字符串常量池中是否存在 javaEEhadoop ,如果存在,返回常量池中javaEEhadoop的地址
//如果字符串常量池中不存在javaEEhadoop,则在常量池中加载一份,并返回此此对象的地址
String s8 = s6.intern(); //放到字符串常量池中
System.out.println(s3==s8); //true
System.out.println("=====================");
String str1="a";
String str2="b";
String str3="ab";
String str4= s1+s2;
String str5= "a"+"b";
System.out.println( str3==str4); // false str3 放在字符串常量表 str4 放在堆空间
System.out.println( str3==str5); // true str5 在编译时候 自动变为 "ab" ;
}
}
P123-字符串变量拼接操作的底层原理 17:21
@Test
public void test41(){
String s1="a";
String s2="b";
String s3="ab";
String s4=s1+s2; // s4 相当于 new StringBuild().append("a").append("b");
System.out.println(s3==s4); //false
}
/*
字符串拼接操作不一定使用StringBuilder
如果拼接符号左右两边都是字符串常量或者常量引用 ,则仍然使用编译期优化,即非StringBuilder的方式
*/
@Test
public void test42(){
final String s1="a"; // final 定义的不能当做变量看,要当做常量看
final String s2="b";
String s3="ab";
String s4=s1+s2; // s4 相当于 "a" + "b"
System.out.println(s3==s4); //true
}
@Test
public void test43(){
String s1="javaEEhadoop";
final String s4="javaEE";
String s5 =s4+"hadoop";
System.out.println(s1==s5); // true s4 用final 修饰,相当于 是个常量了
}
@Test
public void test43() {
String s1 = "javaEEhadoop";
String s4 = "javaEE";
String s5 = "javaEEhadoop";
System.out.println(s1 == s5);
}
P124-拼接操作与append操作的效率对比 10:01
public class StringTest5 {
public static void main(String[] args) {
long timeStart = System.currentTimeMillis();
StringTest5 stringTest5 = new StringTest5();
// stringTest5.methodByString(100000); //用时: 5174
stringTest5.methodByStringBuilder(100000); //用时: 4
long timeEnd = System.currentTimeMillis();
System.out.println("用时:"+ (timeEnd-timeStart));
}
public void methodByString(int times){
String str="";
for (int i = 0; i < times; i++) {
str= str+ "a"; // 每次循环都创建一个StringBuilder
}
}
public void methodByStringBuilder(int times){
StringBuilder str = new StringBuilder(); // new StringBuilder(times);
for (int i = 0; i < times; i++) {
str.append("a");
}
}
}
P125-intern()的理解 11:46
new String().intern() ; new String 可以使用intern() 方法。
intern方法会从字符串常量池中查询当前字符串是否存在,若不存在,会将当前字符串放入常量池中。
(“abc”).intern() = “abc”
(“a”+“b”+“c”).intern() =“abc”
任意字符串调用intern() 方法的返回结果。
和以常量形式出现的字符串示例,
结果相同
Intern()方法,确保当前字符串在(堆空间)下的字符串常量池中只有一份拷贝。
可以节约空间,加快执行速度。
* 如何保证变量S指向的字符串常量池中的数据呢?
* 有两种方式:
* 方式一: String s ="wltest"; //字面量的方式
* 方式二: String s =new String("wltest").intern();
* String s =new StringBuilder("wltest").toString().intern();
/*
0 new #2 <java/lang/String>
3 dup
4 ldc #3 <ab>
6 invokespecial #4 <java/lang/String.<init>>
9 astore_1
10 return
*/
//这个语句做了什么
//String str = new String("ab");
String str2 = new String("ab").intern();
/*
0 new #2 <java/lang/String>
3 dup
4 ldc #3 <ab>
6 invokespecial #4 <java/lang/String.<init>>
9 invokevirtual #5 <java/lang/String.intern>
12 astore_1
13 return
*/
//jdk6
// 1. 堆空间创建一个对象 ab
// 2. 堆空间字符串常量池中创建一个对象 ab
//jdk7 / 8
// 1. 堆空间创建一个对象 ab
// 2. 堆空间字符串常量池中创建一个对象(其存放地址) 地址其指向堆空间中的ab
P126-new String()到底创建了几个对象 12:25
package com.tiza.jvm.str_intern;
/**
* @author leowei
* @date 2021/1/15 - 7:26
* String str = new String("ab");
* new String("ab") 会创建几个对象?
* 两个对象 一个是:new 关键字在堆空间创建的
* 另一个是 字符串常量池中的对象
*
* String strPlus =new String("a")+ new String("b"); 创建了几个对象?
* 对象1: new StringBuilder()
* 对象2: new String("a");
* 对象3: 常量池中的"a" ;
* 对象4: new String("b");
* 对象5: 常量池中的"b" ;
* 深入剖析 StringBuilder 的 toString()
* 对象6 new String("ab");
* 强调一下,toString() 的调用,在字符串常量池中没有 ab
*/
public class StringNewTest {
public static void main(String[] args) {
/* String str = new String("ab");*/
String strPlus =new String("a")+ new String("b");
}
}
package com.tiza.jvm.str_intern;
/**
* @author leowei
* @date 2021/1/15 - 7:57
*
* String s = new String("1");
* 1. 堆空间(常量池)中的 1 ,2.栈空间 的地址
* s.intern();
* 调用此防范前常量池中已经存在了1 ,什么也不做
* String s2="1";
* s2 是 常量池中的地址
*/
public class StringIntern {
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2); //JDK7/8 : false JDK6: FALSE
/* String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4); //JDK7/8 : TRUE JDK6: FALSE
*/
}
}
P127-关于intern()的面试难题 13:40
/**
* @author leowei
* @date 2021/1/15 - 7:57
*
* 如何保证变量S指向的字符串常量池中的数据呢?
* 有两种方式:
* 方式一: String s ="wltest"; //字面量的方式
* 方式二: String s =new String("wltest").intern();
* String s =new StringBuilder("wltest").toString().intern();
*
* String s = new String("1");
* 1. 堆空间中的开辟一个区域存放“1” ,字符串常量池中开辟一个区域放“1” (6中放永久代 7,8 放堆中)
* s.intern();
* 堆空间字符串常量池中找是否有“1” ,(当前是有)什么也不做
* String s2="1";
* s2 是 常量池中的地址
*/
public class StringIntern {
public static void main(String[] args) {
String s = new String("1");
s.intern(); //调用此方法之前 字符串常量池中已经有1了,所以什么也不做
String s2 = "1";
System.out.println(s == s2); //JDK7/8 : false JDK6: FALSE
/* String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4); //JDK7/8 : TRUE JDK6: FALSE
*/
}
}
package com.tiza.jvm.str_intern;
/**
* @author leowei
* @date 2021/1/15 - 7:57
*
* 如何保证变量S指向的字符串常量池中的数据呢?
* 有两种方式:
* 方式一: String s ="wltest"; //字面量的方式
* 方式二: String s =new String("wltest").intern();
* String s =new StringBuilder("wltest").toString().intern();
*
* String s = new String("1");
* 1. 堆空间中的开辟一个区域存放“1” ,字符串常量池中开辟一个区域放“1” (6中放永久代 7,8 放堆中)
* s.intern();
* 堆空间字符串常量池中找是否有“1” ,(当前是有)什么也不做
* String s2="1";
* s2 是 常量池中的地址
*/
public class StringIntern {
public static void main(String[] args) {
/* String s = new String("1");
s.intern(); //调用此方法之前 字符串常量池中已经有1了,所以什么也不做
String s2 = "1";
System.out.println(s == s2); //JDK7/8 : false JDK6: FALSE*/
String s3 = new String("1") + new String("1"); // s3 变量的地址: new String("11")
//执行完上一行代码后,字符串常量池是否存在 11 ,答 没有
// 原因是 s3 地址是 StringBuilder 中的 toString 方法的new String() ,此new String () 比较特殊
// ,只会执行在堆中分配 , 不会执行在字符串常量池中分配
s3.intern(); //在字符串常量池中生成 11
// jdk6: 创建一个新的对象“11”,有了新的地址
// jdk7: 此时常量池中没有创建11 ,而是创新了一个指向对空金new String("ss")的地址 。
String s4 = "11"; //s4变量记录的地址:使用的是上一行代码执行时,在常量池中生成的"11"的地址
System.out.println(s3 == s4); //JDK7/8 : TRUE JDK6: FALSE
}
}
P128-面试的拓展问题 06:21
package com.tiza.jvm.str_intern;
/**
* Author: tz_wl
* Date: 2021/1/15 13:46
* Content:
*/
public class StringIntern02 {
public static void main(String[] args) {
method1();
method2();
}
private static void method1() {
String s3 = new String("1") + new String("1");
//创建有stringbuilder 堆空间 “1” 字符串常量池“1” 堆空间 “1” 字符串常量池“1” 堆空间“11”
s3.intern();
//判断“11” 在字符串常量池中是否存在“11” ,当前不存在,所以要在 字符串常量池中开启一个空间放“11”
//jdk6 中 在常量池中 开辟一个区域放“11”
//jdk7 8 常量池中 开辟一个区域 (由于堆中有了“11”) 放一个指向堆空间 “11” 的地址 (想想如果变量未1000个字符,此处指用4位引用地址就可以了,为了节省空间)
String s4 = "11";
System.out.println(s3==s4); //jkd 6 false jdk 7 8 true
}
private static void method2() {
String s3 = new String("1") + new String("1");
//创建有stringbuilder 堆空间 “1” 字符串常量池“1” 堆空间 “1” 字符串常量池“1” 堆空间“11” ,常量池中不存在“11” s3实际指向的是堆空间new StringBuilder(**)的地址
String s4 = "11"; // 在字符串常量池中 开辟一个空间放 11 其地址放在s4 里面
s3.intern(); //判断“11” 在字符串常量池中是否存在“11” ,当前存在,所以什么也不做
//jdk 6 :
//jdk 7 8 :
String s5 = s3.intern(); // s5 指向 字符串常量池中的“11” 也就是s4指向的地址
System.out.println(s3==s4); //jkd 6 false jdk 7 8 false
System.out.println(s4==s5); //jkd 6 true jdk 7 8 true
}
}
String intern()使用的总结
jdk6中,将这个字符串对象尝试放入串池
如果串池中有,则不会放入,返回以后串池中的对象的地址
如果没有,把此对象复制一份,放入串池,并返回串池中的对象地址
jdk7开始,将这个字符串对象尝试放入串池。
如果串池中有,则不会放入,返回以后传池中的对象的地址
如果没有,把对象引用地址复制一份,翻入串池,并返回串池中的引用地址
总结就是 6 放真实数据 ; 7,8 放引用地址(目的是节省空间)
P129-intern()的课后练习1 08:05
package com.tiza.jvm.str_intern;
/**
* Author: tz_wl
* Date: 2021/1/15 14:39
* Content:
*/
public class StringInternEx01 {
//
public static void main(String[] args) {
method1();
}
private static void method1() {
String s= new String("a")+ new String("b"); //s 存放的是 堆空间 中"ab" 的地址
//上述代码 会在堆中new 一个对象放"ab",但是不会在字符串常量中开辟空间放"ab"
// new String("ab") 会在堆中new 一个对象放"ab", 也会在字符串常量中开辟空间放"ab"
String s2 = s.intern();
//jdk6 查看 字符串常量池中是否有空间已经存放“ab” ,当前没有,会在字符串常量池中开启一个空间放"ab",并将其地址给s2
//jdk7/8 ,会在字符串常量池中开启一个空间,其存放堆中“ab”的地址
System.out.println(s2=="ab"); // jkd6: true jkd7/8: true
System.out.println(s=="ab"); // jkd6: false jkd7/8: true
}
}
package com.tiza.jvm.str_intern;
/**
* Author: tz_wl
* Date: 2021/1/15 14:39
* Content:
*/
public class StringInternEx01 {
//
public static void main(String[] args) {
method1();
method2();
}
private static void method1() {
String s= new String("a")+ new String("b"); //s 存放的是 堆空间 中"ab" 的地址
//上述代码 会在堆中new 一个对象放"ab",但是不会在字符串常量中开辟空间放"ab"
// new String("ab") 会在堆中new 一个对象放"ab", 也会在字符串常量中开辟空间放"ab"
String s2 = s.intern();
//jdk6 查看 字符串常量池中是否有空间已经存放“ab” ,当前没有,会在字符串常量池中开启一个空间放"ab",并将其地址给s2
//jdk7/8 ,会在字符串常量池中开启一个空间,其存放堆中“ab”的地址
System.out.println(s2=="ab"); // jkd6: true jkd7/8: true
System.out.println(s=="ab"); // jkd6: false jkd7/8: true
}
private static void method2() {
String x= "ab";
String s= new String("a")+ new String("b"); //s 存放的是 堆空间 中"ab" 的地址
//上述代码 会在堆中new 一个对象放"ab",但是不会在字符串常量中开辟空间放"ab"
String s2 = s.intern();
//jdk6 查看 字符串常量池中是否有空间已经存放“ab” ,当前 有,也就是x的内容 , s2 = x
//jdk7/8 ,会在字符串常量池中开启一个空间,其存放堆中“ab”的地址
System.out.println(s2=="ab"); // jkd6: true jkd7/8: true
System.out.println(s=="ab"); // jkd6: false jkd7/8: false
}
}
P130-intern()的课后练习2 04:04
package com.tiza.jvm.str_intern;
/**
* Author: tz_wl
* Date: 2021/1/15 15:13
* Content:
*/
public class stringInternEx02 {
public static void main(String[] args) {
mehtod1();
mehtod2();
}
private static void mehtod1() {
// s1 地址是堆空间"ab" 的地址,此时 字符串常量池中,没有"ab"
String s1= new String("a")+ new String("b");
s1.intern();
// jdk 6 时候 : 此时将在字符串常量池中开辟一个空间,放入"ab"
// jdk 7/8 时候 : 此时将在字符串常量池中开辟一个空间,放入 堆空间中 “ab” 的地址, 也就是栈中地址指向堆“ab” ,字符串常量池中地址指向堆中“ab"
String s2="ab"; //s2 是字符串常量池中"ab" 的地址
System.out.println(s1==s2); //jdk6 false ; jdk7/8 true
}
private static void mehtod2() {
// s1 地址是堆空间"ab" 的地址,此时 字符串常量池中有"ab"
String s1 = new String("ab");
s1.intern(); // 此时字符串常量池中有了“ab” 所以什么也不做
String s2="ab"; //s2 是字符串常量池中"ab" 的地址
System.out.println(s1==s2); //jdk6 false ; jdk7/8 false
}
}
P131-intern()的空间效率测试 12:31
arr[i] = new String(String.valueOf(data[i % data.length])); //用时:7075
arr[i]= new String(String.valueOf(data[i%data.length])).intern(); //用时:2280
package com.tiza.jvm.str_intern;
/**
* Author: tz_wl
* Date: 2021/1/15 15:29
* Content: 使用intern 测试执行效率
*
* 使用jvisualvm 查看 抽样器 内存
*
*/
public class StringInternSpaceTest {
static final int MAX_COUNT = 1000 * 10000; // 1000万
static final String[] arr =new String[MAX_COUNT];
public static void main(String[] args) {
Integer[] data = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
long start = System.currentTimeMillis();
for (int i = 0; i < MAX_COUNT; i++) {
arr[i] = new String(String.valueOf(data[i % data.length])); //用时:7075
//arr[i]= new String(String.valueOf(data[i%data.length])).intern(); //用时:2280
}
long end = System.currentTimeMillis();
System.out.println("用时:"+(end-start));
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用JProfiler 9.2.1 进行测试
jProfiler地址:https://blog.csdn.net/wei198621/article/details/112109608
结论:
P132-StringTable的垃圾回收测试 05:32
PrintStringTableStatistics 参数的使用
String 的垃圾回收:
- -Xms15m -Xmx15m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails
package com.tiza.jvm.str_intern;
/**
* Author: tz_wl
* Date: 2021/1/15 16:26
* Content:
*
* jdk 1.8
*
* String 的垃圾回收:
* -Xms15m -Xmx15m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails
*
*/
public class StringInternGCTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
String.valueOf(i).intern();
// valueof () --> Integer.toString(i); --> return new String(buf, true);
}
}
}
20210115
P133-G1垃圾收集器的String去重操作 08:38
去重指的是去除堆空间中的相同值
官方去重说明文档:
http://openjdk.java.net/jeps/192
14 垃圾回收概述
P134-垃圾回收相关章节的说明 08:18
P135-什么是GC,为什么需要GC 19:45
垃圾回收的三个问题
哪些内存需要回收
什么时候回收
如何回收
垃圾是指,运行程序中没有任何指针指向的对象。
内存溢出:没有空间被新的对象使用,就出现内存溢出了
内存泄漏:内存空间已经不使用了,但是进行回收的时候,无法回收相应的内存
P136-了解早期垃圾回收行为 04:08
P137-Java自动内存管理介绍 08:11
java自动进行内存分配及内存回收,这样可以降低内存泄漏和内存溢出的风险
把程序员从繁重的内存管理中释放出来,可以更加专注业务开发。
对于java开发人员来讲,自动的内存管理是个黑匣子,一旦出现内存溢出,无法快速定位问题和解决问题。
这样就需要添加必要的监控和调节工具。
堆空间是垃圾回收器的重点区域。
频繁回收YOUNG
较少回收OLD
基本不动perm(元空间)
15 垃圾回收相关算法
标记阶段:
引用计数算法
可达性分析算法
清除阶段:
标记-清除算法
复制算法
标记-压缩算法
分代收集算法
增量收集算法
分区算法
哪些是垃圾,如何回收;
标记阶段,清除阶段;
P138-垃圾回收相关算法概述 09:17
标记阶段:
引用计数算法
可达性分析算法
P139-引用计数算法的原理及优缺点 13:47
引用计数器算法有个严重的问题,无法处理循环引用的问题,这是个致命的缺陷,导致JAVA垃圾回收器没有使用此算法
P140-Java代码举例_Python的引用计数实施方案 08:25
package com.tiza.jvm.chapter15;
/**
*
* -XX:+PrintGCDetails
* @author leowei
* @date 2021/1/16 - 7:13
*/
public class RefCountGC {
private byte[] bigSize =new byte[5*1024*1024]; // 5M
Object reference=null;
public static void main(String[] args) {
//MethodWithoutGC();
MethodWithGC();
}
private static void MethodWithoutGC() {
RefCountGC obj1 = new RefCountGC();
RefCountGC obj2 = new RefCountGC();
obj1.reference =obj2;
obj2.reference =obj1;
obj1 = null;
obj2=null;
}
private static void MethodWithGC() {
RefCountGC obj1 = new RefCountGC();
RefCountGC obj2 = new RefCountGC();
obj1.reference =obj2;
obj2.reference =obj1;
obj1 = null;
obj2=null;
System.gc();
// obj1 obj2 存在循环引用问题,如果使用的是标记清除算法,无法及时回收,
// 但是单效果是由回收的,所以反证java平台没有使用垃圾回收算法
}
}
P141-可达性分析算法与GC Roots 12:41
可达性分析算法也叫做 根搜索算法、追踪性垃圾收集算法
Java\C# 用的都是(Tracing Garbage Collection )追踪性垃圾收集算法。
GC Roots : 跟对象,一组活跃的引用
Java 语言中哪些放在GC Roots ?
如果要使用可达性分析算法来判断内存是否可以回收,需要此分析工作在一个能够保障一致性的快照中进行。
因为有一致性的要求必须“Stop The World” —STW ,没有不停顿的可达性分析算法,即使是号称不会停顿的CMS收集器也需要STW。
P142-对象的finalization机制 18:34
当垃圾回收器发现没有任何一个引用指向一个对象,即:垃圾回收此对象之前,总会先调用这个对象的finalize()方法。
finalize() 方法允许在子类中被重写,用于在对象被回收时进行资源的释放。常用在finalize()方法中的操作有 : 关闭 文件、套接字、数据库连接等
虚拟机中的对象有三种状态:可触及、可复活、不可触及
P143-代码演示可复活的对象 07:37
package com.tiza.jvm.chapter15;
/**
* @author leowei
* @date 2021/1/16 - 9:21
*/
public class CanReliveObj {
private static CanReliveObj obj;
//注释,或者取消注释,看看打印的区别
// finalizer 只能执行一次
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("重写finalize()方法");
obj =this;
}
public static void main(String[] args) {
obj =new CanReliveObj();
try {
//对象第一次删除空自己
obj=null;
System.gc();
System.out.println(" first time doing gc");
//finalizer线程的优先级比较低,暂停2秒,等待它被执行
Thread.sleep(2000);
if(obj==null){
System.out.println("obj is dead ");
}else {
System.out.println("obj is still alive");
}
//对象第二次删除空自己
obj=null;
System.gc();
System.out.println(" second time doing gc ");
if(obj==null){
System.out.println("obj is dead ");
}else {
System.out.println("obj is still alive");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
P144-使用MAT查看GC Roots 13:42
MAT: Memory Analyzer Tool ,Java 堆内存分析器,用于查找内存泄漏以及查看内存消耗情况。
MAT基于ECLIPSE开发,是免费的性能分析工具,地址: http://www.eclipse.org/mat
mat 打开之前生成的.hprof 文件
P145-使用JProfiler进行GC Roots溯源 06:38
P146-使用JProfiler分析OOM 03:32
package com.tiza.jvm.chapter15;
import java.util.ArrayList;
/**
* @author leowei
* @date 2021/1/16 - 11:16
* -Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
*
* https://www.bilibili.com/video/BV1PJ411n7xZ?p=146
*
*/
public class HeapOOM {
byte[] buffer = new byte[1*1024*1024]; // 1MB
public static void main(String[] args) {
ArrayList<HeapOOM> list = new ArrayList<HeapOOM>();
int count=0;
try {
while (true){
list.add(new HeapOOM());
count++;
}
} catch (Exception e) {
System.out.println("count="+ count);
e.printStackTrace();
}
}
}
P147-标记-清除算法原理及优缺点 16:08
JVM中常用的三种垃圾收集算法:
Mark-Sweep 标记-清除算法
Copying 复制算法
Mark-Compact 标记压缩算法
标记-清除算法 McCarthy 等人1960年就提出了这种算法,并应用于Lisp语言。
注意,标记的是可达对象(非清除对象)
标记清除算法的优点:
标记清除算法的缺点:
效率不算高
在进行GC的时候,要停止整个运行的应用程序,导致用户体验差
清理出来的内存是不连续的,产生内存碎片(见上图),需要维护一个空闲列表。
P148-复制算法原理及优缺点 14:01
复制算法适用于,朝生夕死的,也就是留存率低的(copy对象少)
优点:
没有标记,清除过程,实现简单,运行高效
没有碎片问题
缺点:
需要两倍的内存空间
G1这种拆分大量region的GC,对象的位置要变,需要改变栈引用地址,开销不小。
P149-标记-压缩算法原理及优缺点 11:16
标记整理算法(标记压缩算法、标记清除压缩算法),是在标记清除算法的基础上,优化的结果
标记压缩是个移动式的算法
优点:
无内存碎片化的问题
消除了复制算法,内存减半的问题
缺点:
效率低于复制算法
移动对象的同时,需要调整引用地址
移动过程中,需要全程暂停用户应用程序,STW (所有三个算法都有次问题)
指针碰撞 (Bump the Pointer)
P150-不同指标上对比三种算法 04:38
相对来说 标记-压缩算法 更均衡一些,
P151-分代收集算法的说明 12:36
P152-增量收集算法原理及优缺点 09:14
一大块 分成很多小块 ,交替收集垃圾
P153-分区算法的说明 03:59
G1 使用分区算法
16 垃圾回收相关概念
System.gc()
内存溢出
内存泄漏
STW: Stop The Word
垃圾回收的并行与并发
安全点与安全区域
引用:
强引用
软引用
弱引用
虚引用
终结器引用
P154-垃圾回收相关概念的概述 10:11
P155-System.gc()的理解 08:47
System.gc() 触发的是Full GC .
System.gc() 无法保证对垃圾收集器的调用
System.gc() 只是提醒垃圾回收器触发垃圾回收,一般无需手动触发。
package com.tiza.jvm.chapter16;
/**
* @author leowei
* @date 2021/1/16 - 13:39
*/
public class SystemGCTest {
public static void main(String[] args) {
new SystemGCTest();
System.gc(); // 提醒JVM 执行垃圾收集行为,但是不能确定,是否马上执行
//System.gc() 调用 Runtime.getRuntime().gc();
// System.runFinalization(); //强制调用失去引用对象的finalize方法
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("SystemGCTest 重写了 finalizer () 方法 ");
}
}
P156-手动gc理解不可达对象的回收行为 10:18
package com.tiza.jvm.chapter16;
/**
* @author leowei
* @date 2021/1/16 - 15:17
*
* -XX:+PrintGCDetails
* https://www.bilibili.com/video/BV1PJ411n7xZ?p=156
*
*/
public class LocalVarGC {
public void localVarGC1() {
//回收不了
byte[] buffer = new byte[10 * 1024 * 1024]; // 10M
System.gc();
}
public void localVarGC2() {
byte[] buffer = new byte[10 * 1024 * 1024];
buffer=null;
System.gc();
}
public void localVarGC3() {
{
byte[] buffer = new byte[10 * 1024 * 1024]; //没有被释放掉
}
System.gc();
}
public void localVarGC4() {
{
byte[] buffer = new byte[10 * 1024 * 1024];
}
int value=10; // 由于这步 垃圾被回收
System.gc();
}
public void localVarGC5() {
localVarGC1();
System.gc(); //被回收
}
public static void main(String[] args) {
LocalVarGC local = new LocalVarGC();
local.localVarGC5();
}
}
P157-内存溢出的分析 11:40
内存溢出 (OOM Out Of Memory )
内存泄漏
javadoc中对OutOfMemoryError的解释是:没有空闲内存,并且垃圾收集器也无法提供更多的内存。
P158-内存泄漏的分析 13:04
内存泄漏: Memory Leak,也叫做存储渗漏。对象不再被程序用到,但是GC由不能回收它们。
1.单例模式
2.与外部资源关联,就需要手动关闭关联:
P159-StopTheWorld事件的理解 10:57
STW: Stop-the-World
GC时间发生时,产生应用程序的停顿,程序没有任何响应,像卡死一样。
警察办案,要现场保持不要有人随意漏洞
老妈打扫房屋,不要再丢垃圾了。
所有的GC,都有STW
CMS G1 等垃圾回收器都有STW ,
STW是JVM在后台自动发起和自动完成的。
https://www.bilibili.com/video/BV1PJ411n7xZ?p=159
P160-程序的并行与并发 06:33
并发Concurrent: 同一个处理器,同一个时间段,多个程序看似“同时运行”。
并行 Paraller: 多个CPU,真正的同时执行。
P161-垃圾回收的并行与并发 03:39
并行 Parallel:
串行 Serial :
并发 Concurrent : 用户线程与垃圾收集线程同时执行 (注意不是并行)。
P162-安全点与安全区域的说明 09:01
SafePoint 安全点:
程序在运行的时候,并不是任何时间点都能停顿下来的,在可以停顿的位置叫做安全点“safePoint”
抢先式中断:
主动式中断:
safe Region 安全区域
P163-Java中几种不同引用的概述 10:54
需求: 内存够,保留;内存不够,抛弃;----如缓存。
强引用、软引用、弱引用、虚引用 有什么区别?
如下四种,强软弱虚 使用强度一次递减。 继承 java.lang.ref.Reference
强引用: Stong Reference
软引用: Soft Reference
弱引用: Weak Reference
虚引用: Phantom Reference
强引用(Strong Reference): <引用关系在>, 死也不回收
软引用(Soft Reference):<引用关系在>, 内存不足则回收
弱引用(Weak Reference):<引用关系在>,发现即回收
虚引用(Phantom Reference):,
P164-强引用:不回收 06:35
package com.tiza.jvm.chapter16;
/**
* @author leowei
* @date 2021/1/16 - 20:38
*/
public class StrongReferenceTest {
public static void main(String[] args) {
// method01();
//method02();
method03();
}
private static void method01() {
StringBuffer str = new StringBuffer("hello tiza!");
System.gc(); // 由于gc不一定立即执行所以需要有个3秒的延时
try {
Thread.sleep(3000); //
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str); // hello tiza!
}
private static void method02() {
StringBuffer str = new StringBuffer("hello tiza!");
str=null;
System.gc(); // 由于gc不一定立即执行所以需要有个3秒的延时,本示例实测不sleep也没有问题,正常回收
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str); //null
}
private static void method03() {
StringBuffer str = new StringBuffer("hello tiza!"); // str 指向堆空间分配的StringBuffer地址
StringBuffer str1=str; // str1 指向堆空间分配的StringBuffer地址
str=null;
System.gc(); // 由于gc不一定立即执行所以需要有个3秒的延时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str);// null
System.out.println(str1);// hello tiza!
}
}
P165-软引用:内存不足即回收 16:30
软引用示例: 缓存
当内存足够时,不会回收软引用的可触及对象;
当内存不足时,会回收软引用的可触及对象。
package com.tiza.jvm.chapter16;
import java.lang.ref.SoftReference;
/**
* @author leowei
* @date 2021/1/16 - 20:57
*/
public class SoftReferenceTest {
public static class User{
public int id;
public String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
//method01();
//method02();
method03();
}
// 最基本的写法,不涉及垃圾回收
private static void method01() {
//创建对象,建立软引用 一步到位的写法,一行等于三行
SoftReference<User> userSoftRef = new SoftReference<User>(new User(1, "leo"));
//从软引用中重新获得强引用对象
System.out.println(userSoftRef.get());//User{id=1, name='leo'}
}
// 垃圾回收前 垃圾回收后 看效果 由于堆空间足够大,所以打印没有问题
private static void method02() {
//创建对象,建立软引用 一步到位的写法,一行等于三行
SoftReference<User> userSoftRef = new SoftReference<User>(new User(1, "leo"));
//从软引用中重新获得强引用对象
System.out.println(userSoftRef.get());//User{id=1, name='leo'}
System.gc();
System.out.println("After GC:");
System.out.println(userSoftRef.get());//User{id=1, name='leo'}
}
// 如果设置参数 -Xms10m -Xmx10m -XX:+PrintGCDetails , 新生代 : 老年代 = 1:2 = 3.3 : 6.7 ;
// 新生代 只有3.3M 再分成 8:1:1 ,不够放7M 空间的
private static void method03() {
//创建对象,建立软引用 一步到位的写法,一行等于三行
SoftReference<User> userSoftRef = new SoftReference<User>(new User(1, "leo"));
//从软引用中重新获得强引用对象
System.out.println(userSoftRef.get()); //User{id=1, name='leo'}
System.gc();
System.out.println("After GC:");
System.out.println(userSoftRef.get()); //User{id=1, name='leo'} //此时堆空间内存充足
try {
// 配合 “参数 -Xms10m -Xmx10m” 的设置,让系统认为资源紧张,执行垃圾回收
Byte[] b = new Byte[7 * 1024 * 1024 ]; //7M
} catch (Exception e) {
// java.lang.OutOfMemoryError: Java heap space
e.printStackTrace(); //此处会报错,报错之前会执行gc ,gc 会回收 软引用 userSoftRef
}finally {
// 在报OOM之前,垃圾回收器会回收软引用的可达对象。
System.out.println(userSoftRef.get()); //所以 Null
}
}
}
P166-弱引用:发现即回收 08:02
三级缓存
内存----一级
本地(硬盘)----二级
网络----三级
WeakHashMap ,
package com.tiza.jvm.chapter16;
import java.lang.ref.WeakReference;
/**
* @author leowei
* @date 2021/1/16 - 21:40
*/
public class WeakReferenceTest {
public static class User{
public int id;
public String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
method01();
}
// 弱引用特点 ,触发即回收
private static void method01() {
//创建对象,建立弱引用 一步到位的写法,一行等于三行
WeakReference<User> userSoftRef = new WeakReference<User>(new User(1, "leo"));
//从弱引用中重新获得强引用对象
System.out.println(userSoftRef.get());//User{id=1, name='leo'}
System.gc();
System.out.println("After GC:");
System.out.println(userSoftRef.get());//null 不管内存空间是否充足,都会回收它
}
}
P167-虚引用:对象回收跟踪 13:29
虚引用 Phantom Reference --幽灵引用,幻影引用
package com.tiza.jvm.chapter16;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
/**
* @author leowei
* @date 2021/1/16 - 21:58
*
* 这个里面有详细的过程,太复杂了,没有实操
* https://www.bilibili.com/video/BV1PJ411n7xZ?p=167
*
*/
public class PhantomReferenceTest {
public static void main(String[] args) {
ReferenceQueue<PhantomReferenceTest> phantomQueue = new ReferenceQueue<PhantomReferenceTest>();
PhantomReferenceTest obj = new PhantomReferenceTest();
PhantomReference<PhantomReferenceTest> phantomRef = new PhantomReference<PhantomReferenceTest>(obj, phantomQueue);
phantomRef.get(); // 强软弱虚 虚的什么也取不到数据
}
}
P168-终结器引用的介绍 01:45
FinalReference – 宋老师也没有讲,一笔带过
17 垃圾回收器
P169-垃圾回收器章节概览 05:07
P170-垃圾回收器的分类 15:31
垃圾收集器 在JVM规范中没有规定死,不同厂商有不同的实现。
JAVA不同版本新特性:
1.语法层面: Lambda表达式、switch、自动装箱拆箱、enum
2.API层面: Stream API、 Optional、 新的日期时间、集合框架、String
3.底层优化: JVM优化、GC优化、元空间的引入、静态域、字符串常量池
垃圾回收器分类
按线程数分类: 串行垃圾回收器、 并行垃圾回收器
按工作模式分类: 独占式垃圾回收器、 并发式垃圾回收器
按碎片处理方式: 压缩式垃圾回收器、 非压缩式垃圾回收器
单CPU使用串行垃圾回收器效果比并行的要好
对并发能力强的环境,用并行垃圾回收器效果好
P171-GC性能指标的整体说明 09:16
评估GC性能指标:
吞吐量: 运行用户代码时间/ (运行用户代码时间 + 内存垃圾回收时间 )
暂停时间: 执行垃圾收集器时,程序的工作线程被暂停的时间
内存占用: Java堆区所占内存大小
P172-吞吐量与暂停时间的对比说明 09:41
上面是 两三天 洗一次衣服
下面是 一天 洗一次衣服
暂停时间 pause time
用户交互的,关注的是低延迟,
P173-垃圾回收器的发展迭代史 17:06
Garbage Collection
Garbage Collector 垃圾回收器
java 垃圾回收器有哪些?
SerialGC
Parallel GC
CMS Concurrent Mark Sweep (jdk 1.4 )
G1 (JDK1.7) ----当前使用最多
Epsilon <No-Op 无操作> — (JDK11)
ZGC---------------------以后一定强推此
Shenandoah GC ------ (JDK12)
垃圾收集器发展史
7款经典垃圾收集器
串行回收器:Serial ; Serial Old ;
并行回收器:ParNew ; Parallel Scavenge ; Parallel Old
并发回收器:CMS ; G1 ;
https://www.oracle.com/technetwork/java/javase/tech/memorymanagement-whitepaper-1-150020.pdf
P174-垃圾回收器的组合关系 12:49
为什么需要这么多的收集器?
比如单CPU 用串行的 serial GC ,比用 Parallel Scavenge GC 效果更好,所以要选择适合自己的垃圾回收器。
P175-如何查看默认的垃圾回收器 06:22
-XX:_PrintCommandLineFlags
jinfo -flag
package com.tiza.jvm.chapter17;
import java.util.ArrayList;
/**
* @author leowei
* @date 2021/1/16 - 23:29
* -XX:+PrintCommandLineFlags ----- 1
* jinfo -flag UseParallelGC 21476 ----- 2
*
*
*
-XX:InitialHeapSize=266089344 -XX:MaxHeapSize=4257429504
-XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC ---- 显示使用的 垃圾回收器
C:\Users\wei19>jps
1648
16592
12516 Launcher
19604 Jps
21476 GCUseTest
15576 Main
19560 jprofiler.exe
C:\Users\wei19>jinfo -flag UseParallelGC 21476
-XX:+UseParallelGC
C:\Users\wei19>jinfo -flag UseParallelOldGC 21476
-XX:+UseParallelOldGC
C:\Users\wei19>jinfo -flag UseG1GC 21476
-XX:-UseG1GC
C:\Users\wei19>
*
*/
public class GCUseTest {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<byte[]>();
while (true){
byte[] arr = new byte[100];
list.add(arr);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
P176-Serial与Serial Old垃圾回收器的介绍 08:54
Serial回收器: 串行回收器, JDK1.3之前回收新生代唯一的选择
新生代
Serial GC 收集器采用复制算法、串行回收和Stop-the-World机制执行内存回收
老年代
Serial Old GC 使用标记压缩算法
Serial回收器的优势: 对于单CPU环境,无线程切换,简单高效
HotSpot虚拟机中,使用-XX:+UseSerialG 指定年轻代、老年代都使用串行收集器,也就是说新生代用Serial GC; 老年代用 Serial Old GC
P177-如何设置使用Serial垃圾回收器 04:43
package com.tiza.jvm.chapter17;
import java.util.ArrayList;
/**
* @author leowei
* @date 2021/1/16 - 23:29
*
* -XX:+PrintCommandLineFlags -XX:+UseSerialGC
* 新生代用Serial GC; 老年代用 Serial Old GC
*
*
*/
public class GCUseTest {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<byte[]>();
while (true){
byte[] arr = new byte[100];
list.add(arr);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
P178-ParNew垃圾回收器的介绍 07:22
ParNew:并行回收器 , 采用复制算法,STW机制,可以说ParNew是Serial收集器的多线程版本。
Par = Parallel ; New 表示只能处理新生代
ParNew 是JVM在Server模式下新生代的默认垃圾收集器。
-XX:+UseParNewGC 表示年轻代使用并行垃圾收集器,不影响老年代
-XX:ParallelGCThreads 限制线程数量,默认开启和CPU数据相同的线程数。
-XX:+UseConcMarkSweepGC ---- CMS
P179-如何设置使用ParNew垃圾回收器 03:58
P180-Parallel与Parallel Old垃圾回收器的介绍 08:56
Parallel Scavenge: 吞吐量优先策略
适合后台运算,无需太多交互的场景,如批量处理,科学计算等 。通常用于服务器端。
Parallel Scavenge: —新生代
Parallel Old GC : ----老年代 标记-压缩算法
-XX:+PrintCommandLineFlags
JDK8 默认Parallel -XX:+UseParallelGC
JDK9 默认 G1 -XX:+UseG1GC
P181-Parallel垃圾回收器的相关参数设置 17:29
-XX:+UseParallelGC ------- 相互激活,设置一个就可以了
-XX:+UseParallelOldGC ------- 相互激活,设置一个就可以了
-XX:ParallelGCThreads
-XX:MaxGCPauseMillis ----垃圾收集器最大停顿时间 STW时间
-XX:GCTimeRatio -----------衡量吞吐量值 (垃圾收集时间/(垃圾收集时间+用户程序运行时间))
-XX:+UseAdaptiveSizePolicy -----设置Parallel Scavenge收集器的自适用调节策略
P182-CMS垃圾回收器概述与工作原理 12:45
CMS回收器:低延迟
JDK1.5时期,Hotspot推出的一款强交互应用。现在大部分应用在互联网B/S服务器上,这类服务器注重相应速度,希望系统停顿时间最短。CMS 采用 标记-清除算法,也会使用Stop-the-World
CMS= Concurrent-Mark-Sweep
CMS作为老年代回收器,配合使用Serial / ParaNew 回收新生代。
CMS的工作原理:
初始标记(Initial-Mark) ---------标记出GC Roots 对象
并发标记(Concurrent-Mark)------标记出GC Roots 关联的所有对象(耗时较长)
重新标记(Remark)------------------修正第二部的数据
并发清除(Concurrent-Sweep)----清理标记阶段所有对象,释放内存空间
P183-CMS的特点与弊端分析 15:58
CMS使用标记-清除算法,会产生一些内存碎片,无法使用指针碰撞(Bump the Pointer),只能选择空闲列表(Free List ) 执行内存分配。
标记清除有内存碎片,为什么不用标记压缩呢?
答:有并发处理,不可以用并行压缩改变内存地址,
CMS的优点:
并发收集
低延迟
CMS弊端:
产生内存碎片,不利于处理大对象。
CMS收集器堆CPU资源非常敏感
CMS收集器无法处理浮动垃圾 ,第二个阶段并发标记阶段用户线程产生的最新垃圾,叫做浮动垃圾。
设置参数:
-XX:+UseCMSCompactAtFullCollection —指定执行完Full GC 后,堆内存空间进行压缩整理,避免内存碎片的产生。不过停顿时间较长。
-XX:CMSFullGCsBeforeCompaction ---- 设置执行多少次 Full GC 后,才对内存空间进行压缩整理。
-XX:ParallelCMSThreads -----设置CMS线程数量
P184-CMS垃圾回收器的参数设置 09:06
设置参数:
-XX:+UseConcMarkSweepGC —使用CMS(用于老年代) 自动触发 ParNew 用于新生代
-XX:CMSInitiatingOccupanyFraction — 提前进行垃圾回收设置 (68%–JDK5)(92%–JDK6)
-XX:+UseCMSCompactAtFullCollection —指定执行完Full GC 后,堆内存空间进行压缩整理,避免内存碎片的产生。不过停顿时间较长。
-XX:CMSFullGCsBeforeCompaction ---- 设置执行多少次 Full GC 后,才对内存空间进行压缩整理。
-XX:ParallelCMSThreads -----设置CMS线程数量
P185-CMS的小结及后续JDK版本中的变化 03:45
口令:
最小化内存: Serail GC
最大化吞吐量: Parallel GC
最小化中断(低延时): CMS
JDK9 开始废弃 CMS ; JDK14 删除CMS .
P186-认识G1垃圾回收器 14:52
G1:区域分代化 (G1: G First )
G1的目标:在延迟可控的情况下,获得尽可能高的吞吐量,希望达到“全功能收集器”的目标。但是后期ZGC会替换G1;
为什么名字叫G1 (G First)
G1是个并行的回收器,把堆内存分割为很多不相关的区域(Region-物理上不连续的)
G1的侧重点在于回收垃圾最大的区间(Region),所以我们给G1取了一个名字垃圾优先(Garbage First)
G1:主要针对多核处理器及大容量内存的适用场景
JDK9以后默认垃圾回收器为G1
P187-G1垃圾回收器的优势和不足 20:24
G1回收器特点
1 并行与并发
并行性 —多个线程同时工作,STW
并发性 —
2 分代收集
G1 会区分年轻代、老年代 ,但是G1由于有Region,G1不要求区域连续。
3 空间整合
Region内部适用的是复制算法,整体上看是标记压缩算法,所以不存在内存碎片。
4 可预测的停顿时间模型 (soft real-time 软实时)
由于分区,G1可以选择部分区域进行回收,缩小了回收范围,对全局停顿有较好的控制。
每次回收价值最大的Region
G1的缺点:
G1 占用额外的内存空间,内存在6-8 G 以上使用G1好, 小于6-8G ,CMS 更好,
P188-G1的参数设置 09:12
-XX:+UseG1GC ----- 手动指定G1用于垃圾回收
-XX:G1HeapRegionSize ----设置每个Region大小
-XX:MaxGCPauseMillis -----GC停顿时间指标(默认200ms)
-XX:ParallelGCThread -------STW工作时GC线程值
-XX:ConcGCThreads ----设置并发标记的线程数
-XX:InitiatingHeapOccupancyPercent --------设置触发并发GC周期的java堆占用率阈值,超过此值就触发GC,默认45
G1启动三步骤
1.开启G1垃圾收集器
2.设置堆的最大内存
3.设置最大停顿时间
P189-G1在生产环境的适用场景 03:58
G1针对服务器端的应用,针对具有大内存、多处理器的机器,普通大小的堆了表现不好。
最主要应用于低延时,具有大堆应用。
G1可以替换CMS
P190-region的使用介绍 11:36
分区Region:化整为零
G1收集器,将JAVA堆划分为2048个大小相同的独立Region块,每个Region块的大小在1~32M,且为2的N次幂 。 1M,2M,4M,8M,16M,32M;
可以通过-XX:G!HeapRegionSize进行设定。
eden,survivor,old,humongous
对象大小大于1.5个Region大小是,使用humongous.
指针碰撞 Bump the pointer
TLAB
P191-G1垃圾回收器的主要回收环节 08:14
G1回收三个环节
年轻代GC Young GC
老年代并发标记过程 Concurrent Marking
混合回收 Mixed GC
P192-记忆集与写屏障 08:24
Remembered Set (R Set — 记忆集)
P193-G1垃圾回收过程的详细说明 24:16
P194-G1垃圾回收的优化建议 04:11
P195-7种经典的垃圾回收器总结与调优建议 14:02
没有最好的垃圾收集器,更没有万能的收集器
调优永远是针对特定场景、特定需求,不存在一劳永逸的收集器。
P196-常用的显示GC日志的参数 13:30
垃圾回收设置参数:
-verbose:gc
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeSTamps
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
P197-GC日志中垃圾回收数据的分析 09:03
DefNew
PSYoungGen
ParNew
ParOldGen
garbage-first heap
Allocation Failure
PSYoungGen
Minor GC
PSYoungGen:
Full GC
P198-举例说明日志中堆空间数据如何解读 10:16
P199-日志分析工具的使用 07:29
日志查看工具
GCViewer,
GCEasy — 在线官网 gceasy.io (建议使用)
GCHisto
P200-新时期的Epsilon和Shenandoah垃圾回收器 13:21
Serial GC
CMS GC —JDK9中废弃,JDK14版本中已经移除
G1 GC — 不断改进中 JDK9开始默认GC
Epsilon GC — http://openjdk.java.net/jeps/318
Shenandoah GC ----- RedHat 研发的
ZGC
P201-革命性的ZGC的性能介绍 09:03
https://docs.oracle.com/en/java/javase/12/gctuning
JDK14开始windows可以使用
P202-其他的厂商的垃圾回收器 01:41
AliGC
Zing GC