jvm(二)

微信图片_20221110153753

JVM

JVM四种引用

强引用

对象引用直接new

1
Object obj = new Object();

强引用对象失效

  • 生命周期结束(作用域失效)
1
2
3
4
public void method(){

Object object = new Object() ;
}

/当方法执行完毕后,强引用指向的 引用而对象new Object()就会等待被GC回收

  • 引用被置为null
1
object = null

没有任何引用指向new Object() 因此,new Object() 就会等待被GC回收

除了以上两个情况以外,其他任何时候GC都不会回收强引用对象

软引用

如果内存充足,GC不会随便的回收软引用对象;如果JVM内存不足,则GC就会主动的回收软引用对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    public static void main(String[] args) throws Exception {

// 实例化软引用类
SoftReference<Object> softReference = new SoftReference<>(new Object());

// 声明一个List集合,模仿内存溢出
List<byte[]> list = new ArrayList<>();

new Thread( () ->{
while (true){
try {
// 这块让他睡一会是为了给这个线程一点时间,让他能刷新数据到主内存中进行线程之间的数据同步
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 如果软引用为空,则回收
if (softReference.get() == null) {
System.out.println("softReference 软引用对象已经被回收了");
System.exit(0);
}
}
},"线程1").start();


// 模仿内存溢出
while (true){

// 如果内存没有溢出,则
if (softReference.get() != null){

list.add(new byte[1024 * 1024]);
}
}
}

}

来自java.lang.ref.SoftReference

get() 返回 所引用的对象

弱引用

只要GC执行,就会将弱引用对象进行回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class WeakReferenceTest {

/**
* 测试弱引用
* @param args
*/
public static void main(String[] args) {

// 实例化weakReference 弱引用对象
WeakReference<Object> weakReference = new WeakReference<>(new Object());

// 判断是否被回收
System.out.println(weakReference.get() == null ? "已被回收" : "没被回收");

// 让虚拟机进行一次回收 这个方法概率实践 有可能生效 有可能失效
System.gc();

System.out.println(weakReference.get() == null ? "已被回收" : "没被回收");
}
}

来自java.lang.ref.WeakReference

虚引用

是否使用虚引用,和引用对象本身没有任何关系,无法通过虚引用来获取对象本身

来自 java.lang.ref.PhantomReference

虚引用不会单独使用,一般会和 引用队列(java.lang.ref.ReferenceQueue)一起使用

流程

当gc回收一个对象,如果gc发现 此对象还有一个虚引用,就会将虚引用放入到 引用队列中,之后(当虚引用出队之后)再去回收该对象。因此,我们可以使用 虚引用+引用对象 实现:在对象被gc之前,进行一些额外的其他操作

GC –>如果有虚引用–>虚引用入队–>虚引用出队–> 回收对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class PhantomReferenceTest {

public static void main(String[] args) throws Exception{
// 实例化要引用类
W w = new W();
// 实例化引用队列
ReferenceQueue referenceQueue = new ReferenceQueue();
// 实例化虚引用,需要一个引用队列
PhantomReference<W> phantomReference = new PhantomReference<>(w,referenceQueue);

// 将虚引用对象设置为空
w = null;
// 执行垃圾回收
System.gc();
// 睡一会,缓存
Thread.sleep(10);
// GC-> 虚引用->入队->出队->
System.out.println(referenceQueue.poll());

}
}

class W{

}
  • 特殊情况

虚引用对象重写了finalize(),那么JVM会延迟 虚引用的入队时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class PhantomAndFinalize {

public static void main(String[] args) throws Exception{
// 实例化要引用类
E e = new E();
// 实例化引用队列
ReferenceQueue referenceQueue = new ReferenceQueue();
// 实例化虚引用,需要一个引用队列
PhantomReference<W> phantomReference = new PhantomReference<>(e,referenceQueue);

// 将虚引用对象设置为空
e = null;
// 执行垃圾回收
System.gc();
// 睡一会,缓存
Thread.sleep(10);
// GC-> 虚引用->入队->出队->
System.out.println(referenceQueue.poll());

}

}

class E{
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("被回收之前的操作");
}
}

软引用实现缓存的淘汰策略

java中可以用引用实现 淘汰策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SoftCache {

Map<String, SoftReference<ObjectTest>> map = new HashMap<>();

// 逻辑

void setCaches(String id,ObjectTest obj){
map.put( id,new SoftReference<ObjectTest>(obj));
}

ObjectTest getCache(String id){
SoftReference<ObjectTest> softRef = map.get(id) ;
return softRef == null ? null : softRef.get() ;
}
}
class ObjectTest{
}

双亲委派

当一个加载器要加载类的时候,自己先不加载,而是逐层向上交由双亲去加载;当双亲中的某一个加载器 加载成功后,再向下返回成功。如果所有的双亲和自己都无法加载,则报异常

JVM双亲委派

JVM自带的加载器

在JVM的内部所包含,C++

  • 根加载器,Bootstrap

加载 jre\lib\rt.jar 包含平时编写代码时 大部分jdk api,指定加载某一个jar -Xbootclasspath=a.jar

  • 扩展类加载器,Extension

..\jre\lib\ext\\\*.jar指定加载某一个jar -Djava.ext.dirs= ....

  • AppClassLoader/SystemClassLoader 系统加载器/应用加载器

加载classpath,指定加载 -Djava.class.path= 类/jar

用户自定义的加载器

独立于JVM之外的加载器,Java

都是抽象类java.lang.ClassLoader的子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyLoader {

public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("java.lang.Object");

ClassLoader classLoader = clazz.getClassLoader();

System.out.println(classLoader);

Class<?> clazz2 = Class.forName("com.dream.xiaobo.loader.Loader1");

ClassLoader classLoader2 = clazz2.getClassLoader();

System.out.println(classLoader2);
}
}
class Loader1{

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class MyLoader2 {

public static void main(String[] args) throws Exception {

Class<?> clazz = Class.forName("java.lang.String");

ClassLoader classLoader = clazz.getClass().getClassLoader();

System.out.println("classLoader:" + classLoader);
System.out.println("====");
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

System.out.println("systemClassLoader:" + systemClassLoader);
System.out.println("====");
ClassLoader parent = systemClassLoader.getParent();

System.out.println("parent:" + parent);
System.out.println("====");
ClassLoader parent2 = parent.getParent();

System.out.println("parent2:" + parent2);
System.out.println("====");
// ClassLoader parent3 = parent2.getParent();
//
// System.out.println("parent3:" + parent3);


Enumeration<URL> resources = systemClassLoader.getResources("./com/dream/xiaobo/loader/MyLoader2.class");

if (resources.hasMoreElements()) {
System.out.println("url:" + resources.nextElement());
}
}
}

如果类是rt.jar中的,则该类是被bootstrap(根加载器)加载;如果是classpath中的类(自己编写的类),则该类是被AppClassLoader加载

定义类加载:最终实际加载类的加载器

初始化类加载类:直接面对加载任务的类

自定义类加载器详解

二进制名binary names

1
2
3
4
"java.lang.String"
"javax.swing.JSpinner$DefaultEditor"
"java.security.KeyStore$Builder$FileBuilder$1"
"java.net.URLClassLoader$3$1"

$代表内部类:

$数字:第几个匿名内部类

数组的加载器类型和数组元素的加载器类型是相同

原声类型的数组是没有类加载器的

如果加载的结果是null 可能是此类没有加载器(int[]) , 也可能是 加载类型是“根加载器”

xxx.class文件可能是在本地存在,也可能是来自于网络 或者在运行时动态产生(jsp)

演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public class MyClassLoaderImpl  extends ClassLoader{
private String path ; //null
//优先使用的类加载器是:getSystemClassLoader()
public MyClassLoaderImpl(){
super();
}

public MyClassLoaderImpl(ClassLoader parent){//扩展类加载器
super(parent);
}
@Override
//com.yq.xx.class
public Class findClass(String name) {
System.out.println("加载findClass");
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}

//“com/yq/xxx.class” -> byte[]
private byte[] loadClassData(String name) {
System.out.println("加载loadClassData");
if(path != null){//name: com.yanqun.parents.MyDefineCL
name = path+ name.substring( name.lastIndexOf(".")+1 )+".class" ;
}else{
//classpath ->APPClassLoader
name = dotToSplit("out.production.volatile."+name)+".class" ;
}




byte[] result = null ;
FileInputStream inputStream = null ;
ByteArrayOutputStream output = null ;
try {
inputStream = new FileInputStream( new File( name) );
//inputStream -> byte[]
output = new ByteArrayOutputStream();

byte[] buf = new byte[2];
int len = -1;
while ((len = inputStream.read(buf)) != -1) {
output.write(buf, 0, len);
}
result = output.toByteArray();
}catch (Exception e){
e.printStackTrace(); ;
}finally {
try {
if(inputStream != null ){
inputStream.close();
}
if(output != null ){
output.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
return result ;
}

public static void main(String[] args) throws Exception {
//自定义加载器的对象
MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//默认在双亲委派时,会根据正规流程:系统——》扩展->根

myClassLoader.path = "d:/" ;

//MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//直接指定某个 具体的的委派
Class<?> aClass = myClassLoader.loadClass("com.dream.xiaobo.loader.MyDefineCL");
System.out.println(aClass.getClassLoader());
// MyDefineCL myDefineCL = (MyDefineCL)( aClass.newInstance()) ;
// myDefineCL.say();
}

public static String dotToSplit(String clssName){ return clssName.replace(".","/") ; }

}
class MyDefineCL{
public void say(){
System.out.println("Hello");
}
}
  • 总结

继承ClassLoader,重写的 findClass()

findClass(String name){...} :是全类名的形式 a.b.c.class,并且开头 是: 包名.类名.class

loadClassData(String name){...} :name所代表的文件内容->byte[] ,是文件形式的字符串a/b/c.class,并且开头out.production..

.class文件从classpath中删除,之后才可能用到 自定义类加载器;否在classpath中的.class会被 APPClassLoader加载

loadClass() ->findClass()->loadClassData(): 代码执行流程

类加载器只会把同一个类加载一次,同一个class文件加载后的位置

先委托APPClassLoader加载,APPClassLoader会在classpath中寻找是否存在,如果存在则直接加载;如果不存在,才有可能交给自定义加载器加载

在双亲委派体系中,下层的加载器是通过parent引用上层的加载器。即在双亲委派体系中,各个加载器之间不是继承关系

双亲委派机制优势

可以防止用户自定义的类和rt.jar中的类重名而造成的混乱

双亲委派优势

继承关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class B{
public B(){
System.out.println("B被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
}
}

public class A extends B
{
public A(){
super();
System.out.println("A被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
}
}
//AppClassLoader.class : TestMyClassLoader2
//自定义加载器: A.class/B.class
public class TestMyClassLoader2 {
public static void main(String[] args) throws Exception{
MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl() ;
//自定义加载路径
myClassLoader.path = "d:/" ;
Class<?> aClass = myClassLoader.loadClass("com.dream.xiaobo.extendss.A");
Object aObject = aClass.newInstance();//newInstance()会调用 该类的构造方法(new 构造方法())
System.out.println(aObject);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
存在继承关系

A.class: classpath
B.class: classpath
原因
同一个AppClassLoader 会同时加载A.class和B.class

--
A.class: d:\
B.class: classpath
原因
A.class:自定义加载器加载
B.class:被AppClassLoader加载
因此,加载A.class和B.class的不是同一个加载器

---
A.class: classpath
B.class: d:\
NoClassDefFoundError
原因:A.class: 被AppClassLoader加载 B.class: 自定义加载器加载
因此,加载A.class和B.class的不是同一个加载器

--
A.class d:\
B.class d:\
TestMyClassLoader2 can not access a member of class A with modifiers "public"
A.class/B.class: 自定义加载器加载
原因是 main()方法所在类在 工程中(APPClassLoader),而A和B不在工程中(自定义加载器)。


造成这些异常的核心原因: 命名空间(不是由同一个类加载器所加载)

非继承关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Y {
public Y(){
System.out.println("Y被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
}
}
public class X {
public X(){
new Y() ;//加载Y(系统加载器)
System.out.println("X被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
}
}

//AppClassLoader.class : TestMyClassLoader2
//自定义加载器: A.class/B.class
public class TestMyClassLoader3 {
public static void main(String[] args) throws Exception{
MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl() ;
//自定义加载路径
myClassLoader.path = "d:/" ;
//程序第一次加载时(X),使用的是 自定义加载器
Class<?> aClass = myClassLoader.loadClass("com.dream.xiaobo.extendss.X");



Object aObject = aClass.newInstance();//newInstance()会调用 该类的构造方法(new 构造方法())
System.out.println(aObject);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
没有继承关系

X.class: D: 自定义加载器
Y.class: classpath 系统加载器

Y被加载了,加载器是: AppClassLoader
X被加载了,加载器是: MyClassLoaderImpl


因为下层可以调用上层

---

X.class: classpath 系统加载器
Y.class: D: 自定义加载器

java.lang.NoClassDefFoundError

如果存在继承关系: 继承的双方(父类、子类)都必须是同一个加载器,否则出错

如果不存在继承关系: 子类加载器可以访问父类加载器加载的类(自定义加载器,可以访问到 系统加载器加载的Y类);反之不行(父类加载器 不能访问子类加载器)

OSGI

  • 网状结构的加载结构

  • 屏蔽掉硬件的异构性

可以将项目部署在网络上,可以在A节点上 远程操作B节点。在操作上,可以对硬件无感。也可以在A节点上 对B节点上的项目进行运维、部署,并且立项情况下 在维护的期间,不需要暂时、重启。

类的卸载

  • 系统自带

系统加载器、扩展加载器、根加载器,这些加载器加载的类是不会被卸载

  • 用户自定义的加载器

会被GC卸载GC

JVM监测工具

jps: 查看Java进程 (java命令)

jstat:只能查看当前时刻的内存情况;可以查看到 新生代、老年代中的内存使用情况

jmap:查看堆内存的占用情况;也可以执行dump操作

jconsole:图形的监控界面

如果通过jconsole中的”执行gc”按钮发现 GC回收的内存太少,就说明当前进程是存在问题的(至少是可以被优化的)

jvisualvm: 监视 - 堆Dump -查找最大对象,从中可以发现 当前进程中是哪个对象 占据了最大的内存,从而对这个对象进行分析。

通过VM参数实现: 当内存溢出时,自动将溢出时刻的内存dump下来。

1
2
3
-Xmx100m
-Xms100m
-XX:+HeapDumpOnOutOfMemoryError

GC调优

调优实际是是一种取舍,以xx换xx的策略。因此在调优之前,必须明确方向:低延迟?高吞吐量?

在已知条件相同的前提下,牺牲低延迟 来换取 高吞吐量,或者反之。

GC的发展

JVM默认的GC

CMS GC(在jdk9以后被逐步废弃) –> G1 GC(jdk9) –> Z GC(jdk11)

  • Serial GC:

串行GC,是一种在单核环境下的串行回收器。当GC回收的时刻,其他线程必须等待。一般不会使用。

  • Parallel GC:

在Serial 的基础上,使用了多线程技术,提高吞吐量。

  • CMS GC

CMS使用了多线程技术,使用标记清除算法,可以极大提升效率,在低延迟上有很大的提升,CMS GC繁琐,参数太多

  • G1 GC

jdk9开始使用的默认GC。将堆内存划分为很多大小相等region,并会对这些区域的使用状态进行标记。以便GC在回收时,能够快速的定位出哪些region是空闲的,哪些是有垃圾对象,从而提升GC的效率。G1使用的算法是标记整理算法

  • Z GC

jdk11开始提供全新的GC。回收TB级别的垃圾 在毫秒范围。

根据生命周期划分

  • Minor GC

回收新生代中的对象

  • Full GC

回收整个堆空间(新生代、老年代)

简单的一个小问题

  • 如果通过监测工具发现Minor GC和Full GC都在频繁的回收,如何优化?

思路:Minor GC –> 短生命周期的对象太多了 –> 造成逃逸到老年代中的对象越多 –> 新生代多+老年代多 –> Full GC

解决办法:Minor GC 可以尝试调大 新生代的最大空间,再调大 新生代晋升到老年代的阈值,从而降低 短生命周期的对象 从新生代转移到老年代的概率

正确的开始 微小的长进 然后持续 嘿 我是小博 带你一起看我目之所及的世界……

-------------本文结束 感谢您的阅读-------------

本文标题:jvm(二)

文章作者:小博

发布时间:2022年11月13日 - 19:58

最后更新:2022年11月13日 - 20:06

原始链接:https://codexiaobo.github.io/posts/2855935176/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。