程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

干货-深入理解Java虚拟机-1-虚拟机原理

发布于2021-05-29 20:45     阅读(466)     评论(0)     点赞(26)     收藏(0)


前言

基础不牢,地动山摇,强如老哥也经常巩固自己的基本功,你的基础决定了你的高度。
最近博主在复习Java虚拟机,对Java虚拟机的理解又有了一个更深层次的理解,记录下一些笔记及重点摘要,有兴趣的道友可以一起学习下。
链接附上:《干货-深入理解Java虚拟机》
在这里插入图片描述

正文

1. Java虚拟机的内存结构

在这里插入图片描述

2.内存溢出

Java堆溢出:

异常信息: java.long.OutOfMemoryError,跟着会进一步提示Java heap space。
解决方法: 遇到堆内存溢出,首先需要判断是不是内存泄露,也就是存在大量的对象在引用链中存在导致垃圾回收器无法回收,这时需要找到导致内存泄漏的代码进行修改;
如果不是内存泄漏,换句话说就是确实所有的对象都还活着,也就是确实出现了内存溢出,这时需要检查是不是某些对象生命周期过长、持有时间过长导致,尝试减少运行时间所使用的内存,如果还是不行,就需要通过-Xmx和-Xms参数来调整虚拟机参数,调大内存。

虚拟机栈和本地方法栈溢出:

对于StackOverflowError异常,在单线程时常常会产生,产生的原因通常是因为方法的无限循环调用或者无限递归,导致栈帧过大溢出,这种需要检查代码逻辑,还有一种可能时代码逻辑正确,但是方法栈不够用,这时需要-Xss参数来设置栈容量大小。
对于OutOfMemoryError异常,通常由于过多的创建线程导致,这种情况下如果不能减少线程数时我们只能通过减小最大堆和减小栈容量来换取更多的线程,如果没有接触过,通过这种“减少内存”的方式来解决内存溢出的方法会比较难以想到。
这里解释一下为什么这样可以解决,因为操作系统给每个进程分配的内存是有限制的,虚拟机提供的参数来控制最大堆内存和方法区的最大内存,用进程的内存减去这两个参数设定的内存,再减去虚拟机进程消耗的内存,剩下的就由本地方法栈和虚拟机栈瓜分了,前面我们知道,这两块都是线程私有的内存,如果每个线程瓜分到的内存越大,那自然我们能建立的线程就会变少,建立线程时就更容易耗尽内存。

3. Java对象的四种引用状态

强引用: 直接被引用的对象,当虚拟机内存不足时,抛出异常也不会回收这些对象。

Object object = new Object();
String str = "StrongReference";

软引用: 有用但不是必须的对象,当内存不足时会被当做辣鸡回收,下面代码是示例,obj指向了null就会被JVM回收,但是如果内存够用,我们通过sr.get()能获取到对象,如果内存不足,他就会被回收结果为null。

        Obj obj = new Obj();            
        SoftReference<Obj> sr = new SoftReference<Obj>(obj);  
        obj = null;  
        System.out.println(sr.get());  

弱引用: 在垃圾回收时不管内存够不够用都会被回收,但是垃圾回收器的线程优先级较低,因此不一定很快发现弱引用对象并回收。

       WeakReference<String> sr = new WeakReference<String>(new String("hello"));
        System.out.println(sr.get());
        System.gc();                //通知JVM的gc进行垃圾回收
        System.out.println(sr.get());

虚引用: 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

 	ReferenceQueue<String> queue = new ReferenceQueue<String>();
        PhantomReference<String> pr = new PhantomReference<String>(new 		String("hello"), queue);
        System.out.println(pr.get());

4. Java虚拟机垃圾回收机制

如何判断对象成为垃圾?

传统的算法是引用计数法,就是一个对象被引用就+1,引用失效就-1,任何引用计数是0的对象都是可回收的垃圾,这种算法简单、效率高,但是有一个致命问题就是无法解决循环引用,就是a指向b,b指向a,但是他们两个都没有再其他任何地方被使用,就不会被视为垃圾。
Java虚拟机采用的是可达性分析算法,算法的思路就是通过一系列被称为“GC Roots”的对象作为起点,从这些节点开始构建出引用链,如果一个对象没有任何一条链路可以到达“GC Roots”节点,那么认为对象已经成为垃圾,可以回收,这样就解决了循环引用得问题。

如何判定是否回收?

在Java中,判定对象回收(对象死亡)要经历两次标记,第一次标记是对象无引用链路可达“GC Roots”(死缓),第二次判断是此对象是否有必要调用finalize()方法(宣布死刑),如果对象没有覆盖重新finallize()方法或者finallize()已经被调用过一次,那么直接判定死亡,否则会调用finallize()方法,执行完成后再判定死亡,所有死缓对象可以在这个方法中实现最后一次自救,例如将自己赋值给一个全局的强引用,但在下次回收时如果还是死缓则会被直接回收,原因是finallize()方法只会被执行一次。
通过finallize()执行的功能大多数都可以通过try{}finally{}的方式执行,而且finallize()方法有可能造成垃圾回收器崩溃,所以基本不使用。

JVM垃圾回收的方法原理?

JVM垃圾回收主要对堆内存(存放对象)进行扫描回收,为了提高回收效率并且不影响系统性能,堆内存管理被分为新生代和老年代,采用不同的回收算法。
新生代: 新创建的对象一般放在新生代内存区域,极少部分会直接放在老年代,新生代内存区域被划分为三块:
在这里插入图片描述
这里简单说明一下:新创建的对象会被放在Eden(新生伊甸园)和一块Survivor(存活者)区域中,当进行一次垃圾回收时,将内存中的垃圾对象进行回收,这时内存区域会出现不规则的间断,就是不是一块完整的存储区域,不利于我们的存储,所以我们把没有被清除的对象复制到另一块Survivir区域中,修改下对象的引用地址,再把之前Eden和Survivor内存区域清除,这样就完成了一次GC,熬过一次GC的对象存活年龄会被+1,当到达某一年龄(默认15,可配)时会被移入老年代。
新生代使用的这种回收算法叫做复制算法,Java中叫做Minor GC,因为新生对象90%以上都是要被回收的,而复制算法的优势是可以保证内存区域块的完整性,但是需要修改引用占用一定资源,这正好符合新生代所需。
老年代: 老年代对象大多数是新生代熬过来的,也有少部分占用内存很大(需要直接分配一块完整内存)的对象会被直接放入老年代,老年代垃圾回收使用的是Full GC,采用标记-清除算法,这种算法会直接将垃圾进行回收。Full GC操作没有Minor GC频繁,且进行一次消耗的时间会比较长。

以上就是博主第一天的学习分享,谢谢大家观看,记得一键三连哦!

另外,附上学习书籍:《深入理解Java虚拟机》,需要某宝可以进行购买
在这里插入图片描述



所属网站分类: 技术文章 > 博客

作者:飞向远方

链接:http://www.javaheidong.com/blog/article/207322/0a64882989bb97f8ca4c/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

26 0
收藏该文
已收藏

评论内容:(最多支持255个字符)