一、简介
JVM,java虚拟机,学习JVM的底层原理有助于我们更好的了解JAVA代码的运行。
二、正文
2.1、类加载机制
类加载过程分为
加载: 从磁盘上通过IO流读入字节码文件
验证: 校验字节码文件是否符合JVM规范
准备: 给静态变量分配内存,设置默认值
解析: 符号引用替换为直接引用
初始化: 类的静态变量初始化,执行静态代码块
使用:
卸载:
双亲委派机制
JAVA总共三种类加载器,引导类加载器(bootstrapLoader),扩展类加载器(extClassLoader),应用程序类加载器(appClassLoader)。
import sun.misc.Launcher;
import java.net.URL;
import java.net.URLClassLoader;
public class person {
public static void main(String[] args) {
ClassLoader appClassLoader = new person().getClass().getClassLoader();
ClassLoader extClassLoader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassLoader.getParent();
System.out.println("应用程序类加载器:"+appClassLoader);
System.out.println("扩展类加载器:"+extClassLoader);
System.out.println("引导类加载器:"+bootstrapLoader);
System.out.println("引导类加载器由C++实现,拿到为null");
System.out.println("-------------------------------------------------------");
System.out.println("引导类加载器加载以下文件:");
URL[] bootstrapUrLs = Launcher.getBootstrapClassPath().getURLs();
for (URL urL : bootstrapUrLs) {
System.out.println(urL);
}
System.out.println("扩展类加载器加载以下文件:");
URLClassLoader extUrlLoader = (URLClassLoader)extClassLoader;
URL[] extUrls = extUrlLoader.getURLs();
for (URL url : extUrls) {
System.out.println(url);
}
System.out.println("应用程序类加载器加载以下文件:");
URLClassLoader appUrlLoader = (URLClassLoader)appClassLoader;
URL[] appUrLs = appUrlLoader.getURLs();
for (URL url : appUrLs) {
System.out.println(url);
}
}
}
运行如上代码
"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" "-javaagent:D:\idea\IntelliJ IDEA 2022.2\lib\idea_rt.jar=64937:D:\idea\IntelliJ IDEA 2022.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\Users\Administrator.DESKTOP-EB82FD5\IdeaProjects\SpringTest\target\test-classes;C:\Users\Administrator.DESKTOP-EB82FD5\IdeaProjects\SpringTest\target\classes" person
应用程序类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
扩展类加载器:sun.misc.Launcher$ExtClassLoader@4554617c
引导类加载器:null
引导类加载器由C++实现,拿到为null
-------------------------------------------------------
引导类加载器加载以下文件:
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/classes
扩展类加载器加载以下文件:
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/cldrdata.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/dnsns.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/jaccess.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/jfxrt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/localedata.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/nashorn.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/sunec.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/sunjce_provider.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/sunmscapi.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/sunpkcs11.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/zipfs.jar
应用程序类加载器加载以下文件:
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/deploy.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/cldrdata.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/dnsns.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/jaccess.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/jfxrt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/localedata.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/nashorn.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/sunec.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/sunjce_provider.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/sunmscapi.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/sunpkcs11.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/ext/zipfs.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/javaws.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jfxswt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/management-agent.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/plugin.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/rt.jar
file:/C:/Users/Administrator.DESKTOP-EB82FD5/IdeaProjects/SpringTest/target/test-classes/
file:/C:/Users/Administrator.DESKTOP-EB82FD5/IdeaProjects/SpringTest/target/classes/
file:/D:/idea/IntelliJ%20IDEA%202022.2/lib/idea_rt.jar
Process finished with exit code 0
可以很明显的看出各个类加载器负责加载的类
应用程序类加载器(appClassLoader):加载classPath路径下的类包
扩展类加载器(extClassLoader):加载的是jre的lib目录下ext扩展目录中的JAR
类包
引导类加载器(bootstrapLoader):加载的是jre的lib目录下核心类库
双亲委派机制即当加载类时,不断向上找父加载器加载,父加载器无法加载就会由自己加载。
双亲委派机制的优点
1.避免重复加载:父加载加载了,子加载器就不需要再加载了。
2.沙箱安全:如果你自己定义了一个和JAVA核心类库重名的类,是不会加载金JVM虚拟机的,因为JAVA核心类库是由父加载器加载,只会加载java自己的核心类库。
下面我们来看下类加载器创建的源码
//首先来看类加载器的构造
public Launcher() {
ExtClassLoader var1;
try {
//创建 ExtClassLoader, 来看看 getExtClassLoader方法
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//创建 AppClassLaoder,来看看getAppClassLoader方法,重点关注其中的var1参数,var1为extClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
截取的一部分ExtClassLoader代码
//获取extClassLoader
public static ExtClassLoader getExtClassLoader() throws IOException {
if (instance == null) {
Class var0 = ExtClassLoader.class;
synchronized(ExtClassLoader.class) {
if (instance == null) {
//调用创建extClassLoader方法
instance = createExtClassLoader();
}
}
}
return instance;
}
private static ExtClassLoader createExtClassLoader() throws IOException {
try {
return (ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<ExtClassLoader>() {
public ExtClassLoader run() throws IOException {
File[] var1 = Launcher.ExtClassLoader.getExtDirs();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
MetaIndex.registerDirectory(var1[var3]);
}
//真正创建extClasLoader
return new ExtClassLoader(var1);
}
});
} catch (PrivilegedActionException var1) {
throw (IOException)var1.getException();
}
}
//extClassLoader的构造方法
public ExtClassLoader(File[] var1) throws IOException {
//这边是关键,调用父类的构造方法,第二个参数传的null,此参数即为父类构造器。
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
截取的一部分appClassLoader代码
//获取appClassLoader方法
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
//调用AppClassLoader的创建方法,关注参数var0,var0为ExtClassLoader
return new AppClassLoader(var1x, var0);
}
});
}
//AppClassLoader的构造方法,参数var2为ExtClassLoader
AppClassLoader(URL[] var1, ClassLoader var2) {
//与ExtClassLoader一样,调用父类构造方法,参数var2为父类构造器的参数,为ExtClassLoader
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
关于加载器的创建这部分,个人觉得在idea中点点看,或者debug下也就比较清晰了。
下面来看看appClassLoader和extClassLoader类中关于加载class的源码
//ClassLoader类
//loadClass方法,很显然是加载class的方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//由上面的类加载器的创建我们可以知道,parent为父加载器,我们可以单独看看两个类加载器中的loadClass方法,其实就是不断的由父加载器去进行加载
c = parent.loadClass(name, false);
} else {
//由于ExtClassLoader的父加载器BootstrapLoader拿不到,所有有了这个方法
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果所有父加载器都无法加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//自己加载,此方法在URLClassLoader中,而ExtClassLoader和AppClassLoader均继承了URLClassLoader,大家可以去看看这个方法,大致上就是读取Class的过程
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
//AppClassLoader方法,实际上重写了ClassLaoder中的loadClass方法,不过实质也只是在一部分处理后再调用 ClassLoader类中的loadClass方法。
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
//调用 ClassLoader中的loadClass方法
return super.loadClass(var1, var2);
}
}
当然,JAVA是允许我们自己去创建类加载器,重写其中的loadClass方法即可。
2.2 JVM内存模型
public class Test {
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = a + b;
}
}
用一个简单的代码来看看,对class文件反编译
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
// 前面的数字即为程序运行的顺序(程序计数器)
0: iconst_1 //将int类型常量1压入操作数栈
1: istore_1 //将int类型值存入局部变量1(局部变量a)
2: iconst_2 //将int类型常量2压入操作数栈
3: istore_2 //将int类型值存入局部变量2(局部变量a)
4: iload_1 //从局部变量1中装载int类型值
5: iload_2 //从局部变量2中装载int类型值
6: iadd // 执行int类型的加法
7: istore_3 //将int类型值存入局部变量3(局部变量c)
8: return //返回
}
2.4 对象创建过程
1.类加载检查:虚拟机执行new命令时,首先会去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
2.分配内存:检查通过,为新生对象分配内存。
分配内存方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
内存分配的两种方式:
选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是”标记-清除”,还是”标记-整理”(也称作”标记-压缩”),复制算法内存也是规整的。
内存分配还存在并发问题,JVM采用两种方式来保证线程安全:
CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
TLAB: 为每一个线程预先在 Java堆中分配一块内存,称为本地线程分配缓冲。JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配新的TLAB。
3.初始化零值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值。
4.设置对象头:虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。
5.执行init:初始化。
2.5 垃圾收集
1.优先在Eden区分配
JVM年轻代堆内存可以划分为一块Eden区和两块Survivor区. 在大多数情况下, 对象在新生代Eden区中分配, 当Eden区没有足够空间分配时, JVM发起一次Minor GC, 将Eden区和其中一块Survivor区内尚存活的对象放入另一块Survivor区域, 如果在Minor GC期间发现新生代存活对象无法放入空闲的Survivor区, 则会通过空间分配担保机制使对象提前进入老年代(空间分配担保见下).
2.大对象直接进入老年代
Serial和ParNew两款收集器提供了-XX:PretenureSizeThreshold的参数, 令大于该值的大对象直接在老年代分配, 这样做的目的是避免在Eden区和Survivor区之间产生大量的内存复制(大对象一般指 需要大量连续内存的Java对象, 如很长的字符串和数组), 因此大对象容易导致还有不少空闲内存就提前触发GC以获取足够的连续空间.
Minor GC 只回收新生代
Major GC 只回收老年代
Full GC 回收整个堆
空间分配担保:在Minor GC前, VM会先首先检查老年代连续空间是否大于新生代对象总大小或历次晋升的平均大小, 如果条件成立, 则进行Minor GC, 否则进行Full GC(让老年代腾出更多空间)。
然而取历次晋升的对象的平均大小也是有一定风险的, 如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然可能导致担保失败(Handle Promotion Failure, 老年代也无法存放这些对象了), 此时就只好在失败后重新发起一次Full GC(让老年代腾出更多空间)。
对象晋升
1.年龄阈值
VM为每个对象定义了一个对象年龄(Age)计数器, 对象在Eden出生如果经第一次Minor GC后仍然存活, 且能被Survivor容纳的话, 将被移动到Survivor空间中, 并将年龄设为1. 以后对象在Survivor区中每熬过一次Minor GC年龄就+1. 当增加到一定程度(-XX:MaxTenuringThreshold, 默认15), 将会晋升到老年代。
2.提前晋升: 动态年龄判定
然而VM并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代: 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半, 年龄大于或等于该年龄的对象就可以直接进入老年代, 而无须等到晋升年龄。
对象生死判定
1.引用计数器
该方法不能解决循环引用,一般不用。
2.可达性分析算法
从GC Root开始递归查询并标记,未被标记的即为可回收对象。
在Java, 可作为GC Roots的对象包括:
1.方法区: 类静态属性引用的对象;
2.方法区: 常量引用的对象;
3.虚拟机栈(本地变量表)中引用的对象.
4.本地方法栈JNI(Native方法)中引用的对象。
GC原理- 垃圾收集算法
1.分代收集
当前主流VM垃圾收集都采用”分代收集”(Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为几块, 如JVM中的 新生代、老年代、永久代. 这样就可以根据各年代特点分别采用最适当的GC算法:
在新生代: 每次垃圾收集都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量存活对象的复制成本就可以完成收集.
在老年代: 因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存.
2.分区收集
上面介绍的分代收集算法是将对象的生命周期按长短划分为两个部分, 而分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的好处是可以控制一次回收多少个小区间.
永久代-方法区回收
在方法区进行垃圾回收一般”性价比”较低, 因为在方法区主要回收两部分内容: 废弃常量和无用的类. 回收废弃常量与回收其他年代中的对象类似, 但要判断一个类是否无用则条件相当苛刻:
1.该类所有的实例都已经被回收, Java堆中不存在该类的任何实例;
2.该类对应的Class对象没有在任何地方被引用(也就是在任何地方都无法通过反射访问该类的方法);
3.加载该类的ClassLoader已经被回收。
但即使满足以上条件也未必一定会回收
几种垃圾收集器
1.serial。单线程,简单高效。复制算法
2.PerNew。serial 的多线程版本,并行。
3.parallel Scavenge。 与 PerNew 类似,复制算法、多线程、并行。但侧重吞吐量,拥有自适应调节的能力。适合用在后台不需要太多用户交互的地方。
吞吐量 = 用户代码执行时间 / (用户代码执行时间 + 垃圾回收时间)
自适应调节:虚拟机根据但前系统的运行情况,自动调节虚拟机各参数以确保最大吞吐量。
4.serial old。serial 的永久代版本。采用标记整理算法。
5.parallel old。parallel Scavenge 的老年代版本,采用标记整理算法。与 parallel scavenge 搭配可以用在注重吞吐量及 CPU 资源敏感的地方。
6.CMS(concurrent mark sweep)。并发低停顿,使用标记清理算法。非常优秀的一款收集器,步骤:初始标记、并发标记、重新标记、并发清除。
两种收集算法
标记清除算法
该算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象(可达性分析), 在标记完成后统一清理掉所有被标记的对象。
标记整理算法
标记清除算法会产生内存碎片问题, 而复制算法需要有额外的内存担保空间, 于是针对老年代的特点, 又有了标记整理算法. 标记整理算法的标记过程与标记清除算法相同, 但后续步骤不再对可回收对象直接清理, 而是让所有存活的对象都向一端移动,然后清理掉端边界以外的内存.
2.6 JVM调优
JVM调优也没有太多经验,这部分还是需要实践为主。
这篇文章简单介绍了JVM调优的基础知识。JVM调优参数简介、调优目标及调优经验
本文用作学习记录分享,如有违规请联系作者删除!