几个疑问:
栈区:也叫java虚拟机栈,是由一个一个的栈帧组成的后进先出的栈式结构,栈桢中存放方法运行时产生的局部变量、方法出口等信息。当调用一个方法时,虚拟机栈中就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,则继续在栈顶创建新的栈桢。
总结:一个线程对应一个Java栈,一个方法对应一个栈帧。
0、GC怎么判断堆中一个对象有没有被引用?
java在方法里new一个对象,该对象的生命周期及作用范围
生命周期取决于他有没有被引用,会一直持续到没有变量引用他,作用范围取决于引用他的变量的作用范围。
总结:Java大概设计:
(1)堆中的对象,从创建(在堆中生存时)会携带一个变量引用它的标记。(大概会有被变量引用的标记,否则GC怎么知道堆中对象有没有被引用?)
举例:比如一个局部变量user,如果是需要手动释放内存的c++语言,在它即将离开作用域时,(假定该对象不会被其它对象使用)就会释放user指向的内存空间。
在java中,怎么释放user对象呢?它所在方法执行时,创建了一个栈帧在Java栈顶入栈,user引用在这个栈帧中。(随着栈帧的创建,堆中user对象也生成了?,该对象也被赋予一个被user引用的标记),随着方法的执行,user引用会出栈(堆中对象会清除该对象被该变量引用的标记)。(假定该对象没有被其他对象持有),在下一轮GC运行时,就会发现该对象没有被引用,再查看堆中其它对象时,又发现没有任何对象持有该user对象,那么该user对象就会被清空释放内存。
(注:持有对象,对象之间的持有,每个对象在内存都是独立占有空间,对象之间持有时,大概是做一个标记就行,标记就是在当前对象中标记一下被持有对象的地址。大概不能说持有对象的引用。和引用没关系,被持有对象有没有被变量引用,和当前持有者没有关系。持有者可以使用该对象,变量引用也可以使用该对象)
(2)比如:有两个局部变量
User user1 = new User(); // 离开当前作用域之后,GC应该会清除user1指向的对象
User user2 = session.secuObj.getUser(); // 离开作用域之后,由于user2指向的对象仍然被全局变量?session持有。所以user2指向的对象,不会被清除。
(3)两个成员变量
User user2 = Session.secuObj.getUser(); // 该user2对象被全局变量?持有,生命周期应该和程序一样长。
User user1 = new User(); // 普通的成员变量, 怎么理解?
1)、user1所属的类进行实例化时,user1对象在堆中生成,并被标记被user1变量引用了。
2)所在类的各个方法使用user1变量时,方法结束user1出栈后,由于user1是成员变量,所以指向的对象还是在被user1变量引用,该标记不会被清除。(所在类的其它方法还可能会使用user1)
3)user1是成员字段,会被所属类的当前对象所持有。
3.1、成员字段的生命周期和它所属对象一样长?往上递归?什么时候结束?
3.2、user1堆中对象什么时候失去被user1变量引用的的标记?
1、作用域
在计算机程序中,声明在不同地方的变量具有不同的作用域,例如局部变量、全局变量等。在Java语言中,作用域是由花括号的位置决定的,它决定了其定义的变量名的可见性与生命周期。
在Java语言中,变量的类型主要有3种:成员变量、静态变量和局部变量。
类的成员变量的作用范围与类的实例化对象的作用范围相同,当类被实例化时,成员变量就会在内存中分配空间并初始化,直到这个被实例化对象的生命周期结束时,成员变量的生命周期才结束。(?是这样吗)
成员变量与它所在类的实例对象的生命周期一致,那么对象的生命周期?
被static修饰的成员变量称为静态变量或全局变量,与成员变量不同的是,静态变量不依赖于特定的实例,而是被所有实例所共享,也就是说,只要一个类被加载,JVM就会给类的静态变量分配存储空间。因此,就可以通过类名和变量名来访问静态变量。
局部变量的作用域与可见性为它所在的花括号内。
同一作用域范围的包裹下成员变量名和局部变量名是可以变量名相同的。在方法中使用变量时,如果不指明使用成员变量还是局部变量,那么默认使用局部变量(就近原则),但是如果局部变量超出了它本身的作用域范围则会失效,被JVM垃圾回收,(不管会不会被回收)那么则可以重复命名此变量(同一作用域范围内,局部变量不能重名)。
2、对象的生命周期
3、函数执行的堆栈分析
正文:
出自:https://www.jianshu.com/p/49192175c759
Java对象是否存活的判断算法——根搜索算法。(这是判断对象是否存活的算法,不同于GC算法)
这个算法的思路其实很简单,它把内存中的每一个对象都看作一个节点,并且定义了一些对象作为根节点“GC Roots”。如果一个对象中有另一个对象的引用,那么就认为第一个对象有一条指向第二个对象的边,如下图所示。JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。
四种作为GC Roots的对象==>
1、虚拟机栈中的引用的对象,我们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种情况是最常见的。
2、我们在类中定义了全局的静态的对象,也就是使用了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。
3、常量引用,就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也应该作为GC Roots。
4、使用JNI技术时,有时候单纯的Java代码并不能满足我们的需求,我们可能需要在Java中调用C或C++的代码,因此会使用native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为GC Roots。
网友评论