线程共享:
线程私有:
启动类加载器(Bootstrap)
扩展类加载器(Extension)
应用程序类加载器(Application)
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把请求委托给父加载器去完成,依次向上;只有当父加载器没有找到所需的类时,子加载器才会尝试去加载该类。
作用:保证了Java的出厂源码不会受到开发人员编写的污染。
【请你谈谈Java的类加载过程】
大部分信息都是通过常量池中的符号常量来表述的。
在Java中,常量池包括两层含义:
内存溢出是指可用内存不足。
程序运行需要使用的内存超出系统当前所能提供的最大可用值,如果不进行处理就会影响到其他进程,所以现在操作系统的处理办法是:只要超出立即报错,比如抛出内存溢出错误(OOM) 。
某些对象不会再被程序用到了,但是垃圾回收器又不能回收不了他们,这种情况叫做内存泄漏
举些例子:
如果存在严重的内存泄漏问题,随着时间的推移,则必然会引起内存溢出。
内存泄漏一般是资源管理问题和程序BUG,内存溢出则是内存空间不足和内存泄漏的最终结果。
# JVM启动参数不换行
# 设置堆内存
‐Xmx4g ‐Xms4g
# 指定GC算法
‐XX:+UseG1GC ‐XX:MaxGCPauseMillis=50
# 指定GC并行线程数
‐XX:ParallelGCThreads=4
# 打印GC日志
‐XX:+PrintGCDetails ‐XX:+PrintGCDateStamps
# 指定GC日志文件
‐Xloggc:gc.log
# 指定Meta区的最大值
‐XX:MaxMetaspaceSize=2g
# 设置单个线程栈的大小
‐Xss1m
# 指定堆内存溢出时自动进行Dump
‐XX:+HeapDumpOnOutOfMemoryError
‐XX:HeapDumpPath=/usr/local/
我知道的其实只有三个
垃圾是指在 运行程序中没有任何指针指向的对象, 这个对象就是需要被回收的垃圾。
如果不及时对内存中的垃圾进行清理,那么这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。甚至可能导致内存溢出。
在堆里存放着几乎所有的Java对象实例,在GC(垃圾回收器)执行垃圾回收之前,首先需要区分出内存中哪些是存活对象(有用对象),哪些是死亡对象(垃圾对象)。只有被标记为已经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间,因此这个过程我们可以称为垃圾标记阶段
那么在JVM中究竟是如何标记一个死亡对象呢?简单来说,当一个对象已经不再被任何的存活对象继续引用时,就可以宣告为已经死亡
判断对象存活一般有两种方式:引用计数算法 和 可达性分析算法
引用计数算法(Reference Counting)比较简单,对每个对象保存一个整形的引用计数器属性,用于记录对象被引用的情况
优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。
缺点:
可达性分析特点:
基本思路:
问题来了,哪些对象可被称为GC Roots对象呢?或者说Java中,GC Roots包含哪几类对象呢?
注意:
当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收、释放掉垃圾对象所占用的内存,以便有足够的可用空间为新对象分配内存。
目前在JVM中比较常见的三种垃圾回收算法是:
标记-清除(Mark - Sweep)算法是最基础的收集算法,它分为“标记”和“清除”两个阶段:
优点:
缺点:
标记-整理分为“标记”和“整理”两个阶段:
标记-整理算法的最终效果等同于标记-清除算法执行后,再进行一次内存碎片整理,因此也可以把它称为标记-清除-压缩算法
可以看到,标记的存活对象将会被整理,按照内存地址依次排序。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销
优点:
缺点:
核心思想:
将内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的对象,交换两个内存的角色,最后完成垃圾回收
对于这种算法来说,如果存活的对象过多的话则要执行较多的复制操作,效率会变低,因此它适合存活率较低的情况。事实上在年轻代中就是使用的复制算法。
优点:
缺点:
Mark-Sweep | Nark-Compact | Copying | |
---|---|---|---|
速度 | 中等 | 最慢 | 最快 |
空间开销 | 少(但有内存碎片) | 少(没有内存碎片) | 需要额外的一半内存开销 |
移动对象 | 否 | 是 | 是 |
从效率来说,复制算法是当之无愧的老大,但是却浪费了太多内存
而为了尽量兼顾上面提到的三个指标,标记-整理算法相对来说更平滑一些,但是效率上不尽如人意,它比复制算法多了一个标记的阶段,比标记-清除算法多了一个整理的阶段
通俗的理解java对象的这一辈子
我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。
分代收集过程
8:1:1
的比例分为一个 Eden
(伊甸园区) 和两个 Survivor
(幸存区),幸存区一个称为“From”区、一个称为“To”区,名字是动态的(谁空谁是“to”)。当新对象生成,Eden存满了空间不足,则会发起一次 Minor GC。回收时先将 Eden 区存活对象复制到一个 From区,然后清空 Eden 区。再次触发Minor GC时(Eden园满了才会触发),则将 Eden 区和 From区的存活对象复制到另一个 To区,然后清空 Eden 和这个 From区,此时 From区是空的,然后将 From区和 To区交换(谁空谁是To区), 如此往复。当 To区不足以存放 Eden 和 From的存活对象时,就将存活对象直接存放到老年代。当对象在 Survivor 区躲过一次 GC 的话,其对象年龄便会加 1,默认情况下,如果对象年龄达到 15 岁,就会移动到老年代中。若是老年代也满了就会触发一次 Full GC,也就是新生代、老年代都进行回收。新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制 Eden 和 Survivor 的比例。(垃圾清除算法用的是复制算法)强引用: 表示一种比较强的引用关系,只要还有强引用对象指向一个对象,那么表示这个对象还活着(GC Roots可达),垃圾收集器宁可抛出OOM异常,也不会回收这个对象。
软引用: 软引用用于关联一些可有可无的对象,例如缓存。当系统内存充足时,这些对象不会被回收;当系统内存不足,将要发生内存溢出之前,就会回收这些对象(即使这些对象GC Roots可达),如果回收完这些对象后内存还是不足,就会抛出OOM异常。
弱引用: 被弱引用关联的对象只能生存到一下次垃圾回收之前。当垃圾收集器工作时,无论内存空间是否充足,都会回收掉被弱引用关联的对象。ThreadLocal中就使用了WeakReference来避免内存泄漏。
虚引用: - 形态虚设,如果一个对象仅持有虚引用,那么它**就和没有任何引用一样,在任何时候都可能被垃圾回
jdk8环境下,默认使用 Parallel Scavenge(新生代)+ Serial Old(老年代)
Parallel Scavenge 收集器:
属于新生代收集器,是并行的多线程收集器,采用复制算法**。
该收集器还有个特点就是GC自适应调节策略,虚拟机会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适应调节策略。
Serial Old 收集器:
属于老年代收集器,是单线程收集器,采用标记-整理算法
Java9之后,官方JDK默认使用的垃圾收集器是G1。
一种以获取最短回收停顿时间为目标的收集器。
特点:基于标记-清除算法实现。并发收集、低停顿。
因为GC过程中,有一部分操作需要等所有应用线程都到达安全点、暂停之后才能执行,这时候就叫做Stop-the-world
。除了GC所需的线程外,其他线程都将停止工作,中断了的线程直到GC任务结束才继续它们的任务。
哪些可以当做安全点:
安全点的选择很重要, 如果太少可能导致GC 等待的时间太长, 如果太频繁可能导致运行时的性能问题。大部分指令的执行时间都非常短暂,通常会根据“ 是否具有让程序长时间执行的特征” 为标准。比如: 选择一些执行时间较长的指令作为safe Point ,如:
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_45464560/article/details/123042846
内容来源于网络,如有侵权,请联系作者删除!