JVM的粗浅理解

作者:吴青海
链接:https://www.zhihu.com/question/27339390/answer/36511809
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。




JVM体系结构
    JVM中主要包括(PC寄存器,栈)(堆,方法区)本地方法区
    


    一个Java线程中,包括PC寄存器(保存当前执行的指令位置)
                        栈帧(一个方法对应一个栈帧)
                        本地变量(保存计算中的临时数据)


    当一个线程结束后,线程内所有数据就自动销毁,不需要垃圾回收。
    
    方法区是堆的一部分,就是Java的永久区PermGen


        JVM方法区是用于存储类结构信息的地方,一个class文件会被解析成JVM能识别的几个部分,这些不同的部分在这个class被加载到JVM时,
        会被存储在不同的数据结构中,其中的常量池、域、方法数据、方法体、构造函数,包括类中的专用方法、实例初始化、接口初始化都存储在这个区域。
        
        方法区这个存储区域也属于Java堆中的一部分,也就是Java堆中的永久区。这个区域可以被所有的线程共享,并且它的大小可以通过参数来设置。
        
    本地方法区是为了JVM运行native方法(C/C++等语言写的方法)准备的区域。


内存分配方式
    静态内存分配:
        在编译时确定需要的内存空间,当程序加载时系统把内存一次性分配给它。
        静态内存分配是在Java栈上分配的,当方法结束时对应的栈帧也就撤销,所以分配的内存空间就被回收
    动态内存分配:
        程序执行时才知道要分配的内存大小,当对象创建的时候,在堆上给对象分配一块空间,空间回收时间不定,由JVM垃圾回收器管理。






java堆(JavaHeap)
1.用来存放对象的,几乎所有对象都放在这里,被线程共享的,或者说是被栈共享的
2.堆又可以分为新生代和老年代,实际还有一个区域叫永久代,但是jdk1.7已经去永久代了,所以可以当作没有,永久代是当jvm启动时就存放的JDK自身的类和接口数据,关闭则释放。新生代可以分为Eden区和两个幸存区,这么设计是为了更好地利用内存  之前的设计是只分为两部分一样一半  后来发现这样只利用到了一半的内存  才改为按比例分成三个区的,使用的是复制回收算法,两个幸存区是较小的区域。逻辑是每次使用Eden区和其中一个幸存区,回收时将其还存活着的对象一次性的复制到另一个幸存区中,最后清理到刚才使用的Eden和其中一个幸存区。。新建对象就在Eden区,Eden就是伊甸,顾名思义。但是并不是对象最活跃的区域,对象最活跃的区域是老年代,因为经过各种垃圾回收之后对象都跑到这里来了。
3.内存溢出内存溢出其实没什么好讲的,满了就会溢出。怎么才能满呢,不断创建对象,那问题又来了,创建多了被回收怎么办,好办,将新建的对象存到list里去,就不会回收了,为什么呢,因为jvm判定一个对象的死活就是根据对象是不是被引用。
此外堆跟随jvm的,有jvm就有堆。堆也是垃圾回收的主要区域,又叫GC堆,垃圾堆,玩笑。jvm


默认下,新生代 ( Young ) = 1/3 的堆空间大小,老年代 ( Old ) = 2/3 的堆空间大小;


新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。默认的,Edem : from : to = 8 : 1 : 1;


JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间;


GC 分为两种:老生代中采用标记-清除算法的Full GC ( 或称为 Major GC )和新生代中采用复制算法的Minor GC。新生代是 GC 收集垃圾的频繁区域;



1.要说栈是用来存什么的,其实我感觉不严谨,栈是运行时创建的,是跟随线程的,它不是用来存什么的,那它用来干什么的,它是用来存栈帧的,没有图不太好说呢,等下我去截个图。






图来了我就不用多说了。每个栈帧其实可以理解为一个方法,我是这么理解的,之间的关系就是调用。
2.栈的好处就是不需要垃圾回收,随着线程结束内存就释放。
3.但是并不是说就不会内存溢出,那么栈的内存溢出是怎么产生的呢,肯定也是满了,这个满了怎么理解呢,一是要申请的不够了,二是jvm内存太小,这是个有趣的问题。但是产生的错误却是不一样的,如果创建一个void方法调用自身,错误是stackoverflowError,如果不断创建线程则会outOfMemoryError。这里就有一个比较高级的问题了,对于第二种多线程内存溢出该怎么解决呢,深入理解jvm一书中给出的解决方案是这样的,通过减小最大堆和栈容量来换取更多的线程。


方法区和运行时常量池
1.方法区是堆的一个逻辑区域,但是又叫非堆。运行时常量池又是方法区的一部分,真正的一部分。方法区并不是存方法的,存方法的应该是栈或者栈帧。方法区存的是类信息、常量、静态变量等,也是被线程共享的区域。运行时常量池存放的是编译期生产的各种字面量和符号引用。
2.这块内存区域的回收没啥好说的,因为我也不太清楚,我只知道HotSpot的设计团队选择把GC分代扩展至方法区了,或者是使用永久代实现方法区。
3.内存是肯定会溢出的,不断创建类会导致方法区内存溢出,而不断将常量放入常量池(String.intern()),常量池也会内存溢出。

猜你喜欢

转载自blog.csdn.net/qq_21325705/article/details/79996921