一直对jvm看了又忘,忘了又看的。今天做一个笔记整理存放在这里。
我们先看一下JVM的内存模型图:
上面有5个区,这5个区干嘛用的呢?
我们想象一个场景:
我们有一个class文件,里面有很多的类的定义是不是,类的定义放在什么地方呢?类的定义就放在方法区里面。
程序在运行时会产生很多的对象,比如LinkList等这些对象就存放在堆里面。
栈:
我们的程序在运行时本质上就是启动线程在运行,比如main函数就是一个主线程。一个main主线程可以有很多的子线程。
线程在工作就是执行我们的各种方法。方法里面肯定有我们自己定义了一些局部的变量,比如我们在方法里面new了一个对象,对象肯定是放在堆里面的,但是对堆的引用我们就放在栈里面。那么栈就有问题了,我们一个程序有很多的线程,如果把所有的线程里面的变量存放在一起,肯定有会有变量是重复的,冲突。所有不能放在一起。所以栈里面是分线程来存放的。每一个线程都是自己的栈空间,线程私有的。堆是线程共享的。
栈里面有一个细节:
就是说一个栈空间是以不同的线程区分开来。每个线程有自己栈,每个线程里面又会执行很多的方法,每一个方法对应一个栈帧:
每个方法执行时都会创建一个栈帧(Stack Frame)用语存储局部变量表、操作数栈、动态链接、方法出口等信息。从下图从可以看到,每个线程在执行一个方法时,都意味着有一个栈帧在当前线程对应的栈帧中入栈和出栈。
图中可以看到每一个栈帧中都有局部变量表。局部变量表存放了编译期间的各种基本数据类型,对象引用等信息。
好了,我们看一下具体的东西:
本地方法栈(Native Stack)
本地方法栈(Native Stack)与Java虚拟机站(Java Stack)所发挥的作用非常相似,他们之间的区别在于虚拟机栈为虚拟机栈执行java方法(也就是字节码)服务,而本地方法栈则为使用到Native方法服务。
堆(Heap)
对于大多数应用来说,Java Heap是Java虚拟机管理的内存的最大一块,这块区域随着虚拟机的启动而创建。在实际的运用中,我们创建的对象和数组就是存放在堆里面。如果你听说线程安全的问题,就会很明确的知道Java Heap是一块共享的区域,操作共享区域的成员就有了锁和同步。在程序的运行中不断地new 对象,就存在堆里面。
与Java Heap相关的还有Java的垃圾回收机制(GC),Java Heap是垃圾回收器管理的主要区域。程序猿所熟悉的新生代、老生代、永久代的概念就是在堆里面,现在大多数的GC基本都采用了分代收集算法。如果再细致一点,Java Heap还有Eden空间,From Survivor空间,To Survivor空间等。
Java Heap可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
上面这张图就是我们新生代,老年代的图。涉及到垃圾回收机制。
我们来介绍一下垃圾回收机制:
刚开始时创建的对象都存放在Eden Sapce里面(伊甸园,新生代),大家都无忧无虑的。然后呢 ,垃圾回收机制来了。先对生活在伊甸园的对象检测一次,发现还有利用价值就把对象放在Survivor空间里面的From Space,survivior空间分为两块,一块是From Space,一块是ToSapce空间。ToSpace空间是很少用的(就是用来做垃圾回收的挪动的)。然后垃圾回收机制对From Space的对象回收比如18次,还能幸存下来,就放在老年代里面(Old Generation)。我们很奇怪,这里的ToSpace是干嘛用的。
好了我们来介绍下垃圾回收算法:
有 两种算法。
1:对象A有没有引用对象B.有引用就不是垃圾,这种方法有问题。A,B相互引用,就不能区分出是不是垃圾了。
2:根节点搜索。从根节点往下搜索。能搜索到了就不是垃圾,不能搜索到的,就是垃圾。
这是由映射表记录的,再详细就不知道了。
对象其实就是一个方格:
如上,红色是好的对象,黑色的是垃圾对象。
我们怎么去回收黑色的对象呢:
方法一:标志-清除:
直接把黑色的对象清除掉。
但是有一个问题:这样清除掉的恶化,我的空间变得很零散。下次要放一个大的对象的话(必须是一个连续的空间)比如四个格子,就放不下去了。
方法二:标记整理:
我只移动有用的:把红色的有用的对象移动到白色的空白地方。但是造成的问题是:对在运行的程序有影响。
方法三:
复制算法:
预先存留一块有用的的空间:那就是上文提到的ToSpace空间。
在FromSpace空间中,垃圾回收机制把没用的的对象标记成黑色的,然后把红色的有用的对象都转移到右边的ToSpace空间,等到垃圾回收机制把左边的黑色和红色全部清除之后。再把右边的ToSpace的那些红色有用对象再移植到左边的FromSpace空间。这样就整齐了。
然后我们会想一个问题。那右边给他预留这么大的空间不是很浪费吗?其实实际上右边的空间不需要那么大,只要一点点就够了。如下图
因为我们的有用的对象(红色的方块)其实是很少的。很多对象用好一次就不用了的。
方法区(Method Area)
方法区(Method Area)与堆(Java Heap)一样,是各个线程共享的内存区域,它用于存储虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是她却有一个别名叫做非堆(Non-Heap)。分析下Java虚拟机规范,之所以把方法区描述为堆的一个逻辑部分,应该觉得她们都是存储数据的角度出发的。一个存储对象数据(堆),一个存储静态信息(方法区)。
在上文中,我们看到堆中有新生代、老生代、永久代的描述。为什么我们将新生代、老生代、永久代三个概念一起说,那是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。这样HotSpot的垃圾收集器就能想管理Java堆一样管理这部分内存。简单点说就是HotSpot虚拟机中内存模型的分代,其中新生代和老生代在堆中,永久代使用方法区实现。根据官方发布的路线图信息,现在也有放弃永久代并逐步采用Native Memory来实现方法区的规划,在JDK1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出。