大型企业JVM性能调优实战Java垃圾收集器及gcroot
本次的系列课程需要今天再次分享这次的课程,今晚分享之后就完结了,欢迎大家来一起阅读!
02:JVM类加载机制
大型企业JVM性能调优实战之gcroot
01:JVM类加载机制 - 类加载的生命周期
概述
java的class类文件实际上是二进制(字节码)文件格式,class文件中包含
了java虚拟机指令集和符号表以及若干其他辅助信息。实际上虚拟机载入和
执行同一种平台无关的字节码(class文件),实现了平台无关性。
而java的类加载机制指的是虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
在Java的语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的。
指的是类从被加载到虚拟机内存开始,到卸载出内存为止,一共要经历7个阶段:
02:类加载器-ClassLoader
目标
明白和了解类加载器的加载过程和运行流程
分析
类加载器负责装入类,搜索网络,jar、zip、文件夹、二进制数据、内存等制定位置的类资源。一个java程序运行,最少有三个类加载器实例,负责不同类的加载。“将class文件加载进JVM的方法区,并在方法区中创建一个java.lang.Class对象作为外界访问这个类的接口。”实现这一动作的代码模块称为类加载器
案例
类和类加载器:对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在虚拟机中的唯一性。
通俗点说:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才用意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。这里的“相等” 包括: Class对象的 equals() 方法、isInstance()方法,也包括 instanceof关键字
package com.aicode.classloaderdemo.chapter01;
import java.io.IOException;
import java.io.InputStream;
/**
* 使用自定义类加载器加载Class对象
* 系统默认类加载器加载Class 对象
* 属于两个不同的类
**/
public class ClassLoaderTest1 {
public static void main(String[] args) throws
Exception{
// 自定义 类加载器
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name)
throws ClassNotFoundException {
try {
package com.aicode.classloaderdemo.chapter01;
import java.io.IOException;
import java.io.InputStream;
/**
* 使用自定义类加载器加载Class对象
* 系统默认类加载器加载Class 对象
* 属于两个不同的类
**/
public class ClassLoaderTest1 {
public static void main(String[] args) throws
Exception{
// 自定义 类加载器
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name)
throws ClassNotFoundException {
try {
运行结果如下:
com.aicode.classloaderdemo.chapter01.ClassLoaderTest1$1
@2f0e140b
sun.misc.Launcher$AppClassLoader@18b4aac2
false
小结可以得出结论,即使是同一个类,用不同的加载器去加载类,出现两个class不一致的情况。所有的加载器都存放在: sun.misc.Launcher 包下
03:类加载器-ClassLoader类加载器的分类
目标
掌握类加载器的分类和它们具体的含义
分析
代码如下:
package com.aicode.classloaderdemo.chapter01;
public class ClassLoaderView {
public static void main(String[] args) throws
Exception{
// 加载核心的类加载器
System.out.println("核心类库加载器:" +
ClassLoaderView.class.getClassLoader().loadClass(Strin
g.class.getName()).getClassLoader());
// 加载拓展类库加载器
System.out.println("拓展类库加载器:" +
ClassLoaderView.class.getClassLoader().loadClass("com.
sun.nio.zipfs.ZipInfo").getClassLoader());
// 加载应用程序加载器
System.out.println("应用程序加载器:" +
ClassLoaderView.class.getClassLoader());
// 双亲委派模型 Parents Delegation Model
System.out.println("应用程序加载器的父类"
+ClassLoaderView.class.getClassLoader().getParent());
System.out.println("应用程序加载器的父类的父类"
+ClassLoaderView.class.getClassLoader().getParent().ge
tParent());
System.out.println(System.getProperty("java.class.pat
h"));
}
}
04:类加载器-启动类加载器-BootStrap
ClassLoader
目标
掌握核心加载器
分析
启动类加载器-BootStrap ClassLoader,又称根加载器
每次执行 java 命令时都会使用该加载器为虚拟机加载核心类。该加载器是由 native code 实现,而不是 Java 代码,加载类的路径为
/jre/lib 。特别的
/jre/lib/rt.jar 中包含了 sun.misc.Launcher 类,而 sun.misc.Launcher 和
AppClassLoader 都是 sun.misc.Launcher的内部类,所以拓展类加载器和系统类加载器都是由启动类加载器加载的。
疑问:什么是核心类
1:其实就是我们每天在开发的使用java.lang,java.util,等这些包下的类。而这个类都是存放在 jre/lib/rt.jar 中
先定位 String.class 这个类 。
查看某个类:
查看类所在位置
定位jar包所在位置
最后展示如下:
小结
疑虑:为什么要学习和掌握这个知识点呢?
答案是:告诉我们我们在开发java项目的时候,我们虽然使用的开发工具是idea或eclicpse。在开发工具中已经帮我们提供了一个java的环境,这样我们就可以直接开发和使用了,我们在开发过程中之所以能直接去调用java提供的核心类库(java.lang、java.io等)。是因为我们在启动和运行项目的时候,java提供的类加载器会自动去找到这些jar包,并且把我们当前项目的java文件进行编译一起编译到jvm中。
05:类加载器-扩展类加载器(ExtensionClassLoader)
目标
用于加载拓展库中的类。拓展库路径为
/jre/lib/ext/ 。实现类为 sun.misc.Launcher$ExtClassLoader。
分析源码
private static File[] getExtDirs() {
String var0 =
System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new
StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];
for(int var4 = 0; var4 < var3; ++var4)
{
var1[var4] = new
File(var2.nextToken());
}
} else {
var1 = new File[0];
}
return var1;
}
可以去打印
System.out.println(System.getProperty("java.ext.dirs")
);
结果如下:
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext;C:\Windows\Sun\Java
\lib\ext
小结
扩展性的包,都属于java第三方依赖的jar,这个扩展包是实际的开发中可以删除,它都属于实验性阶段。并没有纳入到java的开发标准中。本人怀疑:就是oracle的jdk的开发团队也在学习别人好的东西,但是又怕忘记所以把他放入到了ext中,用于后记的升级和开发维护中。
06:类加载器-应用程序加载器-AppClassLoader
目标
掌握和了解应用程序加载
分析
打印:
System.out.println(System.getProperty("java.class.path"
));
结果如下:
==============================AppLoader默认加载器ext包
======================
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_211\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\accessbridge-64.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext\cldrdata.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext\dnsns.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext\jaccess.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext\jfxrt.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext\localedata.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext\nashorn.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext\sunec.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext\sunjce_provider.ja
r;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext\sunmscapi.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext\sunpkcs11.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\ext\zipfs.jar;
==============================AppLoader默认加载器核心lib
包======================
C:\Program Files\Java\jdk1.8.0_211\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_211\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_211\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_211\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_211\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_211\jre\lib\managementagent.jar;
C:\Program Files\Java\jdk1.8.0_211\jre\lib\plugin.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_211\jre\lib\rt.jar;
===============================java项目编译的class文件目录
===========================
C:\czbk\备课\07-高级架构师就业加强课-88888888888888\05_Jvm
优化及面试热点深入解析\代码\classloaderdemo\target\classes;
===========================项目依赖的mavenjar包
==============================
C:\respository\org\springframework\boot\spring-bootstarter\2.2.1.RELEASE\spring-boot-starter-
2.2.1.RELEASE.jar;
C:\respository\org\springframework\boot\springboot\2.2.1.RELEASE\spring-boot-2.2.1.RELEASE.jar;
C:\respository\org\springframework\springcontext\5.2.1.RELEASE\spring-context-
5.2.1.RELEASE.jar;
C:\respository\org\springframework\springaop\5.2.1.RELEASE\spring-aop-5.2.1.RELEASE.jar;
C:\respository\org\springframework\springbeans\5.2.1.RELEASE\spring-beans-5.2.1.RELEASE.jar;
C:\respository\org\springframework\springexpression\5.2.1.RELEASE\spring-expression-
5.2.1.RELEASE.jar;
C:\respository\org\springframework\boot\spring-bootautoconfigure\2.2.1.RELEASE\spring-boot-autoconfigure-
2.2.1.RELEASE.jar;
C:\respository\org\springframework\boot\spring-bootstarter-logging\2.2.1.RELEASE\spring-boot-starterlogging-2.2.1.RELEASE.jar;
C:\respository\ch\qos\logback\logbackclassic\1.2.3\logback-classic-1.2.3.jar;
C:\respository\ch\qos\logback\logbackcore\1.2.3\logback-core-1.2.3.jar;
C:\Program Files\Java\jdk1.8.0_211\jre\lib\plugin.jar;
C:\Program
Files\Java\jdk1.8.0_211\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_211\jre\lib\rt.jar;
===============================java项目编译的class文件目录
===========================
C:\czbk\备课\07-高级架构师就业加强课-88888888888888\05_Jvm
优化及面试热点深入解析\代码\classloaderdemo\target\classes;
===========================项目依赖的mavenjar包
==============================
C:\respository\org\springframework\boot\spring-bootstarter\2.2.1.RELEASE\spring-boot-starter-
2.2.1.RELEASE.jar;
C:\respository\org\springframework\boot\springboot\2.2.1.RELEASE\spring-boot-2.2.1.RELEASE.jar;
C:\respository\org\springframework\springcontext\5.2.1.RELEASE\spring-context-
5.2.1.RELEASE.jar;
C:\respository\org\springframework\springaop\5.2.1.RELEASE\spring-aop-5.2.1.RELEASE.jar;
C:\respository\org\springframework\springbeans\5.2.1.RELEASE\spring-beans-5.2.1.RELEASE.jar;
C:\respository\org\springframework\springexpression\5.2.1.RELEASE\spring-expression-
5.2.1.RELEASE.jar;
C:\respository\org\springframework\boot\spring-bootautoconfigure\2.2.1.RELEASE\spring-boot-autoconfigure-
2.2.1.RELEASE.jar;
C:\respository\org\springframework\boot\spring-bootstarter-logging\2.2.1.RELEASE\spring-boot-starterlogging-2.2.1.RELEASE.jar;
C:\respository\ch\qos\logback\logbackclassic\1.2.3\logback-classic-1.2.3.jar;
C:\respository\ch\qos\logback\logbackcore\1.2.3\logback-core-1.2.3.jar;
小结
从上面打印的结果可以清晰的看到:应用程序app加载器,可以很清楚的看到我们java项目整个加载和过程是什么样子的。我们的编写的类和用maven依赖的jar包是如何加入到jvm虚拟机的。
07:类加载器-JVM如何知道我们的类在何方
目标
掌握JVM如何知道我们的类在何方
分析
class信息存放在不同的位置,桌面的jar,项目bin目录,target目录等等。
步骤
1:查看openjdk源代码,sun.misc.Launcher.AppClassLoader
3:验证过程:利用jps、jcmd两个命令
jps:查看本机java进程
代码
package com.aicode.classloaderdemo.chapter01;
public class Main {
public static void main(String[] args) throws
Exception{
System.out.println("Hello world....");
System.in.read();
}
}
这个命令其实还发非常有用处,在实际的应用开发和生产中,有些时候我们不知道当前项目经常出现端口被占用的问题,可以通过jps来查看是否还有java程序正在运行,如果有可以执行命令 taskkill /PID 309184 /F
查看运行时配置:jcm 进程号 VM.system_properties
jcmd help
Usage: jcmd <pid | main class> <command
...|PerfCounter.print|-f file>
or: jcmd -l
or: jcmd -h
command must be a valid jcmd command for the
selected jvm.
Use the command "help" to see which commands are
available.
If the pid is 0, commands will be sent to all Java
processes.
The main class argument will be used to match
(either partially
or fully) the class used to start Java.
If no options are given, lists Java processes (same
as -p).
PerfCounter.print display the counters exposed by
this process
-f read and execute commands from the file
-l list JVM processes on the local machine
-h this help
使用方式如下:
# 格式 jcmd pid help
>jcmd 307512 help
307512:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
JFR.check
VM.native_memory
VM.check_commercial_features
VM.unlock_commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
VM.classloader_stats
GC.rotate_log
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.finalizer_info
GC.heap_info
GC.run_finalization
GC.run
VM.uptime
VM.dynlibs
VM.flags
VM.system_properties
VM.command_line
VM.version
help
使用如下:
> jcmd 307512 VM.system_properties
#Mon Dec 02 10:02:57 CST 2019
java.runtime.name=Java(TM) SE Runtime Environment
sun.boot.library.path=C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\bin
java.vm.version=25.211-b12
java.vm.vendor=Oracle Corporation
java.vendor.url=http\://java.oracle.com/
path.separator=;
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
file.encoding.pkg=sun.io
user.script=
user.country=CN
sun.java.launcher=SUN_STANDARD
sun.os.patch.level=
java.vm.specification.name=Java Virtual Machine
Specification
user.dir=C\:\\czbk\\\u5907\u8BFE\\07-
\u9AD8\u7EA7\u67B6\u6784\u5E08\u5C31\u4E1A\u52A0\u5F3
A\u8BFE-88888888888888\\05_Jvm\u4F18\u5316\u53CA\u9
762\u8BD5\u70ED\u70B9\u6DF1\u5165\u89E3\u6790\\\u4EE3
\u7801\\classloader-demo
java.runtime.version=1.8.0_211-b12
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs=C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\endorsed
os.arch=amd64
java.io.tmpdir=C\:\\Users\\86150\\AppData\\Local\\Tem
p\\
line.separator=\r\n
java.vm.specification.vendor=Oracle Corporation
user.variant=
os.name=Windows 10
sun.jnu.encoding=GBK
java.library.path=C\:\\Program
Files\\Java\\jdk1.8.0_211\\bin;C\:\\Windows\\Sun\\Jav
a\\bin;C\:\\Windows\\system32;C\:\\Windows;C\:\\tools
\\id
ea\\IntelliJ IDEA
2019.1.3\\jre64\\\\bin;C\:\\tools\\idea\\IntelliJ
IDEA 2019.1.3\\jre64\\\\bin\\server;C\:\\Xshell
6\\;C\:\\Program Files (x
86)\\Intel\\Intel(R) Management Engine
Components\\iCLS\\;C\:\\Program
Files\\Intel\\Intel(R) Management Engine
Components\\iCLS\\;C\:\\Windo
ws\\system32;C\:\\Windows;C\:\\Windows\\System32\\Wbe
m;C\:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C
\:\\Windows\\System32\\OpenSSH\\;C\
:\\Program Files (x86)\\Intel\\Intel(R) Management
Engine Components\\DAL;C\:\\Program
Files\\Intel\\Intel(R) Management Engine
Components\\D
AL;C\:\\Program Files (x86)\\Intel\\Intel(R)
Management Engine Components\\IPT;C\:\\Program
Files\\Intel\\Intel(R) Management Engine Componen
ts\\IPT;C\:\\Program Files (x86)\\NVIDIA
Corporation\\PhysX\\Common;C\:\\Program Files\\NVIDIA
Corporation\\NVIDIA NvDLISR;C\:\\Program Files
\\Java\\jdk1.8.0_211\\bin;C\:\\Program
Files\\Redis\\;C\:\\tools\\apache-maven-
3.6.1\\bin;C\:\\Users\\86150\\TortoiseSVN\\bin;C\:\\P
rogram Fi
les\\erl10.0.1\\bin;C\:\\tools\\rabbitmq_server-
3.7.7\\sbin;C\:\\Program Files\\nodejs\\;C\:\\Program
Files\\Git\\cmd;C\:\\Program Files\\Tor
toiseGit2\\bin;C\:\\Users\\86150\\AppData\\Local\\Mic
rosoft\\WindowsApps;C\:\\Users\\86150\\AppData\\Local
\\Pandoc\\;C\:\\Users\\86150\\AppDa
ta\\Local\\Programs\\Microsoft VS
Code\\bin;C\:\\Users\\86150\\AppData\\Roaming\\npm;C\
:\\Program Files\\nodejs;;.
java.specification.name=Java Platform API
Specification
java.class.version=52.0
sun.management.compiler=HotSpot 64-Bit Tiered
Compilers
os.version=10.0
user.home=C\:\\Users\\86150
user.timezone=Asia/Shanghai
java.awt.printerjob=sun.awt.windows.WPrinterJob
file.encoding=UTF-8
java.specification.version=1.8
java.class.path=C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\charsets.jar;C\:
\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\deploy.jar;C
\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\ext\\accessbridge-64.jar;C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\ext\\cldrdata.ja
r;C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\ext\\dnsns.jar;C
\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\ext\\jaccess.jar
;C\:\\Pr
ogram
Files\\Java\\jdk1.8.0_211\\jre\\lib\\ext\\jfxrt.jar;C
\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\ext\\localedata.
jar;C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\ext\\nashorn.jar
;C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\ext\\sunec.jar;C
\:\\Program Files\\Ja
va\\jdk1.8.0_211\\jre\\lib\\ext\\sunjce_provider.jar;
C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\ext\\sunmscapi.j
ar;C\:\\Program Files\
\Java\\jdk1.8.0_211\\jre\\lib\\ext\\sunpkcs11.jar;C\:
\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\ext\\zipfs.jar;C
\:\\Program Files\\Java\\
jdk1.8.0_211\\jre\\lib\\javaws.jar;C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\jce.jar;C\:\\Pro
gram Files\\Java\\jdk1.8.0_211\\jre\\lib
\\jfr.jar;C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\jfxswt.jar;C\:\\
Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\jsse.jar;C\:\\Pr
ogra
m Files\\Java\\jdk1.8.0_211\\jre\\lib\\managementagent.jar;C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\plugin.jar;C\:\\
Program Files\\
Java\\jdk1.8.0_211\\jre\\lib\\resources.jar;C\:\\Prog
ram Files\\Java\\jdk1.8.0_211\\jre\\lib\\rt.jar;
==========================这里加载的
=============================
C\:\\czbk\\\u5907\u8BFE\\07-\u9AD8\u7EA7
\u67B6\u6784\u5E08\u5C31\u4E1A\u52A0\u5F3A\u8BFE-
88888888888888\\05_Jvm\u4F18\u5316\u53CA\u9762\u8BD5\
u70ED\u70B9\u6DF1\u5165\u89E3\u6790\\\u
4EE3\u7801\\classloader-demo\\target\\classes;
C\:\\respository\\org\\springframework\\boot\\springboot-starter\\2.2.1.RELEASE\\spring-boot-s
tarter-
2.2.1.RELEASE.jar;C\:\\respository\\org\\springframew
ork\\boot\\spring-boot\\2.2.1.RELEASE\\spring-boot-
2.2.1.RELEASE.jar;C\:\\resposi
tory\\org\\springframework\\springcontext\\5.2.1.RELEASE\\spring-context-
5.2.1.RELEASE.jar;C\:\\respository\\org\\springframew
ork\\spring-ao
p\\5.2.1.RELEASE\\spring-aop-
5.2.1.RELEASE.jar;C\:\\respository\\org\\springframew
ork\\spring-beans\\5.2.1.RELEASE\\spring-beans-
5.2.1.RELEAS
E.jar;C\:\\respository\\org\\springframework\\springexpression\\5.2.1.RELEASE\\spring-expression-
5.2.1.RELEASE.jar;C\:\\respository\\org\\sp
ringframework\\boot\\spring-bootautoconfigure\\2.2.1.RELEASE\\spring-bootautoconfigure-
2.2.1.RELEASE.jar;C\:\\respository\\org\\springframe
work\\boot\\spring-boot-starterlogging\\2.2.1.RELEASE\\spring-boot-starter-logging-
2.2.1.RELEASE.jar;C\:\\respository\\ch\\qos\\logback\
\log
back-classic\\1.2.3\\logback-classic-
1.2.3.jar;C\:\\respository\\ch\\qos\\logback\\logback
-core\\1.2.3\\logback-core-1.2.3.jar;C\:\\resposito
ry\\org\\apache\\logging\\log4j\\log4j-toslf4j\\2.12.1\\log4j-to-slf4j-
2.12.1.jar;C\:\\respository\\org\\apache\\logging\\lo
g4j\\log4j-api\\
2.12.1\\log4j-api-
2.12.1.jar;C\:\\respository\\org\\slf4j\\jul-toslf4j\\1.7.29\\jul-to-slf4j-
1.7.29.jar;C\:\\respository\\jakarta\\annotatio
n\\jakarta.annotation-api\\1.3.5\\jakarta.annotationapi-
1.3.5.jar;C\:\\respository\\org\\springframework\\spr
ing-core\\5.2.1.RELEASE\\spring
-core-
5.2.1.RELEASE.jar;C\:\\respository\\org\\springframew
ork\\spring-jcl\\5.2.1.RELEASE\\spring-jcl-
5.2.1.RELEASE.jar;C\:\\respository\\org
\\yaml\\snakeyaml\\1.25\\snakeyaml-
1.25.jar;C\:\\respository\\org\\slf4j\\slf4japi\\1.7.29\\slf4j-api-
1.7.29.jar;C\:\\tools\\idea\\IntelliJ
IDEA 2019.1.3\\lib\\idea_rt.jar
user.name=86150
java.vm.specification.version=1.8
sun.java.command=com.aicode.classloaderdemo.chapter01
.Main
java.home=C\:\\Program Files\\Java\\jdk1.8.0_211\\jre
sun.arch.data.model=64
user.language=zh
java.specification.vendor=Oracle Corporation
awt.toolkit=sun.awt.windows.WToolkit
java.vm.info=mixed mode
java.version=1.8.0_211
java.ext.dirs=C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\ext;C\:\\Windows
\\Sun\\Java\\lib\\ext
sun.boot.class.path=C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\resources.jar;C\
:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\rt.jar;
C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\sunrsasign.jar;C
\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\jsse.jar;C\:\\Pr
ogram File
s\\Java\\jdk1.8.0_211\\jre\\lib\\jce.jar;C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\lib\\charsets.jar;C\:
\\Program Files\\Java\\jdk1.8.0_21
1\\jre\\lib\\jfr.jar;C\:\\Program
Files\\Java\\jdk1.8.0_211\\jre\\classes
java.vendor=Oracle Corporation
file.separator=\\
java.vendor.url.bug=http\://bugreport.sun.com/bugrepo
rt/
sun.io.unicode.encoding=UnicodeLittle
sun.cpu.endian=little
sun.desktop=windows
sun.cpu.isalist=amd64
08:类加载器-类不会重复加载
目标
验证类会不会重复加兹安
分析
类的唯一性:同一个类加载器,类名一样,代表是同一个类。
识别方式:ClassLoader Instance id + packagename + className
验证方式:使用类加载器,对同一个class类的不同斑斑,进行多次加载,检查是否会加载到最新的代码。
代码1:
在磁盘目录下新建一个c://test/HelloService.java
public class HelloService {
public static String value = getValue();
static{
System.out.println("*************** static
area code");
}
public static String getValue(){
System.out.println("************** static
method");
return "success";
}
public void test(){
System.out.println("hello 1111111111" +
value);
}
}
2:然后进行编译javac HelloService.java
> javac com/aicode/HelloService.java
3:生成 HelloService.class 文件即可。
4:自定义类加载器
package com.aicode.classloaderdemo.chapter01;
import java.net.URL;
1
2
3
import java.net.URLClassLoader;
public class LoadTest {
public static void main(String[] args) throws
Exception{
URL url = new URL("file:C:\\test\\");//jvm需要
加载的类存放的位置
//定义一个父加载器
URLClassLoader classLoader = new
URLClassLoader(new URL[]{url});
//创建一个新的类加载器
while (true){
// 1:这里并不会去执行静态成员的加载,只会做静态成
员的存储和划分
Class clz =
classLoader.loadClass("HelloService");
System.out.println("HelloService 使用的类加载
器是:" +clz.getClassLoader());
// 2:通过反射实例化对象,真正的执行在创建对象的时候
静态成员会执行和初始化
Object newIntance = clz.newInstance();
Object test =
clz.getMethod("test").invoke(newIntance);
Thread.sleep(3000);
}
}
}
这个时候如果我们去修改了,然后重新编译一下,看是否能够自动加载我们的类呢?如下:
然后重新编译javac HelloService.java 你会发现并没有去加载类信息。
如果你重新运行以后即可加载修改的信息
小结
可以看出来java的代码是基于编译和运行的机制,都是通过类加载器去加载和装配的
通过上面的例子也可以告诉我们,我们在平时的开发中,为什么修改了java的类、修改了配置文件都需要重新启动的原因,因为修改了类它并不会重新把修改和编译好的class重新调用类加载器去执行。如果你要实现热部署的功能可以把类加载器放入到死循环中,每次保证类加载是全新的即可:
代码如下:
package com.aicode.classloaderdemo.chapter01;
import java.net.URL;
import java.net.URLClassLoader;
public class LoadTest {
public static void main(String[] args) throws
Exception{
URL url = new URL("file:C:\\test\\");//jvm需
要加载的类存放的位置
//创建一个新的类加载器
while (true){
//定义一个父加载器
URLClassLoader classLoader = new
URLClassLoader(new URL[]{url});
// 1:这里并不会去执行静态成员的加载,只会做静
态成员的存储和划分
Class clz =
classLoader.loadClass("HelloService");
System.out.println("HelloService 使用的类
加载器是:" +clz.getClassLoader());
// 2:通过反射实例化对象,真正的执行在创建对象的
时候静态成员会执行和初始化
Object newIntance = clz.newInstance();
Object test =
clz.getMethod("test").invoke(newIntance);
Thread.sleep(3000);
}
}
}
09:类加载器-类的卸载
目录
掌握类的卸载
分析
卸载类必须满足两个条件
该class所有的实例都已经被GC。
加载该类的ClassLoader实例已经被GC。在jvm的启动项里增加命令参数:verbose:class 参数,输出类加载和卸载的日志信息。
代码
package com.aicode.classloaderdemo.chapter01;
import java.net.URL;
import java.net.URLClassLoader;
public class LoadTest {
public static void main(String[] args) throws
Exception
URL url = new URL("file:C:\\test\\");//jvm需要
加载的类存放的位置
//创建一个新的类加载器
URLClassLoader classLoader = new
URLClassLoader(new URL[]{url});
while (true){
if(classLoader==null)break;
// 1:这里并不会去执行静态成员的加载,只会做静态成
员的存储和划分
Class clz =
classLoader.loadClass("HelloService");
System.out.println("HelloService 使用的类加载
器是:" +clz.getClassLoader());
// 2:通过反射实例化对象,真正的执行在创建对象的时候
静态成员会执行和初始化
Object newIntance = clz.newInstance();
Object test =
clz.getMethod("test").invoke(newIntance);
Thread.sleep(3000);
//GC
classLoader = null;
newIntance = null;
}
System.gc();
Thread.sleep(10000);
}
}
1:增加启动运行的参数-verbose:class
2:查看日志如下:
[Unloading class HelloService 0x00000007c0061028]
[Loaded java.lang.Shutdown from C:\Program
Files\Java\jdk1.8.0_211\jre\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from C:\Program
Files\Java\jdk1.8.0_211\jre\lib\rt.jar]
小结
静态代码块执行,并不会在类加载器加载的时候执行,而是在创建第一次实例对象的时候才会去执行。
并且gc并不需要我们自己去控制和调用,jvm会自动维护一个垃圾回收的机制。
10:类加载器-双亲委派模型
目标
掌握和了解类加载器的双亲委派模型
基本概念
如果一个类加载器收到了加载类的请求,它首先将请求交由父类加载器加载;若父类加载器加载失败,当前类加载器才会自己加载类。
作用
像java.lang.Object这些存放在rt.jar中的类,无论使用哪个类加载器加载,最终都会委派给最顶端的启动类加载器加载,从而使得不同加载器加载的Object类都是同一个。
为了避免重复加载,由下到上逐级委托,由上到下逐级查找
首先不会自己去尝试加载类,而是把这个请求委派给父加载器去完成,每一次层级的加载器都是如此,因此所有的类加载器请求都会传给上层的启动类加载器。
只有当父加载器反馈自己无法完成该加载请求时,子加载器才会尝试自己去加载。
也称之为:败家子模型
双亲委派模型的代码在java.lang.ClassLoader类中的loadClass函数中实现:
package com.aicode.classloaderdemo.chapter01;
import java.net.URL;
import java.net.URLClassLoader;
public class LoadTest {
public static void main(String[] args) throws
Exception{
URL url = new URL("file:C:\\test\\");//jvm需要
加载的类存放的位置
//创建一个新的类加载器
URLClassLoader parentLoader = new
URLClassLoader(new URL[]{url});
while (true){
//创建一个新的类加载器
URLClassLoader classLoader = new
URLClassLoader(new URL[]{url},parentLoader);
// 1:这里并不会去执行静态成员的加载,只会做静态成
员的存储和划分
Class clz =
classLoader.loadClass("HelloService");
System.out.println("HelloService 使用的类加载
器是:" +clz.getClassLoader());
// 2:通过反射实例化对象,真正的执行在创建对象的时候
静态成员会执行和初始化
Object newIntance = clz.newInstance();
Object test =
clz.getMethod("test").invoke(newIntance);
Thread.sleep(3000);
//GC
classLoader = null;
newIntance = null;
}
}
}
小结
这个时候可以清晰看到,如果去修改了HelloService的信息,并不会重新加载信息。
其实这样设计的目的和含义就是:去收集父加载的核心信息,把核心的一些jar和类信息全部加载到jvm中。直接全部加载完毕为止以后,然后在查找对应的自己的加载器去执行你的程序。‘
目的是:类加载器之间不存在父类子类的关系,“双亲” 只是一种翻译,而用逻辑逻辑上的上下级关系,责任划分的关系。也是为了安全性去考虑。比如核心类库中的String.class ,如果去修改的话是有风险的。
如果我们不指定parentLoader 默认也会去加载AppClassLoader加载器。
03:java自动内存管理机制
大型企业JVM性能调优实战之java垃圾收集器
对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new的操作去写对应的内存管理操作,不容器出现内存泄漏和内存溢出问题,由虚拟机管理内存一切都看起来很美好。
大型企业JVM性能调优实战之java垃圾收集器
不过,也正是因为我们把内存控制的权利交给了虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎么样使用内存的,那么排查错误将会变得特别的困难。所以,接下来我们要一层一层接下内存管理机制的面试来了解它究竟是怎样实现的。
11:自动垃圾收集
自动垃圾收集:是查看堆内存,识别正在使用那些对象以及那些对象未被删除以及删除未使用对象的过程。
使用中的对象或引用的对象意味着:程序的某些部分仍然维护指向该对象的指针。
程序的任何部分都不再引用未使用的对象或未引用的对象,因此可以回收未引用对象使用的内存。
像C这样的编程语言中,分配和释放内存是一个手动的过程,在java中,解除分配内存的过程是由垃圾收集器自动处理。
12:如何确定内存需要被回收 -- 标记
该过程的第一步成为:标记。这是垃圾收集器识别哪些内存正在使用而哪些不在使用的地方。
比喻:就好比搞卫生一样,垃圾收集器就好比清洁阿姨,她在搞卫生的时候会去标记哪些地方比较脏就会去清理。垃圾收集器也是一样的原理。这种性能会较差而且会造成连续的内存空间浪费,所以在一些商业的虚拟机里已经不在使用了。
不同类型判断内存的方式:
1:对象回收 -- 引用计数器 。java默认情况下没有采用引起计数器的方式,因为会存在循环引入的问题。
2:对象回收 --- 可达性分析简单来说,将对象及其引用关系看做一个图,选定 活动的对象 作为GCRoots ;
然后跟踪引用链条,如果一个对象和GC Roots之间不可达,也就是不存在引用,那么即可认定为可回收对象。什么样子的对象可以作为GcRoot对象:
整个执行的过程如下:
GC Root的对象
虚拟机栈中正在引用的对象
本地方法栈中正在引用的对象
静态属性应用的对象
方法区常量引用的对象
你可以理解成为:一个方法执行的时候,会开启一个线程去执行该方法。这个时候就可以当做一个GC Root, 它会自动去收集该方法中所有的对象的引用关系,变成一个树结构,当执行完毕一会就自动删除GC Root.先所有的对象会自动全部释放。
13:垃圾收集器与内存分配策略
目标
掌握和了解GC的引用类型
概述
说起垃圾收集(Garbage Clollection , GC),大家肯定都不陌生,目前内存的动态分配与内存回收技术已经非常成熟,那么我们为什么还要去了解GC和内存分配呢?原因很简单:当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时。我们就需要对这些自动化的技术实施必要的监控和条件。
在我们的java运行时内存当中,程序计数器、Java虚拟机栈、本地方法栈 3个区域都是随线程生而生,随线程灭而灭,因此这个区域的内存分配和回收都具备了确定性,所以在这几个区域不需要太多的考虑垃圾回收问题,因为方法结束了,内存自然就回收了。但Java堆不一样,它是所有线程共享的区域,我们只有在程序处于运行期间时才能知道会创建哪些对象,这个区域的内存分配和内存回收都是动态的,所以垃圾收集器主要关注的就是这部分的内存。
关于回收的知识点,我们会从以下几方面去讲解:
1. 什么样的对象需要回收
2. 垃圾收集的算法(如何回收)
3. 垃圾收集器(谁来回收)
4. 内存分配与回收策略(回收细节)
对象已死吗
在Java堆中存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还存活着,哪些已经死去(即不可能在被任何途径使用的对象)
如何判断对象是否死亡,主要有两种算法:引用计数法和可达性分析算法主流的商业虚拟机基本使用的是 可达性分析算法
分析
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析判断对象的引用链是否可达,判断对象的存货都与引用有关,在JDK1.2之后,java对引用进行了细分,用于描述一些特殊的对象,如:当内存空间还足够时,就保留在内存之中,如果内存空间在进行垃圾收集后还是很紧张则抛弃这些对象,所以java又对引用进行了一系列的划分:强引用、软引用、弱引用、虚引用
引用类型
强引用(StrongRefercence):最常见的普通对象引用,只要还有强引用指向一个对象,就不会回收。就比如你 new了一个对象。
Java中默认声明的就是强引用,比如:
Object obj = new Object(); //只要obj还指向Object对象,
Object对象就不会被回收
obj = null; //手动置null
只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不
足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中
断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一
来,JVM就可以适时的回收对象了
软引用(SoftRefercence) JVM认为内存不足时,才会去试图回收软引用执行的对象。(缓存常见)软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
public class TestOOM {
private static List<Object> list = new
ArrayList<>();
public static void main(String[] args) {
testSoftReference();
}
private static void testSoftReference() {
for (int i = 0; i < 10; i++) {
byte[] buff = new byte[1024 * 1024];
SoftReference<byte[]> sr = new
SoftReference<>(buff);
list.add(sr);
}
System.gc(); //主动通知垃圾回收
for(int i=0; i < list.size(); i++){
Object obj = ((SoftReference)
list.get(i)).get();
System.out.println(obj);
}
}
}
弱引用(WeakRefercence)虽然是引用,但随时可能被回收掉。弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。我们以与软引用同样的方式来测试一下弱引用:
private static void testWeakReference() {
for (int i = 0; i < 10; i++) {
byte[] buff = new byte[1024 * 1024];
WeakReference<byte[]> sr = new
WeakReference<>(buff);
list.add(sr);
}
System.gc(); //主动通知垃圾回收
for(int i=0; i < list.size(); i++){
Object obj = ((WeakReference)
list.get(i)).get();
System.out.println(obj);
}
}
虚引用(PhantomRefercence)不能通过它方为对象,供了对象呗fifinalize以后,执行指定逻辑的机制(Cleaner)。虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。
可达性分析算法和级别
强可达 :一个对象可以有一个或多个线程可以不通过各种引用访问的情况。
软可达:就是当我妈只能通过软引用才能方为到对象的状态。
弱可达:只能通过弱引用访问时的状态,当弱引用被清除的时候,就复合销毁条件
幻象可达:不存在其他引用,并且fifinalize过了,只有幻想引用指向这个对象。
不可达:意味着对象可以被清除了。
基本思路:通过系列的称为 GC Roots 的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径成为引用连,
当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可达的,会被判断为可回收的对象。
如图: 对象5,6,7 虽然互相有关联,但是他们到GC Roots是不可达的,所以它们会被判定为可回收的对象。
14:垃圾收集算法
标记-清除算法
标记—清除算法是最基础的收集算法,过程分为标记和清除两个阶段,首先标记出需要回收的对象,之后由虚拟机统一回收已标记的对象。这种算法的主要不足有两个:
1、效率问题,标记和清除的效率都不高;
2、空间问题,对象被回收之后会产生大量不连续的内存碎片,当需要分配较大对象时,由于找不到合适的空闲内存而不得不再次触发垃圾回收动作。
用标记清除的问题就是:清除以后的内存空间是不连续的。如果要存储大新的对象的时候,找不到合适的内存空间,而再次启发垃圾回收扫描。
复制算法
为了解决效率问题,复制算法出现了。算法的基本思路是:
将内存划分为大小相等的两部分,每次只使用其中一半,当第一块内存用完了,就把存活的对象复制到另一块内存上,
然后清除剩余可回收的对象,这样就解决了内存碎片问题。我们只需要移动堆顶指针,按顺序分配内存即可,简单高效。但是算法的缺点也很
明显:
1、它浪费了一半的内存,这太要命了。
应用场景分析:
这种收集算法经常被采用到新生代,因为新生代中的对象 绝大部分都是 朝生夕死。
步骤和过程
HotSpot默认的空间比例是 8:1
所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性的复制到另外一块Survivor空间上,最后清理Eden和刚才用过的Survivor空间,,如图:
标记-整理算法
根据老年代的特点,有人提出了另一种改进后的“标记—清除”算法:标记—整理算法。
标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GCRoots,然后将存活的对象标记。
不难看出,标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价,可谓是一举两得。
分代收集算法
根据对象的存活周期,将内存划分为几个区域,不同区域采用合适的垃圾收集算法。
新对象会分配到Eden,如果超过-XX:+PretenureSizeThreshold:设置大对象直接进入老年代的阈值。
现代商业虚拟机垃圾收集大多采用分代收集算法。主要思路是根据对象存活生命周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,然后根据各个年代的特点采用最合适的收集算法。
新生代中,对象的存活率比较低,所以选用复制算法,
老年代中对象存活率高且没有额外空间对它进行分配担保,所以使用“标记-清除”或“标记-整理”算法进行回收。
Eden代表是:新生代
s0代表:新手代-s0
s1代表:新手代-s1
old Memory:老年代
s0:s1:Eden:它们之间的比例是:1:1:8
新生代:老年代的比例是:1:2
解说
初始状态
1:默认情况下,我们的创建的对象是在Eden区去存放。如果这个时候内存空间不足,jvm会开始进行标记哪些对象可以被回收,那么对象不被回收,就会把不需要被回收的对象从eden区移动到s0或者s1中,当然也可能是老年代中区。(分配担保:我们没办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够时,需要依赖其他内存(这里指老年代)进行分配担保)
采用的算法是:复制算法。并且累加1。
这个时候就会把不可以使用的对象进行回收即可。
2:随着程序的运行,又会去生成一些新的对象和新的垃圾。这个时候新生代-s0可能内存空间也会存在需要回收的垃圾,也会出现存满的情况,也会启动垃圾回收机制进行回收。
3:老年代的存活对象,并且做内存标记清理和标记整理 算法
如果一个对象一直存活,并且移动的累加次数大于某个阈值的时候,就会进入到老年代。因为在对象的移动过程中,新生代的s1也会存满,它接下来就会把存活的对象复制到老年代中去。
或者是新生代内存不够的时候也会直接复制到老年代去。
或者一个大对象创建的时候也会直接进入到老年代
15:垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图是HotSpot虚拟机所包含的所有垃圾收集器:
从JDK3(1.3)开始,HotSpot团队一直努力朝着高效收集、减少停顿(STW:
Stop The World)的方向努力,也贡献了从串行到CMS乃至最新的G1在内的一系列优秀的垃圾收集器。上图展示了JDK的垃圾回收大家庭,以及相互之间的组合关系。
STW:Stop The World 是指由于虚拟机在后台发起垃圾收集时,会暂停
所有其他的用户工作线程,造成用户应用暂时性停止。从JDK1.3开始,
HotSpot虚拟机开发团队一直为消除或者减少工作线程因为内存回收而导致
停顿而努力着。用户线程的停顿时间不短缩短,但仍无法完全消除。
下面就几种典型的组合应用进行简单的介绍:
串行收集器
串行收集器组合 Serial + Serial Old
串行收集器是最基本、发展时间最长、久经考验的垃圾收集器,也是client模式下的默认收集器配置。
串行收集器采用单线程stop-the-world的方式进行收集。当内存不足时,串行GC设置停顿标识,待所有线程都进入安全点(Safepoint)时,应用线程暂停,串行GC开始工作,采用单线程方式回收空间并整理内存。单线程也意味着复杂度更低、占用内存更少,但同时也意味着不能有效利用多核优势。事实上,串行收集器特别适合堆内存不高、单核甚至双核CPU的场合。
开启选项:-XX:+UseSerialGC
就好比以前的一个电动车一样,充电中就不能使用。充电完成才可以使用。现在的电脑都是属于多核的CPU。这个时候串行的收集就有点不适用了。
并行收集器
并行收集器组合 Parallel Scavenge + Parallel Old
并行收集器是以关注吞吐量为目标的垃圾收集器,也是server模式下的默认收集器配置,对吞吐量的关注主要体现在年轻代Parallel Scavenge收集器上。
并行收集器与串行收集器工作模式相似,都是stop-the-world方式,只暂
停时并行地进行垃圾收集。年轻代采用复制算法,老年代采用标记-整理,在回收的同时还会对内存进行压缩。关注吞吐量主要指年轻代的Parallel
Scavenge收集器,通过两个目标参数-XX:MaxGCPauseMills和-
XX:GCTimeRatio,调整新生代空间大小,来降低GC触发的频率。并行收集器适合对吞吐量要求远远高于延迟要求的场景,并且在满足最差延时的情况下,并行收集器将提供最佳的吞吐量。
开启选项:-XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活)
并发清除收集器
并发标记清除(CMS)是以关注延迟为目标、十分优秀的垃圾回收算法,开启后,年轻代使用STW式的并行收集,老年代回收采用CMS进行垃圾回收,对延迟的关注也主要体现在老年代CMS上。
年轻代ParNew与并行收集器类似,而老年代CMS每个收集周期都要经历:初始标记、并发标记、重新标记、并发清除。其中,初始标记以STW的方式标记所有的根对象;并发标记则同应用线程一起并行,标记出根对象的可达路径;在进行垃圾回收前,CMS再以一个STW进行重新标记,标记那些由mutator线程(指引起数据变化的线程,即应用线程)修改而可能错过的可达对象;最后得到的不可达对象将在并发清除阶段进行回收。值得注意的是,初始标记和重新标记都已优化为多线程执行。CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。
专为老年代设计的收集算法,基于一种:标记-清除(Mark-Sweep)算法,设计目标就是进来减少停顿的时间。采用的标记清除算法,存在着内存碎片化的问题,长时间运行等情况下发送full gc,导致恶劣的停顿。CMS会占用更多的CPU资源,并和用户线程争抢。为了减少停顿时间,这一点对于互联网web等对时间敏感的系统非常重要,一直到尽头,仍然有很多系统使用CMSGC。
并发标记清除收集器组合 ParNew + CMS + Serial Old
但是CMS并不完美,它有以下缺点:由于并发进行,CMS在收集与应用线程会同时会增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间;
标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认0,即每次都进行内存整理)来指定多少次CMS收集之后,进行一次压缩的Full GC。
开启选项:-XX:+UseConcMarkSweepGC
Garbage First (G1)
G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一。早在JDK7就已加入JVM的收集器大家庭中,成为HotSpot重点发展的垃圾回收技术。同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回器,也同样适合大尺寸堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS。G1最大的特点是引入分区的思路,弱化了分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷。
G1垃圾收集器也是以关注延迟为目标、服务器端应用的垃圾收集器,被
HotSpot团队寄予取代CMS的使命,也是一个非常具有调优潜力的垃圾收集
器。虽然G1也有类似CMS的收集动作:初始标记、并发标记、重新标记、清
除、转移回收,并且也以一个串行收集器做担保机制,但单纯地以类似前三种
的过程描述显得并不是很妥当。事实上,G1收集与以上三组收集器有很大不
同:
G1的设计原则是"首先收集尽可能多的垃圾(Garbage First)"。因此,G1
并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而
是在内部采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。
同时G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小,暂停
目标越短年轻代空间越小、总空间就越大;
G1采用内存分区(Region)的思路,将内存划分为一个个相等大小的内存分
区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。
由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案
(局部压缩);
G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的
区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有
逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切
换;
G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合
(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),
也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆
内存很大时,也可以限制收集范围,从而降低停顿。
开启选项:-XX:+UseG1GC
16:内存分配与回收策略和演示
1.对象是在Eden区进行分配,如果Eden区没有足够空间时触发一次Minor GC
JVM提供 -XX:+PrintGCDetails这个收集器日志参数
2.占用内存较大的对象,对于虚拟机内存分配是一个坏消息,虚拟机提供了一个 -XX:PretenureSizeThreshold让大于这个设置的对象直接存入老年代。通过代码查看GC日志
3.长期存入的对象会存入老年代。
虚拟机给每个对象定义了一个Age年龄计数器,对象在Eden中出生并经过第一次Minor GC后仍然存活,对象年龄+1,此后每熬过一次Minor GC则对象年龄+1,当年龄增加到一定程度默认15岁,就会晋升到老年代。
可通过参数设置晋升年龄 -XX:MaxTenuringThreshold
Minor GC 和 Full GC的区别
新生代GC(Minor GC):指发生在新生代的垃圾收集动作,Minor GC非常频繁,一般回收速度也很快
老年代GC(Full GC/Major GC):指发生在老年代的GC,出现Full GC一般会伴随一次 Minor GC,Full GC的速度要慢很多,一般要比MinorGC慢10倍
1.对象是在Eden区进行分配,如果Eden区没有足够空间时触发一次
Minor GC
-XX:+PrintGCDetails这个收集器日志参数
2.占用内存较大的对象,对于虚拟机内存分配是一个坏消息,虚拟机提供了
-XX:PretenureSizeThreshold
让大于这个设置的对象直接存入老年代。
1
2
3
4
5
6
通过代码查看GC日志
3.长期存入的对象会存入老年代。
虚拟机给每个对象定义了一个Age年龄计数器,对象在Eden中出生并经
GC后仍然存活,对象年龄+1,此后每熬过一次Minor GC
、
-XX:MaxTenuringThreshold
Minor GC 和 Full GC的区别
GC):指发生在新生代的垃圾收集动作,Minor GC非常
频繁,一般回收速度也很快
GC/Major GC):指发生在老年代的GC,出现Full GC
Minor GC,Full GC的速度要慢很多,一般要比Minor
GC慢10倍
通过代码查看GC日志
[GC (Allocation Failure) [PSYoungGen: 6489K-
>872K(9216K)] 6489K->4976K(19456K), 0.0030214 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 9216K, used 6313K
[0x00000000ff600000, 0x0000000100000000,
0x0000000100000000)
eden space 8192K, 66% used
[0x00000000ff600000,0x00000000ffb50678,0x00000000ffe00
000)
from space 1024K, 85% used
[0x00000000ffe00000,0x00000000ffeda020,0x00000000fff00
000)
to space 1024K, 0% used
[0x00000000fff00000,0x00000000fff00000,0x0000000100000
000)
ParOldGen total 10240K, used 4104K
[0x00000000fec00000, 0x00000000ff600000,
0x00000000ff600000)
object space 10240K, 40% used
[0x00000000fec00000,0x00000000ff002020,0x00000000ff600
000)
Metaspace used 3357K, capacity 4496K, committed
4864K, reserved 1056768K
class space used 367K, capacity 388K, committed
512K, reserved 1048576K
分析结论:
GC:
表明进行了一次垃圾回收,前面没有Full修饰,表明这是一次Minor GC
,注意它不表示只GC新生代,并且现有的不管是新生代还是老年代都会
STW。
Allocation Failure:
表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据
了。
PSYoungGen:
04:JVM性能监控与故障处理工具
表明本次GC发生在年轻代并且使用的是Parallel Scavenge垃圾收
集器。不同的垃圾收集器显示的新生代老年代名字都不相同。
6489K->4976K(19456K):单位是KB
三个参数分别为:GC前该内存区域(这里是年轻代)使用容量,GC后该内存
区域使用容量,该内存区域总容量。
0.0030214 secs:
该内存区域GC耗时,单位是秒
6489K->4976K(19456K):
三个参数分别为:堆区垃圾回收前的大小,堆区垃圾回收后的大小,堆区总
大小。
0.0025301 secs:
该内存区域GC耗时,单位是秒
[Times: user=0.04 sys=0.00, real=0.01 secs]: