目录
Java虚拟机的内存结构:
类加载器图:
双亲委托模式:
Java允许创建和JDK自带类库相同名称类
但是JVM不会加载我们自己定义的类,而是加载JDK提供的类
JVM的实现方式为:双亲委派机制
1) 如果应用类加载器加载一个类,不是马上加载
2)委托父加载器(扩展类加载器)加载
3)扩展类加载器加载类的时候,也不是马上加载
4) 委托父加载器(启动类加载器)加载
5)启动类加载器开始加载这个类,如果加载成功,那么直接使用。
6)如果加载失败,那么会返回到扩展类加载器,由扩展类加载器加载这个类
7) 扩展类加载器开始加载这个类,如果加载成功,那么直接使用。
8)如果加载失败,会抛出异常给子加载器(应用类加载器)
9)应用类加载器捕捉异常后开始加载这个类,如果加载成功,那么直接使用。
10)如果加载失败,会将异常抛出给JVM
JVM自身提供了3个类加载器,每一个类加载器会加载不同位置的类
1)启动类加载器:加载JDK核心类库,由C++语言实现,
加载位置: $JRE_HOME/lib
2)扩展类加载器:是java类,可以加载JDK扩展类库
加载位置: $JRE_HOME/lib/ext
3)应用类加载器:是java类,可以加载环境变量classpath中的类
加载位置: $classpath
JAVA_HOME, PATH, CLASSPATH
堆内存:
GC解析图:
GC算法
内存效率:复制算法>标记清除算法>标记整理算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。 内存整齐度:复制算法=标记整理算法>标记清除算法。
内存利用率:标记整理算法=标记清除算法>复制算法。
可以看出,效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存,而为了尽量兼顾上面所提到的三个指标,标记/整理算法相对来说更平滑一些,但效率上依然不尽如人意,它比复制算法多了一个标记的阶段,又比标记/清除多了一个整理内存的过程
年轻代(Young Gen) ----->复制算法的
年轻代特点是区域相对老年代较小,对象存活率低。这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对像大小有关,因而很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。
老年代(Tenure Gen) ----> 标记清除或者是标记清除与标记整理的混合实现
老年代的特点是区域较大,对象存活率高。这种情况,存在大量存活率高的对像,复制算法明显变得不合适。一般是由标记清除或者是标记清除与标记整理的混合实现。
Mark阶段(标记)的开销与存活对像的数量成正比,这点上说来,对于老年代,标记清除或者标记整理有一些不符,但可以通过多核/线程利用,对并发、并行的形式提标记效率。
Sweep阶段(清除)的开销与所管理区域的大小形正相关,但Sweep“就地处决”的特点,回收的过程没有对像的移动。使其相对其它有对像移动步骤的回收算法,仍然是效率最好的。但是需要解决内存碎片问题。
Compact阶段(整理)的开销与存活对像的数据成开比,如上一条所描述,对于大量对像的移动是很大开销的,做为老年代的第一选择并不合适。
以hotspot中的CMS回收器为例,CMS是基于Mark-Sweep实现的,对于对像的回收效率很高,而对于碎片问题,CMS采用基于Mark-Compact算法的Serial Old回收器做为补偿措施:当内存回收不佳(碎片导致的Concurrent Mode Failure时),将采用Serial Old执行Full GC以达到对老年代内存的整理。 从JDK1.9后不再推荐使用CMS垃圾回收器, 推荐采用 Garbage-First(G1)垃圾回收器, Garbage-First(G1)是专门针对多核处理器的,虽然是整理,但是非常是多核完成的,因此较快。
一个case(说明引用计数法存在的问题):
package hello_java;
public class Test4 {
public static void main(String[] args) {
test();
System.gc();// full gc
System.out.println( "main finish" );
}
public static void test() {
A a = new A();
B b = new B();
a.b = b;
b.a = a;
}
}
class B {
public A a= null;
@Override
protected void finalize() throws Throwable {
System.out.println( "B被回收了");
}
}
class A {
public B b = null;
@Override
protected void finalize() throws Throwable {
System.out.println( "A被回收了");
}
}
打印结果:
main finish
B被回收了
A被回收了
解释:
Test方法出栈,gc回收、
稍作修改:
package hello_java;
public class Test4 {
public static void main(String[] args) {
A a = test();
System.gc();// full gc
System.out.println( "main finish" );
}
public static A test() {
A a = new A();
B b = new B();
a.b = b;
b.a = a;
return a;
}
}
class B {
public A a= null;
@Override
protected void finalize() throws Throwable {
System.out.println( "B被回收了");
}
}
class A {
public B b = null;
@Override
protected void finalize() throws Throwable {
System.out.println( "A被回收了");
}
}
main finish
解释:
main方法中有指向对象A的,不会被回收。