代码演示对象分配过程
/** * 内存分配过程演示 * 设置内存大小:-Xms600m -Xmx600m * 通过 jvisualvm 查看内存分布 */ public class HeapInstanceTest { byte[] buffer = new byte[1024 * 200]; public static void main(String[] args) throws InterruptedException { ArrayList<HeapInstanceTest> list = new ArrayList<>(); while (true) { list.add(new HeapInstanceTest()); Thread.sleep(10); } } } 方法区(Method Area)概述:是线程共享的,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。
方法区主要存放的是 Class,而堆中主要存放的是 实例化的对象。
方法区与Java堆一样,是各个线程共享的内存区域
方法区在JVM启动的时候创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的
方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展
方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutofMemoryError:PermGen space 或者java.lang.OutOfMemoryError:Metaspace
加载大量的第三方的jar包
Tomcat部署的工程过多(30~50个)
大量动态的生成反射类
关闭JVM就会释放这个区域的内存
方法区的演进
首先只有HotSpot才有永久代
jdk版本 变化jdk1.6及之前 有永久代(permanent generation),静态变量存放在永久代上
jdk1.7 有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中
jdk1.8及之后 无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆中
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存
永久代、元空间二者并不只是名字变了,内部结构也调整了
永久代为什么要被元空间替代?
为永久代设置空间大小很难确定的(而元空间在本地内存中,没有这个问题)
对永久代进行调优是很困难的(方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不在使用的类型)
垃圾回收(GC)什么是垃圾?
为什么要垃圾回收?
Java垃圾回收机制
什么是垃圾垃圾是指在运行程序中没有任何指向的对象,这个对象就是需要被回收的垃圾。
为什么需要垃圾回收如果不及时对内存中的垃圾进行清理,那么,这些垃圾对象所占的内存空间就会一直保留到应用程序的结束,被保留的空间无法被其他对象使用,久而久之就会导致内存溢出。
除了释放没用的对象,垃圾回收也可以清除内存里的记录碎片。碎片整理所占用的堆内存移到堆的一端,以便JVM将整理出的内存分配给新的对象。
Java垃圾回收特点优点
自动内存管理,无需开发人员手动参与内存的分配与回收,这样降低内存泄漏和内存溢出的风险
自动内存管理,将程序员从繁重的内存管理中释放出来,可以更专注于业务开发。
担忧
对于Java开发人员而言,自动内存管理就像是一个黑匣子,如果过度依赖于”自动“,那么最严重的情况就是会弱化Java开发人员在程序出现内存溢出时,定位问题和解决问题的能力。
此时,了解JVM的自动内存分配和内存回收原理就显得非常重要,只有真正了解JVM是如何管理内存后,我们才能在遇见OOM时,快速地根据错误日志定位问题和解决问题。
当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术实施必要的监控和调节。
垃圾回收相关算法在堆里存放着几乎所有的Java对象实例,在GC执行垃圾回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为已经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间,因此这个过程我们可以称为垃圾标记阶段。
那么在JVM中究竟是如何标记一个死亡对象呢?简单来说,当一个对象已经不再被任何的存活对象继续引用时,就可以宣判为已经死亡。
判断对象存活一般有两种方式:引用计数算法和可达性分析算法。
标记阶段:引用计数算法引用计数算法(Reference Counting)比较简单,对每个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。