JVM内存结构图

- 其中类加载器主要负责加载class文件到jvm的方法区,类加载器分为以下四类
- bootstrapClassloader,主要加载jvm运行时的一些类库,位于jre下载rt.jar
- extClassloader,加载除rt.jar之外的jvm运行时的依赖类库
- appClassloader,加载项目中所引入的第三方jar包及项目本身的class文件
- 自定义类加载器,加载用户指定的class文件,通过集成ClassLoader抽象类实现
- jvm使用类加载器的原则是双亲委派原则,目的是为了正确有序的加载class文件,保证程序的稳定性和安全性。当一个类需要被加载时,首先会去该类所对应的类加载器的缓存中找,如果找到则返回该类所对应的Class对象,如果没有找到就把该次加载任务委托给父类加载器,同理,父类加载器找自己的缓存,如果找到,返回,如果没有找到就交给自己的父类加载器,最终会交给根加载器加载,根加载器尝试加载,如果加载失败,此时再一层一层的返回给子类加载器加载。
- 本地接口,本地方法栈,当线程调用一个包含本地方法的Java方法时,虚拟机会创建一个新的栈帧并压入Java栈。这个栈帧里面就包含指向本地方法的动态链接,当执行到本地方法时,不会有新的栈帧压入java栈,而是通过动态链接直接找到并执行对应的本地方法,而这个本地方法栈就是在执行本地方法时使用的,比如会将本地方法执行需要的参数入本地方法栈,当本地方法执行的时候直接从栈中取出对应的参数,本地方法执行完后会将返回值入本地方法栈,供jvm使用。本地方法的执行时完全脱离jvm的,只是用到了jvm中的本地方法栈这块儿内存而已。如果此时,该本地方法还未加载进内存,那么就会加载该本地方法对应的动态链接库进入内存,此时动态链接才是具体的指针
- 本地类库,c或c++可执行文件,可以来操作操作系统
- 程序计数器,指向该线程执行的下一条指令的内存地址
- 虚拟机栈,主管java程序的运行,它不存在垃圾回收问题,方法执行完,立即回收该方法对应的栈帧内存,栈帧包括,本地变量表(包含引用变量),动态链接:指方法对应的运行时常量池的符号引用(方法在编译期间无法确定其属于的类,在运行期确定),返回值信息等等。
- 堆,分为新生代,老年代,方法区(1.7永久代,1.8元空间),新生代又分为eden,和survivor幸存区,幸存区又分为From区和To区,主要是为了gc的复制算法。
- 新生代,当我们创建对象时,会存在新生代的eden中,当新来一个对象,Eden存不下时,会触发一次minor gc,将eden中的存活对象复制到from区,然后清空eden区,然后新对象接着存在Eden区,当Eden区再次达到临界值时,又会触发一次minor gc,将eden和from区存活的对象复制到to区,交换from区和to区,然后清空Eden区和to区。如果进行minor gc之后,仍然存活的对象内存大于to区,这时,会根据内存担保策略,将部分对象转移到老年代(根据垃圾回收器的不同,担保策略也有差异)。
- 老年代,当new一个大对象时,如果eden放不下,会直接放到老年代,当某个对象经历多次gc后仍然存在时(默认是15次),也会即将其放入老年代,如果此时老年代已经放不下,则会执行一次full gc,执行full gc之前会先执行一次minor gc,然后执行full gc清理整个堆中的垃圾对象,如果fullgc完成后仍存放不下,会再进行一次fullgc,如果还不行,则抛出OOM异常,jvm停止
- 方法区,存储class文件的元信息,一般不会发生OOM异常。jdk1.6及之前,实现为永久代,1.7,逐渐去永久代,将字符串常量池移入堆中。1.8使用元空间代替永久代,作为方法区的实现。有可能发生OOM:java permgen space,场景
- 永久代设置的太小
- 引入了大量的第三方jar包
- tomcat中运行了太多的项目
- 代码中大量的使用了反射
- 执行引擎主要将class文件对应的java指令转换为对应操作系统的系统指令,执行引擎是实现java跨平台的关键,执行引擎除了进行指令转化以外,还包括了垃圾回收器和即时编译器。、
- 即时编译器主要是用于保存一些经常被执行引擎翻译的jvm指令所转化的操作系统指令,从而可以提高执行引擎的效率
- 垃圾回收器主要是为了清理无用的对象,释放jvm内存。常见的垃圾回收器有以下几种,可以通过参数配置动态指定使用哪种垃圾回收器 -XX:+垃圾回收器
- Serial/Serial Old垃圾回收器,Serial用于回收新生代的垃圾对象,Serial Old用于回收老年代的垃圾对象。他们都是单线程的,当进行垃圾回收时,会导致STW(阻塞其他线程)。Serial使用了复制算法。Serial Old使用了标记压缩算法。
- ParNew垃圾回收器和Serial一样是作用于新生代的(复制算法),不过不同之处是ParNew垃圾回收器是多线程的,所以会比Serial要快一些,它可以和Serial Old或者cms垃圾回收器配套使用,可以通过参数控制线程数量-XX:ParallelGCThreads
- Parallel / Parallel Old垃圾回收器,Parallel 用于新生代(复制算法),Parallel Old用于老年代(标记压缩算法),他们都是多线程的,和ParNew不同的是,Parallel 和Parallel Old更关注系统的吞吐率,所以他可以通过配置参数来配置gc的时间,只让gc执行固定的时间,没有回收的下次gc时回收。
- CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。作用于老年代可以和parNew配合使用。它也是多线程的。可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。他主要是用标记清除算法实现的,这样可能会导致产生大量的内存碎片,还可以使用参数控制进行内存整理,不过,会产生较长的SWT,它的执行大致分为四个步骤
- 初始标记,主要标记GCRoot对象可以直达的对象,会有STW产生,但是很快
- 并发标记,就是进行GCRoot可达性算法分析的过程,会标记全部对象
- 确认标记,因为并发标记时,程序仍在运行,所以可能会有新创建的对象,或者没有被标记到的对象,所以要确认标记一下,会产生STW,虽然仍然会遍历所有对象,但省去了大部分对象的标记时间,并且这个阶段是多线程执行的,所以还是提升了一定的速度
- 标记清除,这个阶段仍然是并发执行的,所以不会产生STW
- G1收集器,采用标记整理算法,不会产生内存空间碎片,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。将java堆划分为多个大小相等的独立的块(region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。额外维护了一个链表,用来记录每个region的垃圾占用率,优先回收垃圾占用率高的区域,将存活对象移动到,该region的一端,然后清理该region的剩余区域。如果在标记阶段,发现某个region的对象全部是垃圾对象,直接清理掉。
- 初始标记:此时会有一次 stop the world(STW)暂停事件. 在G1中, 这附加在(piggybacked on)一次正常的年轻代GC. 标记可能有引用指向老年代对象的survivor区(根regions).
- 扫描根区域(Root Region Scanning) ,扫描 survivor 区中引用到老年代的引用. 这个阶段应用程序的线程会继续运行. 在年轻代GC可能发生之前此阶段必须完成.
- 并发标记,此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收
- 确认标记,再标记,会有短暂停顿(STW),并行的。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
- Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。
- 复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。
- GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方
- 复制算法,将内存分成两部分,只使用其中的一部分,当该部分满了时,会将可达性分析后仍然存活的对象复制到另一区,然后清空该区
- 优点,实现简单,不会产生内存碎片
- 缺点,当对象存活率高时,需要复制大量的对象,并且要重置指针,会花费时间,并且因为只使用了一半内存,所以会浪费一般的内存空间。新生代中多使用该算法进行垃圾回收,而且因为eden和from,to的比例为8:1:1,所以只会浪费十分之一的内存
- 标记清除,分为两步,标记可回收对象和清理可回收对象,首先通过可达性分析算法标记出,可以回收的对象,然后清理掉这些对象
- 优点,效率高,不用进行复制和指针的重置
- 缺点,容易产生大量的内存碎片,会遍历两次
- 标记压缩,基于标记清除,但是当清除完成后会整理内存空间
- 优点,不会有内存碎片
- 缺点,效率低,不仅要遍历两次,并且还进行了复制移动,重置指针
- 对比
- 内存利用率:标记压缩 > 标记清理 > 复制算法
- 内存连续性: 复制算法 = 标记压缩 > 标记清理
- 效率:
- 对象存活率高: 标记清理 > 复制算法 > 标记压缩
- 对象存活率不高: 复制算法 > 标记清理 > 标记压缩
- 复制算法,将内存分成两部分,只使用其中的一部分,当该部分满了时,会将可达性分析后仍然存活的对象复制到另一区,然后清空该区
- 判断一个对象是否为垃圾对象
- 引用计数
- 一个对象被引用一次,计数器加一,被释放一次,计数器减一,当对象的计数器为0时标记为垃圾对象
- 问题:需要为每一个对象维护一个计数器,可能存在循环引用问题,导致内存泄漏
- 可达性分析算法
- 会将栈中保存的引用所指的对象,方法区中的静态常量引用所指的对象,方法区中类的静态属性引用的对象,本地方法栈中native方法的引用对象(不会被垃圾回收机制清理的对象)作为GCRoot,然后将GCRoot不可达的对象标记为垃圾对象(至少要标记两次)
- 第一次标记为垃圾的对象并不会被立刻回收,第二次标记会对第一次标记的垃圾对象进行筛选,只有当第一第二次都标记为垃圾对象的对象才会被垃圾回收器回收
- 引用计数
- java的几种引用
- 强引用,new出来的对象,需要被标记为垃圾对象后,才可以被垃圾回收机制回收
- 软引用,通过SoftReference 类实现软引用,在系统要发生内存溢出异常之前,才会将这些对象列进回收范围之中进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。软引用可用来实现内存敏感的高速缓存。
- 弱引用,WeakReference 类实现弱引用。当垃圾回收器工作时,就会清理掉该类型的对象,不管此时堆内存是否够用
- 虚引用,PhantomReference 类实现虚引用。无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
- 一个类被加载到jvm内存,jvm都做了什么?主要经历了以下几个过程,加载,链接(验证,准备,解析),初始化
- 加载,jvm将class文件通过类加载器加载到方法区,并在堆中创建一个Class对象,作为方法区各项数据的访问入口,将class文件中的静态常量池,转为方法区的运行时常量池,其中1.8之后字符串常量池由方法区转移到了堆中,其余常量池仍在方法区。
- 链接
- 验证,检验方法区class数据结构的正确性
- 准备,为static变量分配内存并赋初始值0
- 解析,将常量池的方法引用转为直接引用
- 初始化,赋予静态变量初始化的值,执行静态代码块等
版权声明:本文为qq_41335337原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。