首先有class文件=》由类加载器加载class文件到内存空间=》内存空间(方法去、堆、虚拟机栈、程序计数器、本地方法栈)=》垃圾回收负责对内存空间的清理工作=》
JVM有两种类加载器:启动类加载器(JVM实现的一部分)和用户自定义的类加载器(Java程序的一部分:ClassLoad的子类),有如下几种:
- BootStrap ClassLoad:JVM的跟ClassLoad,由C++语言实现;JVM启动时初始化它,并由他加载JAVA_HOME下的jre/lib/rt.jar包的所有class文件,改包具有Java规范定义的所有接口;
- Extension ClassLoad:JVM用它来加载其他扩展功能的jar包;
- System ClassLoad:JVM用它来加载启动参数中指定的ClassPath中的jar包及目录,Sun JDK中它对应的类名为:AppClassLoader;
- User-Defined ClassLoad:用户自定义类加载器。
在jdk1.6及以下版本中,方法区还存放了常量池(例如字符串常量池),在jdk1.7及以后的版本中常量池被移到堆区了
栈主要用来实现Java方法的调用和执行,每个方法在执行时都会创建一个栈帧用来存储方法的局部变量、操作栈、动态链接和方法出口等信息;方法调用时通过入栈和出栈操作进行栈空间的分配和释放
它可以被看作是当前线程执行的字节码的行号指示器,解释器的工作原理便是通过改变这个计数器来确定下一条需要被执行的字节码指令,程序的流程控制(循环、分支、异常处理)都是通过它来完成的
当线程调用Java方法时,JVM会创建栈帧压入虚拟机栈,但当调用的是本地方法时,虚拟机栈保持不变,而是简单的动态链接并直接调用指定的本地方法,如果本地方法接口使用的是C++连接模型,那么它的本地方法栈就是C++栈
根据对象的生命周期长短不同将其分为不同的类型(年轻代、老年代和永久代)并分别进行垃圾回收:分代垃圾回收
主要思路:把堆分为两个或多个子堆,每个子堆被视为一代,在运行中优先收集年幼对象,如果一个对象经过多次收集仍然存活那么就把它转移到高一级的堆里,以减少对其扫描的次数;目前最常用的JVM是SUN(现被Oracle收购)公司的HotSport所采用的算法是分代回收
新建的对象优先在Eden区分配内存,如果Eden已满则在创建对象时会因无法申请到空间而触发minorGC,minorGC主要用来对年轻代进行回收:把Eden区不能回收的对象放到空的Survivor区,另一个Survivor区里不能回收的也放到这里,这样就保证有一个Survivor区是空的;但是如果Survivor区也满了或者有些对象已经存在了很长时间,那么这些对象会被放到老年代中,老年代满触发fullGC。
fullGC是用来清理整个堆空间的,会造成很大的系统资源开销,应尽量避免,触发fullGC的情况:
String s1 = new String("abc"); // 等价于"abc"和new String()两个操作,先从常量池取"abc"再作为参数传入构造方法 String s2 = "abc";
只要使用new总会生成新的对象;String的实现采用了Flyweight的设计模式优先使用常量池中存在的对象,s2时"abc"在编译时产生并放到常量池中,s1时在运行时把"abc"放到堆中;
jdk1.8开始永久代被移除,类的元数据被放到了本地化的堆内存(native heap)中即MetaSpace元空间;
移除永久代的原因:
MetaSpace的内存分配:
MetaSpaceVM分配内存:类与类加载器有相同的生命周期,只有类加载器还存活,MetaSpace中的类信息就不能释放,MetaSpaceVM通过一个块分配器来管理内存的分配,块的大小取决于类加载器的类型。VM维护一个全局的块列表,类加载器按需取块,当类加载器生命周期结束时释放块,把申请的块归还到全局块列表,每个块被分为多个block,每个block存储一个元数据单元;
由于类的大小不固定,当类加载器需要一个块时,有可能空闲的块太小而不足以容纳当前类,就会出现内存碎片,目前MetaSpaceVM还没有解决这个碎片问题。
MetaSpace还增加了一些参数可配置