文章目录
- 内存结构
- 内存溢出的产生
内存结构
对于JVM的内存结构,我将按照上面两张图配合来讲。
Java Source就是我们的Java源代码,经过JVM编译器编译之后变为Java Class也称Java字节码文件。当我们运行java命令对字节码文件进行运行的时候,将会创建一个Java虚拟机。
此时虚拟机将会创建一个名字为main的主线程来执行我们的入口方法的代码,也就是它会去找main方法。
而运行代码也是需要内存的,而他的内存从JVM Stacks虚拟机栈中获取,当然不光光是主线程,还有我们在代码中new Thread的线程也是从这里获取内存资源。
当然,想要运行main方法,会发现main方法是在一个类Main中的,而此时还没有加载这个类呢,因此我们需要对这个类进行加载,此时就会通过类加载子系统将类的原始信息加载到Method Area方法区中,加载到这个区中的信息包括类的名字,类的继承关系,类的成员变量,类中引用的其他类,以及类的方法代码等。
而当我们的方法执行到一个new操作的时候,也就是需要创建一个实例对象的时候,对象所占用的内存来自于堆Heap,会先计算这个对象需要占用多大的内存,然后去堆中找到一个这么大的内存来创建这个对象。
而指向这个对象的引用,以及方法参数,他们都是引用值,他们就不是放在堆中了,而是放在虚拟机栈JVM Stacks中。
而当调用update这种我们自己编写的方法,或者调用hashCode这种Java无法实现,必须依靠底层操作系统来实现的代码,我们都放在JVM Stacks中,当然hashCode这种方法一般是放在Native Method Stacks本地方法栈中,不过我们JVM的实现是直接将他们整合在JVM Stacks中了。
而当前线程他不可能一直占用CPU,那么当他交出CPU的使用权的时候,下一次再次轮到它之后如何直到要从哪里开始呢?
这个时候就用到了PC Register,程序计数器,他将会当前这个线程运行到的代码的位置,因此即使当前线程被切换了,那么下次切换回来的时候通过程序计数器也能直到该从哪里继续运行。
那么当我们的对象被设定为null,或者没有引用的时候,那么我们的GC垃圾回收器将会对这些对象进行回收。
同时,我们的CPU其实是不能理解字节码指令的,因此我们还需要JVM提供的解释器对字节码指令进行解释,将其翻译成CPU可以理解的机器码指令,当然对于重复的代码也是重复进行解释的。
而对于一些频繁调用的热点代码,如执行万次的循环,那么就会通过JIT进行优化,加快这些热点代码的执行,从而提高效率。
内存溢出的产生
上面的结构中,除了程序计数器不会发生内存溢出,其他的都会发生内存溢出。
出现 OutOfMemoryError的情况
- 堆内存耗尽–对象越来越多,又一直在使用,不能被垃圾回收
- 方法区内存耗尽–加载的类越来越多,很多框架都会在运行期间动态产生新的类
- 虚拟机栈累积–每个线程最多会占用1M内存,线程个数越来越多,而又长时间运行不销毁时出现
StackOverflowError的区域
- 虚拟机栈内部–方法调用次数过多,每一次调用方法都会从线程分配到的内存中拿走一点去做函数调用,如果调用太多,那么内存不够了,就会溢出