精华内容
下载资源
问答
  • 使用计数:和其他内核对象一样,用来标识使用该事件对象的不同线程个数。表示自动或手动重置事件的布尔值:当一个事件是自动重置事件事件被触发后,只有一个等待的线程会变成可调度状态(根据系统的调度策

    在所有的内核对象中,事件内核对象比其他的简单的多,可以用事件内核对象对不同进程进行同步。

    事件内核对象主要包括三个部分:使用计数一个表示是自动还是手动重置事件的布尔值一个表示是否有信号的布尔值

    • 使用计数:和其他内核对象一样,用来标识使用该事件对象的不同线程个数。
    • 表示自动或手动重置事件的布尔值:当一个事件是自动重置事件,事件被触发后,只有一个等待的线程会变成可调度状态(根据系统的调度策略),然后该事件会自动变成未触发状态;当一个事件是手动重置事件,事件被触发后,所有等待的线程都会变成可调度状态,该事件在触发后一直为触发状态,直到手动重置该事件为未触发状态。
    • 是否有信号的布尔值:表示改事件是否被触发。

    下面是使用事件内核对象的所要使用的函数接口:

    1CreateEvent()

    HANDLE WINAPI CreateEvent(
      _In_opt_  LPSECURITY_ATTRIBUTES lpEventAttributes,
      _In_      BOOL bManualReset,
      _In_      BOOL bInitialState,
      _In_opt_  LPCTSTR lpName
    );

    lpEventAttributes:事件对象的安全属性,一般置为NULL

    bManualReset:事件对象是手动重置事件(TRUE)还是自动重置事件(FALSE);

    bInitialState:初始状态时触发状态(TRUE)还是非触发状态(FALSE);

    lpName:创建有名的事件对象,用于进程间的共享;

    如果该事件对象已经存在,那么CreateEvent会返回该内核对象的句柄,并通过系统返回错误ERROR_ALREADY_EXISTS,通过GetLastError()获得。

    2OpenEvent()

    HANDLE WINAPI OpenEvent(
      _In_  DWORD dwDesiredAccess,
      _In_  BOOL bInheritHandle,
      _In_  LPCTSTR lpName
    );

    dwDesiredAccess:指定想要的访问权限,EVENT_ALL_ACCESS 请求对事件对象的完全访问,EVENT_MODIFY_STATE 允许使用 SetEvent,,ResetEventPulseEvent函数;

    bInheritHandle:是否希望子进程继承事件对象的句柄,一般设置为false

    lpName:要打开的事件对象的名称;

    其他进程中的线程可以通过OpenEventCreateEvent访问已经存在的事件内核对象。和其他内核对象的访问一样。

    3WaitForSingleObject()

    DWORD WINAPI WaitForSingleObject(
      _In_  HANDLE hHandle,
      _In_  DWORD dwMilliseconds
    );

    hHandle:指向内核对象的句柄;

    dwMilliseconds:线程最大等待多长时间,直到该对象被触发。经常使用INFINITE,表示阻塞等待。

    WaitForSingleObject被称呼为等待函数,是等待内核对象被触发通用的等待函数,被用在所有的内核对象触发等待中。当事件对象处于未触发状态,等待函数会被阻塞。当处于触发状态时,等待函数会被系统调用,成功返回。当等待函数返回后,该事件对象的状态是被重置为未触发状态还是仍然处于触发状态,由该事件对象是自动重置还是手动重置事件决定。当该事件对象时自动重置事件,等待函数返回时,该事件会变成未触发状态,如果为手动重置事件,那么等待函数返回后,该事件仍然处于触发状态,直到调用ResetEvent函数,使该事件变成未触发状态。

    4SetEvent()

    BOOL WINAPI SetEvent(
      _In_  HANDLE hEvent
    );

    hObject:指向内核对象的句柄

    设置事件内核对象为触发状态;

    5ResetEvent()

    BOOL WINAPI ResetEvent(
      _In_  HANDLE hEvent
    );

    hObject:指向内核对象的句柄

    设置事件内核对象为未触发状态;对于事件内核对象,当该事件对象被设置为自动重置事件的时候,ResetEvent的调用时不必要的,因为在自动重置事件上进行等待时,即调用WaitForSingleObject,当等待函数返回时,该事件会被自动重置为未触发的状态。

    6CloseHandle()

    BOOL WINAPI CloseHandle(
      _In_  HANDLE hObject
    );

    hObject:指向内核对象的句柄

    和其他内核对象一样,无论以什么方式创建内核对象,我们都必须通过调用CloseHandle向系统表明结束使用内核对象。如果传入的句柄有效,系统将获得内核对象数据结构的地址,并将结构中的使用计数减1,如果使用计数0,就会将内核对象销毁,从内存空间中擦除。

    下面是例子:

    首先下面是两个进程对头一个文件未经同步的操作:

    //process 1
    int main()
    {  
      ofstream fileStream1("c:/test.txt", ios_base::app);
        for (int i = 0, j = 1; i < 10; ++i)
        {
            Sleep(1000);
            fileStream1<<j;
            fileStream1<<' '<<flush;
        }
    }
      
      
    //process 2
    int main()
    {
        ofstream fileStream2("c:/test.txt", ios_base::app);
        for (int i = 0, j = 2; i < 10; ++i)
        {
            Sleep(1000);
            fileStream2<<j;
            fileStream2<<' '<<flush;
        }
    }

    结果"c:/test.txt"中的内容如下:

    2 2 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 1 1 

    而下面两个进程是通过事件内核对象对文件"c:/test.txt"的操作进行同步,

    //process 1
    int main()
    {  
      //自动触发事件,初始状态为触发
        HANDLE stream1Event = CreateEvent(NULL, false, true, (LPCWSTR)"streamEvent");
        WaitForSingleObject(stream1Event, INFINITE);
       
      ofstream fileStream1("c:/test.txt", ios_base::app);
        for (int i = 0, j = 1; i < 10; ++i)
        {
            Sleep(1000);
            fileStream1<<j;
            fileStream1<<' '<<flush;
        }
        SetEvent(stream1Event);
        CloseHandle(stream1Event);
    }
      
      
    //process 2
    int main()
    {
      //自动触发事件,初始状态为触发
        HANDLE stream2Event = CreateEvent(NULL, false, true, (LPCWSTR)"streamEvent");
        WaitForSingleObject(stream2Event, INFINITE);
    
        ofstream fileStream2("c:/test.txt", ios_base::app);
        for (int i = 0, j = 2; i < 10; ++i)
        {
            Sleep(1000);
            fileStream2<<j;
            fileStream2<<' '<<flush;
        }
        SetEvent(stream2Event);
        CloseHandle(stream2Event);
    }
    

    结果"c:/test.txt"中的内容如下:

    2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 

    Jue 14, 2013 @lab

    展开全文
  • 11 可用来修改bean对象的BeanPostProcessor11.1 简介BeanPostProcessor是Spring中定义的一个接口,其与之前介绍的InitializingBean和DisposableBean接口类似,也是供Spring进行回调的。Spring将在初始化bean前后对...

    11 可用来修改bean对象的BeanPostProcessor

    11.1 简介

    BeanPostProcessor是Spring中定义的一个接口,其与之前介绍的InitializingBean和DisposableBean接口类似,也是供Spring进行回调的。Spring将在初始化bean前后对BeanPostProcessor实现类进行回调,与InitializingBean和DisposableBean接口不同的是BeanPostProcessor接口将对所有的bean都起作用,即所有的bean初始化前后都会回调BeanPostProcessor实现类,而InitializingBean和DisposableBean接口是针对单个bean的,即只有在对应的bean实现了InitializingBean或DisposableBean接口才会对其进行回调。

    BeanPostProcessor接口的定义如下:

    public interface BeanPostProcessor {
    
    	Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    
    	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
    
    }

    如你所见,BeanPostProcessor接口中定义了两个方法,其中方法postProcessBeforeInitialization()将在一个bean被完全初始化前进行回调,此时对应的bean已经实例化了,但是对应的属性注入等还没有进行,即在调用InitializingBean的afterPropertiesSet()方法或bean对应的init-method之前;而方法postProcessAfterInitialization()将在bean被完全初始化后进行回调,此时对应的依赖注入已经完成,即在调用InitializingBean的afterPropertiesSet()方法或对应init-method方法之后。两个方法的参数以及返回值对应的意义都是一样的,其中参数bean表示当前状态的bean,参数beanName表示当前bean的名称,而方法对应的返回值即表示需要放入到bean容器中的bean,所以用户如果有需要完全可以在这两个方法中对bean进行修改,即封装自己的bean进行返回。

    以下是Spring源码中对bean进行初始化的逻辑,从源码中我们可以看到是先通过applyBeanPostProcessorsBeforeInitialization()方法使用注册的BeanPostProcessor的postProcessBeforeInitialization()方法依次回调,然后是通过invokeInitMethods()方法依次调用当前bean对应的初始化方法,再通过applyBeanPostProcessorsAfterInitialization方法使用注册的BeanPostProcessor的postProcessorAfterInitialization()方法依次进行回调。

    	protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
    		if (System.getSecurityManager() != null) {
    			AccessController.doPrivileged(new PrivilegedAction<Object>() {
    				@Override
    				public Object run() {
    					invokeAwareMethods(beanName, bean);
    					return null;
    				}
    			}, getAccessControlContext());
    		}
    		else {
    			invokeAwareMethods(beanName, bean);
    		}
    
    		Object wrappedBean = bean;
    		if (mbd == null || !mbd.isSynthetic()) {
    			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    		}
    
    		try {
    			invokeInitMethods(beanName, wrappedBean, mbd);
    		}
    		catch (Throwable ex) {
    			throw new BeanCreationException(
    					(mbd != null ? mbd.getResourceDescription() : null),
    					beanName, "Invocation of init method failed", ex);
    		}
    
    		if (mbd == null || !mbd.isSynthetic()) {
    			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    		}
    		return wrappedBean;
    	}

    11.2 注册

    BeanPostProcessor的注册是非常简单的,我们只需要把它当做一个普通的bean定义到Spring的bean容器中,Spring将能够自动检测到它,并将它注册到当前的bean容器中。BeanPostProcessor是容器绑定的,即BeanPostProcessor只能对跟它属于同一个bean容器中的bean进行回调,即BeanPostProcessor不能对属于它父容器或子容器中的bean进行回调。

    在bean容器中定义了BeanPostProcessor之后,Spring将最先将BeanPostProcessor对应的bean进行实例化,如果我们制定BeanPostProcessor的lazy-initialization=”true”default-lazy-initialization=”true”,Spring将对其进行忽略,即这些配置对BeanPostProcessor不起作用。这也很好理解,因为只有这样之后在实例化其它bean的时候这些BeanPostProcessor才能派上用场。鉴于这种机制,所以这里有一个问题需要注意,Spring在初始化bean的时候将优先初始化depends-on属性指定的bean,所以当我们的BeanPostProcessor通过depends-on指定了对其它bean的依赖时,其它bean是不会被BeanPostProcessor所回调的,当然这里也包括简介的depends-on对应的bean。此外,在BeanPostProcessor实例化后需要直接或间接的进行注入的bean也由于实例化时间提前不会被BeanPostProcessor回调。还有就是BeanPostProcessor之间不会进行回调,即BeanPostProcessorA不会在BeanPostProcessorB初始化时对其进行回调。

    BeanPostProcessor在Spring内部也是用的比较多的,尤其是AOP代理部分。包括用户需要自己实现BeanPostProcessor实现代理功能时也需要注意BeanPostProcessor直接或间接关联的bean是不会被回调的,即不会被代理成功的。

    11.3 示例

    接下来看一个简单的定制自己的BeanPostProcessor的示例,在示例中我们将仅仅简单的实现一个BeanPostProcessor,在postProcessBeforeInitialization()postProcessAfterInitialization()方法中都直接返回对应的bean,然后在postProcessBeforeInitialization()方法中简单的打印一下对应的bean名称。

    public class HelloBeanPostProcessor implements BeanPostProcessor {
    
    	public Object postProcessBeforeInitialization(Object bean, String beanName)
    			throws BeansException {
    		System.out.println("beanName-----------" + beanName);
    		return bean;
    	}
    
    	public Object postProcessAfterInitialization(Object bean, String beanName)
    			throws BeansException {
    		return bean;
    	}
    
    }

    实现了BeanPostProcessor之后就可以将其定义到bean容器中,其定义方式跟普通bean的定义方式是一样的。

    <bean class="com.app.HelloBeanPostProcessor"/>

    11.4 回调顺序

    在bean容器中我们可以同时定义多个BeanPostProcessor,这样在新实例化一个bean后将依次使用每个BeanPostProcessor回调一遍,当然,如果某一个BeanPostProcessor回调后的返回的bean为null,则不再继续往下回调,将直接返回null,这个时候bean容器中对应beanName对应的bean也是null。当在一个bean容器中同时定义有多个BeanPostProcessor时,默认将根据BeanPostProcessor在bean容器中定义的先后顺序对新实例化的bean进行回调。还有一种定义BeanPostProcessor回调顺序的方法是将我们自定义的BeanPostProcessor实现类同时实现Ordered接口,然后Spring将根据Ordered接口定义的getOrder()方法的返回值来决定BeanPostProcessor回调的先后顺序,getOrder()返回值越小的越先进行回调。此外,实现了Ordered接口的BeanPostProcessor总是比没有实现Ordered接口的BeanPostProcessor先进行回调,为了便于管理,推荐要么都实现Ordered接口,要么都不实现。

    以下是一个实现了Ordered接口,并把getOrder()方法的返回值作为一个参数进行配置的示例。

    public class HelloBeanPostProcessor implements BeanPostProcessor, Ordered {
    
    	private int order;
    	
    	public Object postProcessBeforeInitialization(Object bean, String beanName)
    			throws BeansException {
    		System.out.println("beanName-----------" + beanName);
    		return bean;
    	}
    
    	public Object postProcessAfterInitialization(Object bean, String beanName)
    			throws BeansException {
    		return bean;
    	}
    
    	public void setOrder(int order) {
    		this.order = order;
    	}
    	
    	public int getOrder() {
    		return order;
    	}
    
    }

    之后就可以在配置的时候通过参数order来指定我们的getOrder()方法的返回值。

    <bean class="com.app.HelloBeanPostProcessor" p:order="3"/>

    (注:本文是基于Spring4.1.0所写)

    展开全文
  • 本文通过对象的创建步骤中的检查加载->分配内存->内存空间初始化->设置->对象初始化,对象的内存布局,什么是垃圾的两种算法以及四种引用,讲述JVM中对象及引用。

    本文通过对象的创建步骤中的检查加载->分配内存->内存空间初始化->设置->对象初始化,对象的内存布局,什么是垃圾的两种算法以及四种引用,讲述JVM中对象及引用,本篇篇幅较长,适合点赞+收藏。有什么错误希望大家直接指出~

    对象的创建

    当JVM加载后遇到一条new指令首先检查是否被类加载器加载,如果没有,那必须先执行相应的类加载过程。类加载就是把 class 加载到 JVM 的运行时数据区的过程(类加载后面有专门的专题讲)。

    一、检查加载

    首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用( 符号引用:符号引用以一组符号来描述所引用的目标),并且检查类是否已经被加载。
    解析和初始化过。

    二、分配内存

    接下来虚拟机将为新生对象分配内存。为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。
    指针碰撞
    如果 Java 堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“ 指针碰撞”。


    空闲列表
    如果 Java 堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“ 空闲列表”。

    选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。(这部分知识先了解,后续结合垃圾回收器一起去理解)
    如果是 Serial、ParNew 等带有压缩的整理的垃圾回收器的话,系统采用的是指针碰撞,既简单又高效。
    如果是使用 CMS 这种不带压缩(整理)的垃圾回收器的话,理论上只能采用较复杂的空闲列表。

    并发安全
    除如何划分可用空间之外,还有另外一个需要考虑的问题是对象创建在虚拟机中是非常频繁的行为,即使是仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。
    CAS机制
    解决这个问题有两种方案,一种是对分配内存空间的动作进行同步处理——实际上虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性;
    分配缓冲
    另一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块私有内存,也就是本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),JVM 在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个 Buffer,如果需要分配内存,就在自己的 Buffer 上分配,这样就不存在竞争的情况,可以大大提升分配效率,当 Buffer 容量不够的时候,再重新从 Eden 区域申请一块继续使用。
    TLAB 的目的是在为新对象分配内存空间时,让每个 Java 应用线程能在使用自己专属的分配指针来分配空间,减少同步开销。
    TLAB 只是让每个线程有私有的分配指针,但底下存对象的内存空间还是给所有线程访问的,只是其它线程无法在这个区域分配而已。当一个 TLAB 用满(分配指针 top 撞上分配极限 end 了),就新申请一个 TLAB。
    参数:
    -XX:+UseTLAB:允许在年轻代空间中使用线程本地分配块(TLAB)。默认情况下启用此选项。要禁用 TLAB,请指定-XX:-UseTLAB。(Enables the use of thread-local allocation blocks (TLABs) in the young generation space. This option is enabled by default. To disable the use of TLABs, specify -XX:-UseTLAB.)

    三、内存空间初始化

    (注意不是构造方法)内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(如 int 值为 0,boolean 值为 false 等等)。这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

    四、设置

    接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息(Java classes 在 Java hotspot VM 内部表示为类元数据)、对象的哈希码、对象的 GC 分代年龄等信息。这些信息存放在对象的对象头之中。

    五、对象初始化

    在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚刚开始,所有的字段都还为零值。所以,一般来说,执行 new 指令之后会接着把对象按照程序员的意愿进行初始化(构造方法),这样一个真正可用的对象才算完全产生出来。

    对象的内存布局

    在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

     

    对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
    对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个 Java 数组,那么在对象头中还有一块用于记录数组长度的数据。
    对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于 HotSpot VM 的自动内存管理系统要求对对象的大小必须是 8 字节的整数倍。当对象其他数据部分没有对齐时,就需要通过对齐填充来补全。

    对象的访问定位

    建立对象是为了使用对象,我们的 Java 程序需要通过栈上的 reference 数据来操作堆上的具体对象。目前主流的访问方式有使用句柄和直接指针两种。
    句柄
    如果使用句柄访问的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
    使用句柄来访问的最大好处就是 reference 中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。


    直接指针
    如果使用直接指针访问, reference 中存储的直接就是对象地址。
    这两种对象访问方式各有优势,使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在 Java 中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。
    对 Sun HotSpot 而言,它是使用直接指针访问方式进行对象访问的。

    判断对象存活

    在堆里面存放着几乎所有的对象实例,垃圾回收器在进行回收前,要做的事情就是确定这些对象中哪些还是“存活”着,哪些已经“死去”(死去代表着不可能再被任何途径使用得对象了)

    什么是垃圾 ?

    C 语言申请内存:malloc free ,C++: new delete。 C/C++ 手动回收内存
    Java: new。Java 是自动内存回收,编程上简单,系统不容易出错。
    手动释放内存,容易出两种类型的问题:
    1、忘记回收
    2、多次回收
    没有任何引用指向的一个对象或者多个对象(循环引用)

    引用计数法

    在对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1,当引用失效时,计数器减 1。Python 在用,但主流虚拟机没有使用,因为存在对象相互引用的情况,这个时候需要引入额外的机制来处理,这样做影响效率,
    在代码中看到,只保留相互引用的对象还是被回收掉了,说明 JVM 中采用的不是引用计数法。

    可达性分析( 面试时重要的知识点,牢记)

    来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是不可用的。

    作为 GC Roots 的对象包括下面几种(重点是前面 4 种):

    1、虚拟机栈(栈帧中的本地变量表)中引用的对象;各个线程调用方法堆栈中使用到的参数、局部变量、临时变量等。
    2、方法区中类静态属性引用的对象;java 类的引用类型静态变量。
    3、方法区中常量引用的对象;比如:字符串常量池里的引用。
    4、本地方法栈中 JNI(即一般说的 Native 方法)引用的对象。
    5、JVM 的内部引用(class 对象、异常对象 NullPointException、OutofMemoryError,系统类加载器)。(非重点)
    6、所有被同步锁(synchronized 关键)持有的对象。(非重点)
    7、JVM 内部的 JMXBean、JVMTI 中注册的回调、本地代码缓存等(非重点)
    8、JVM 实现中的“临时性”对象,跨代引用的对象( 在使用分代模型回收只回收部分代的对象,这个后续会细讲,先大致了解概念)(非重点)

    以上的回收都是对象,类的回收条件:

    注意 Class  要被回收,条件比较苛刻,必须同时满足以下的条件(仅仅是可以,不代表必然,因为还有一些参数可以进行控制):
    1、该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例。
    2、加载该类的 ClassLoader 已经被回收。
    3、该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
    4、参数控制:-Xnoclassgc:禁用类的垃圾收集(GC) 。这样可以节省一些GC时间, 从而缩短了应用程序运行期间的中断时间。
    当您-Xnoclasagc在启动时指定时,应用程序中的类对象在GC期间将保持不变,并且始终被认为是活动的。这可能会导致更多的内存被永久占用,如果不谨慎使用,将抛出内存不足异常。

    废弃的常量和静态变量的回收其实就和 Class 回收的条件差不多

    Finalize  方法

    即使通过可达性分析判断不可达的对象,也不是“非死不可”,它还会处于“缓刑”阶段,真正要宣告一个对象死亡,需要经过两次标记过程,一次是没有找到与 GCRoots 的引用链,它将被第一次标记。随后进行一次筛选(如果对象覆盖了 finalize),我们可以在 finalize 中去拯救。
    代码演示:

    /**
     * @author macfmc
     * @date 2020/7/31-22:39
     */
    public class FinalizeGC {
        public static FinalizeGC instance = null;
        public void isAlive() {
            System.out.println("I am still alive!");
        }
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("finalize method executed");
            FinalizeGC.instance = this;
        }
        
        public static void main(String[] args) throws Throwable {
            instance = new FinalizeGC();
    
            //对象进行第1次GC
            instance = null;
            System.gc();
            Thread.sleep(1000);//Finalizer 方法优先级很低,需要等待
            if (instance != null) {
                instance.isAlive();
            } else {
                System.out.println("I am dead! ");
            }
    
            //对象进行第2次GC
            instance = null;
            System.gc();
            Thread.sleep(1000);
            if (instance != null) {
                instance.isAlive();
            } else {
                System.out.println("I am dead! ");
            }
        }
    }

    运行结果:

    finalize method executed
    I am still alive!
    I am dead! 

    可以看到,对象可以被拯救一次(finalize  执行第一次,但是不会执行第二次)
    代码改一下,再来一次。

    /**
     * @author macfmc
     * @date 2020/7/31-22:39
     */
    public class FinalizeGC {
        public static FinalizeGC instance = null;
        public void isAlive() {
            System.out.println("I am still alive!");
        }
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("finalize method executed");
            FinalizeGC.instance = this;
        }
        
        public static void main(String[] args) throws Throwable {
            instance = new FinalizeGC();
            //对象进行第1次GC
            instance = null;
            System.gc();
            // Thread.sleep(1000);//Finalizer 方法优先级很低,需要等待
            if (instance != null) 
                instance.isAlive();
            else 
                System.out.println("I am dead! ");
            //对象进行第2次GC
            instance = null;
            System.gc();
            // Thread.sleep(1000);
            if (instance != null) {
                instance.isAlive();
            } else {
                System.out.println("I am dead! ");
            }
        }
    }

    运行结果:

    finalize method executed
    I am dead! 
    I am dead! 

    对象没有被拯救,这个就是 finalize()方法执行缓慢,还没有完成拯救,垃圾回收器就已经回收掉了。
    所以建议大家尽量不要使用 finalize, 因为这个方法太不可靠 。 在生产中你很难控制方法的执行或者对象的调用顺序 , 建议大家忘了 finalize  方法 ! 因为在finalize  方法能做的工作,java中有更好的,比如 try-finally或者其他方式可以做得更好

    各种引用

    强引用

    一般的 Object obj = new Object() ,就属于强引用。在任何情况下,只有有强引用关联(与根可达)还在,垃圾回收器就永远不会回收掉被引用的对象。

    软引用 SoftReference

    一些有用但是并非必需,用软引用关联的对象,系统将要发生内存溢出(OuyOfMemory)之前,这些对象就会被回收(如果这次回收后还是没有足够的空间,才会抛出内存溢出)。参见代码:VM 参数 -Xms10m -Xmx10m -XX:+PrintGC
    代码演示:

    import java.lang.ref.SoftReference;
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * @author macfmc
     * @date 2020/7/31-22:56
     */
    public class gcRoot {
        public static void main(String[] args) {
            gcRoot u = new gcRoot(); //new是强引用
            SoftReference<gcRoot> userSoft = new SoftReference<>(u);
            u = null;//干掉强引用,确保这个实例只有userSoft的软引用
            System.out.println(userSoft.get());
            System.gc();//进行- -次GC垃圾回收
            System.out.println("After gc");
            System.out.println(userSoft.get());
            //往堆中填充数据,导致00M
            List<byte[]> list = new LinkedList<>();
            try {
                for (int i = 0; i < 100; ++i) {
                    System.out.println("*************" + userSoft.get());
                    list.add(new byte[1024 * 1024 * 1]); //1M的对象
                }
            } catch (Throwable e) {
                //抛出了00M异常时打印软引用对象
                System.out.println("Excepti n*************" + userSoft.get());
            }
        }
    }
    

    运行结果:

    main.java.JVM.gcRoot@4554617c
    [GC (System.gc())  3341K->856K(125952K), 0.0043645 secs]
    [Full GC (System.gc())  856K->662K(125952K), 0.0074266 secs]
    After gc
    main.java.JVM.gcRoot@4554617c
    *************main.java.JVM.gcRoot@4554617c
    *************main.java.JVM.gcRoot@4554617c
    [GC (Allocation Failure)  33072K->32502K(125952K), 0.0102263 secs]
    *************main.java.JVM.gcRoot@4554617c
    *************main.java.JVM.gcRoot@4554617c
    [GC (Allocation Failure)  64906K->64247K(125952K), 0.0123316 secs]
    [Full GC (Ergonomics)  64247K->64149K(194560K), 0.0198417 secs]

    例如,一个程序用来处理用户提供的图片。如果将所有图片读入内存,这样虽然可以很快的打开图片,但内存空间使用巨大,一些使用较少的图片浪费内存空间,需要手动从内存中移除。如果每次打开图片都从磁盘文件中读取到内存再显示出来,虽然内存占用较少,但一些经常使用的图片每次打开都要访问磁盘,代价巨大。这个时候就可以用软引用构建缓存。

    弱引用 WeakReference

    一些有用(程度比软引用更低)但是并非必需,用弱引用关联的对象,只能生存到下一次垃圾回收之前,GC 发生时,不管内存够不够,都会被回收。
    代码演示:

    import java.lang.ref.WeakReference;
    
    /**
     * @author macfmc
     * @date 2020/7/31-22:56
     */
    public class gcRoot {
        public static void main(String[] args) {
            gcRoot u = new gcRoot();
            WeakReference<gcRoot> userWeak = new WeakReference<>(u);
            u = null;//干掉强引用,确保这个实例只有userWeak的弱引用
            System.out.println(userWeak.get());
            System.gc();//进行一次GC垃圾回收
            System.out.println("After gc");
            System.out.println(userWeak.get());
        }
    }
    

    运行结果:

    main.java.JVM.gcRoot@4554617c
    [GC (System.gc())  3341K->752K(125952K), 0.0014105 secs]
    [Full GC (System.gc())  752K->625K(125952K), 0.0103507 secs]
    After gc
    null

    注意:软引用 SoftReference 和弱引用 WeakReference,可以用在内存资源紧张的情况下以及创建不是很重要的数据缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。实际运用(WeakHashMap、ThreadLocal)

    虚引用 PhantomReference

    幽灵引用,最弱(随时会被回收掉)垃圾回收的时候收到一个通知,就是为了监控垃圾回收器是否正常工作

    展开全文
  • 基础一:一切都是对象

    千次阅读 2016-05-07 22:25:29
    概述OOP-面向对象编程(Object Oriented Programming),在Java中(几乎)一切都是对象。用引用操作对象在Java中一切都是被看作为对象,因此可以采用单一固定的语法。尽管一切都看做对象,但操作的标示符实际上仅仅是...

    概述

    OOP-面向对象编程(Object Oriented Programming),在Java中(几乎)一切都是对象。


    用引用操作对象

    在Java中一切都是被看作为对象,因此可以采用单一固定的语法。

    尽管一切都看做对象,但操作的标示符实际上仅仅是对象的一个“引用”(reference)。

    如果想操作一个字符串,则可以创建一个String 引用:

    String s ;

    但是这里创建的仅仅是引用,而不是对象。因此如果要操作s,这会返回错误。这是因为s实际上并没有与任何对象关联.

    错误如下所示:

    这里写图片描述

    因此一种安全的做法就是:创建一个引用的同时便进行初始化

    String s = "abc";

    必须由你创建所有对象

    一旦创建了一个引用,就希望它能与一个新的对象相关联。

    通常使用new操作符来实现这一目的。比如

    String s = new String("abc");

    存储到什么位置

    程序运行时,对象是怎样放置安排的呢? 特别是内存是怎样分配的呢?

    其实有五个不同的地方可以存储数据:

    名称 说明
    寄存器 最快的存储区,位于存储器内部,但是寄存器的数量极其有限,按需分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。
    堆栈 位于通用RAM(随机访问存储器)中,但通过堆栈指针可以从处理器那里获得直接支持。堆栈指针向下移动—>分配新的内存,向上移动—>释放内存。这种分配存储的方法效率仅次于寄存器。 一般来讲,引用存放在堆栈中,Java对象并不存储在其中。
    一种通用的内存池(也位于RAM中),用于存放所有的Java对象。堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存活多长时间。 因此在堆里分配存储有很大的灵活性。这种灵活性必须付出相应的代价:比用堆栈进行分配存储需要更多的时间
    常量存储 常量值通常存放在程序代码内部,永远不会被改变。
    非RAM存储 数据完全存活于程序之外,比如 流对象 和 持久化对象。

    特例:基本类型

    在程序设计中常用到一系列类型,他们需要特殊对待,可以把他们想象成基本类型。

    之所以特殊对待,是因为new将对象存储在“堆”中,故用new创建一个对象–特别是小的、简单的变量,往往不是很有效。

    因此,Java采用了和C,C++相同的方法,也就是不用new创建,而是创建一个并非是引用的“自动“变量。 这个变量直接存储”值“,并置于堆栈中,因此更高效。

    Java要确定每种基本类型所占存储控件的大小,它们的大小并不像其他语言那样随着机器硬件架构的变化而变化。

    基本类型 大小 最小值 最大值 包装类型
    boolean Boolean
    char 16-bit Unicode 0 Unicode 2^16-1 Character
    byte 8-bits -128 +127 Byte
    short 16-bits -2^15 +2^15-1 Short
    int 32-bits -2^31 +2^31-1 Integer
    long 64-bits -2^63 +2^63-1 Long
    float 32-bits IEEE754 IEEE754 Float
    double 64-bits IEEE754 IEEE754 Double
    void Void

    基本类型具有的包装器类,使得可以在堆中创建一个非基本的对象,用来表示对应的基本类型。比如:

    char c = 'x';
    Character ch = new Character(c);
    或者:
    Character ch  = new Character('x');

    Java SE5的自动包装功能自动的将基本类型转换为包装器类型

    Character ch = 'x';

    并可以反向转换:

    char c = ch;

    高精度数字:

    Java提供了2个高精度计算的类

    BigInteger BigDecimal.

    虽然他们大体上属于包装器类的范畴,但是却没有对应的基本类型。

    • 这两个类都有自己的一系列方法,类似于我们针对主类型执行的操作,也就是说能用 int 或float 做的事情,用BigInteger和BigDecimal 一样可以做,只是必须换用方法调用,而不是使用运算符。此外由于牵涉更多,所以运算速度会慢一点总之我们牺牲了速度,但换来了精度。

    • BigInteger支持任意精度的整数,也就是说我们可精确表示任意大小的整数值;同时在运算过程中不会丢失任何信息;

    • 在BigInteger类中有所有的基本算术运算方法,如加、减、乘、除,以及可能会用到的位运算如或、异或、非、左移、右移等。

    • 从数值上比较两个 BigDecimal 值时,应该使用 compareTo() 而不是 equals()。

    • 要小心使用 BigDecimal(double) 构造函数, 因为如果不了解它,会在计算过程中产生舍入误差。请使用基于整数或 String 的构造函数。

    • 由于 BigDecimal 对象是不可变的,这些方法中的每一个都会产生新的 BigDecimal 对象。因此,因为创建对象的开销,BigDecimal 不适合于大量的数学计算,但设计它的目的是用来精确地表示小数。如果您正在寻找一种能精确表示如货币量这样的数值,则 BigDecimal 可以很好地胜任该任务。


    永远不需要销毁对象

    作用域

    作用域由花括号的位置决定。

    
            {
                // x available
                int x = 12 ;
                System.out.println("x:" + x );
                {
                    // Both x & y available
                    int y = 9 ;
                    System.out.println("x:" + x + ",y:" + y);
                }
                // only x available
                System.out.println("x:" + x );
            }

    在作用域中定义的变量只可用于作用域结束之前。

    对象的作用域

    Java对象不具备和基本类型一样的生命周期。
    当使用new创建一个java对象时,它可以存活于作用域之外。
    比如

    {
        String s = new String("sss");
    }// end of scope

    引用s在作用域终点就消失了,但是s指向的对象任然占据这内存空间。
    我们无法在这个作用域之后访问这个对象,因为对它唯一的引用已经超出了作用域的范围。

    事实证明,由new创建的对象,只要你需要就会一直保留下去。

    那java如何防止这些对象填满内存空间,进而阻塞你的程序呢?

    Java中有一个垃圾回收器,用来监视用new创建的所有对象,并辨别那些不会被再引用的对象。随后,释放这些对象占用的内存空间,以便提供给其他新的对象使用。


    创建新的数据类型:类

    如果一切都是对象,那什么决定了某一类对象的外观和行为呢? 换句话说 什么确定了对象的类型?

    在Java中使用class这个关键字

    class ATypeName {
        // class body gose here 
    }

    实例化

    ATypeName  a = new ATypeName();

    字段和方法

    一旦定义了一个类,就可以在类中设置两种类型的元素:字段+方法。

    字段可以是任何类型的对象,可以通过起引用与其进行通信;也可以是基本类型中的一种。

    如果字段是某个对象的引用,那么必须初始化该引用,以便使其与一个实际的对象相关联。

    每个对象都有用来存储其字段的空间; 普通字段不能在对象间共享。

        class  DataOnly{
                int i ;
                double d ;
                boolean b ;
            }

    尽管这个类除了存储数据之外什么也不能做,但是依然可以创建它的一个对象:

    DataOnly data = new DataOnly();

    可以给字段赋值,但首先必须知道如何引用一个对象的成员:
    objectReference.member

    例如:

    data.i = 10;
    data.d = 1.1;
    data.b = false;

    基本成员默认值

    若类的某个成员是基本类型数据,即使没有初始化,Java会确保它有一个默认值。

    当变量作为类的成员变量使用时,Java才确保给定其默认值,以确保那些是基本类型的成员变量得到初始化,防止产生程序错误。

    不过最好明确的对变量进行初始化。

    上述确保初始化的方法并不适用于“局部变量”(即并非某个类的字段)。
    如下:

    某个方法中定义

    int x ;

    这是不会被自动化初始为0 ,如果未明确的赋值,在编译时会抛出异常

    这里写图片描述

    方法、参数和返回值

    Java的方法决定了一个对象能接收什么样的的消息。 参数列表给出了要传给方法的信息的类型和名称。 方法名和参数列表唯一的标识出某个方法。

    Java中的方法只能作为类的一部分创建。 方法只能通过对象才能被调用(static方法是针对类调用的,并不依赖于对象的存在),且这个对象必须能执行这个方法的调用。

    如下:

    objectName.methodName(arg1,arg2,arg3);

    参数列表

    像java中任何传递对象的场合一样,这里传递的实际上也是引用(对于基本类型是一个类外。通常尽管传递的是对象,而实际上传递的对象的引用。)

    并且引用的类型必须正确。

    比如 参数类型为String ,则必须传递一个String对象,否则编译器抛出异常。

    假设某个方法接收String为其参数,具体定义如下,该方法必须置于某个类的定义内才能被正确的编译。

    int storage(String s){
        return s.length();
    }

    通过上面的例子,我们还可以了解到return关键字的用法。

    • 代表已经执行完毕,离开此方法
    • 如果该方法有返回值,需要放到return后
    boolean flag(){ return true;}
    double naturalLogBase(){ return 2.879;}
    
    void nothing(){return ;}
    void nothing2(){}

    若返回类型是void ,return关键字的作用只是用来退出方法。因此没有必要到方法结束时才离开,可以在任何地方离开。

    但如果返回类型不是void ,无论在何处返回,编译器都会强制返回一个正确类型的返回值。


    static关键字

    通常来说,当创建一个类时,就是在描述那个类的对象的外观与行为。

    除非用new创建那个类的对象,否则,实际上并没有获取到任何对象, 执行new 来创建对象时,数据存储空间才能被分配,其方法才能被外界调用。

    有两种情况是以上方法无法解决的:
    1. 只想为某特定域分配单一存储空间,而不考虑究竟要创建多少对象,甚至根本就不创建对象。
    2. 第二种情况是:希望某个方法不与包含它的类的任何对象关联在一起,也就是说,即使没有创建对象,也能够调用该方法。

    通过static关键字可以满足这两方面的需求。

    当声明一个事物是static时,也就意味着这个域或者方法不会与包含它的那个类的任何对象实例关联子啊一起, 所以即使从未创建某个类的任何对象,也可以通过调用其static方法或者访问其static域。

    通常你必须创建一个对象,并且用它来访问数据或者方法,因为非static域和方法必须知道他们一起运作的特定对象。

    只需要将static关键字放在定义之前,就可以将字段或者方法设定为static

    例如

    class StaticTest{
        static int i = 5;
    }

    即使我们创立了两个StaticTest对象,StaticTest.i也只有一份存储空间,这两个对象共享同一个i.

    这里写图片描述

    在这里 a.i和b.i都指向同一个存储空间。

    引用static变量的两种方法:

    1. 可以通过一个对象去引用它,比如 上面中的 a.i
    2. 也可以通过类名直接引用,而这对于非静态成员则不行,比如Demo21.i == Demo21.i 这种方式是首选方式,推荐使用这种方式。

    类似逻辑同样也适用于 静态方法。

    展开全文
  • 对于那些在创建事件时没有指定事件名的事件内核对象,可以通过使用内核对象的继承性或是调用DuplicateHandle()函数来调用CreateEvent()以获得对指定事件对象的访问权。在获取到访问权后所进行的同步操作与在同一...
  • 什么是Java的对象引用? Java中都有哪些类型的对象引用? Java中提供的Java对象引用主要有什么目的? 通过本文,你就能很清楚得了解Java的对象引用
  • 本系列文章导航从零开始学习jQuery (一) 开天辟地入门篇从零开始学习jQuery (二) 万能的选择器从零开始学习jQuery (三) 管理jQuery包装集...事件事件对象从零开始学习jQuery (六) jQuery中的Ajax从零开始学习jQuery
  • 什么是拖放?拖放是一种常见的特性,即抓取对象以后拖到另一个位置。 为了让元素可拖动,需要使用 HTML5 draggable 属性(设置为draggable=true)。(链接和图片默认是可拖动的,不需要 draggable 属性。) 在拖动...
  • “我到底可以用Python做什么?” 这是个棘手的问题,因为Python有很多用途。但是随着时间的推移,我发现Python主要可用于一下三个方面: Web开发 数据科学——包括机器学习、数据分析和数据可视化 脚本编写 ...
  • Kubernetes对象模型

    万次阅读 2018-10-13 20:44:20
    Kubernetes对象 在之前的文章已经讲到了很多Kubernets对象,包括pod,service,deployment等等。Kubernets对象是一种持久化,表示集群状态的实体。它是一种声明式的意图的记录,一般使用yaml文件描述对象,它使用...
  • 深入JVM对象引用

    万次阅读 多人点赞 2015-11-17 12:19:31
    在jdk 1.2以前,创建的对象只有处在可触及(reachaable)状态下,才能被程序所以使用,垃圾回收器一旦发现无用对象,便会对其进行回收。但是,在某些情况下,我们希望有些对象不需要立刻回收或者说从全局的角度来说...
  • 面向对象基本概念

    万次阅读 多人点赞 2019-02-06 21:56:15
    面向对象就是:把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象。对同类对象抽象出其共性,形成类。类中的大多数数据,只能用本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象...
  • 浅谈一下JAVA对象对象引用以及对象赋值

    万次阅读 多人点赞 2013-09-19 00:50:29
    今天有班级同学问起JAVA对象的引用是什么。正好趁着这次机会,自己总结一下JAVA对象对象引用以及对象赋值。自己总结了所看到的网上相关方面的不少帖子,整理汇总形成下面的文章。   Java对象及其引用  初学Java...
  • 介绍 在前面文章中提到的策略模式提到过,需要客户自行选择类并创建实例,那么这个选择的过程放在哪里呢...工厂类是指包含了一个专门用来创建其他对象的方法的类。所谓按需分配,传入参数进行选择,返回具体的类。工厂
  • R语言面向对象指南

    千次阅读 2015-09-21 21:53:00
    面向对象指南:这一章主要介绍怎样识别和使用 R 语言的面向对象系统(以下简称 OO)。R 语言主要有三种 OO 系统(加上基本类型)。本指南的目的不是让你精通 R 语言的 OO,而是让你熟悉各种系统,并且能够准确地区分...
  • JVM对象引用与内存分配策略

    千次阅读 2016-05-31 20:46:41
    前两天对《深入理解虚拟机》一书做了个总结:《JVM理解...即就算两个对象相互引用,只要这两个对象没有引用链连接GC Roots,这两个对象都会被判定为可回收的对象!注意,这里是指被判定位可回收的对象,并不是说他们
  • (1)控制反转是应用于软件工程领域的,在运行时被装配器对象用来绑定耦合对象的一种编程技巧,对象之间的耦合关系在编译时通常是未知的。在传统的编程方式中,业务逻辑的流程是由应用程序中早已被设定好关联关系的...
  • 了解并发编程中对象的共享

    万次阅读 2019-12-30 11:59:56
    我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且希望确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。如果没有同步,那么这种情况就无法实现,所以我们可以通过显示...
  • 搞技术的囫囵吞枣可是大忌,所以决定各个击破。 以下是摘自blogjava中的一篇文章,感觉这篇文章把持久化和ORM技术分析的非常透彻,放在博客中供大家参阅!原文地址:http://www.blogjava.net/fyz210/archi
  • 对象的访问方式是由虚拟机的实现而决定的,主流的访问方式主要有两种:通过句柄 和 使用指针 通过句柄访问 使用句柄访问,Java堆中将可能会划分出一块内存用来作为句柄池,reference中寸的就是对象的句柄地址,而...
  • 目前面向对象的语言有很多,Objective-C中的对象又和其他语言中的对象什么区别呢?下面来简单介绍Objective-C中对象的实现。 1、Objective-C中的类 谁都知道,所有的对象都是由其对应的类实例化而来,殊不知类本身...
  • Android事件分发机制

    千次阅读 2017-07-07 11:18:46
    在android开发中会经常遇到滑动冲突(比如ScrollView或是SliddingMenu与ListView的嵌套)的问题,需要...1.涉及到事件响应的常用方法构成 用户在手指与屏幕接触过程中通过MotionEvent对象产生一系列事件,它有四种状态
  • 今天看别人的博客,讲到面试相关的问题,其中有一个知识点是:synchronized关键字,Java对象头、Markword概念、synchronized底层实现,monitorenter和monitorexit指令,一脸蒙逼,虽然早期把《深入理解Java虚拟机》...
  • Windows 内核对象

    千次阅读 2011-12-15 13:56:41
    2. 内核对象固定成员变量:每个内核对象至少有一个引用计数(用来决定是否释放内核对象,与com引用计数类似)和一个安全属性对象(用来决定访问权限等)成员变量。 3. keneral object 与handle 区
  • RxJS的秘密 Observable 可观察对象

    千次阅读 2017-07-26 11:39:41
    前言最近因为Angular2了解到了RxJS这个东西,总的来说,RxJS的出现也是为了解决异步回调的,它针对于事件序列。RxJS是一个通过使用可观察序列来构建异步和基于事件的程序的库。它提供了一个核心类型:Observable、...
  • *如果数据窗口的DoubleClicked事件中编写了脚本,那么在数据窗口的C1icked事件中的脚本应该尽量短,否则双击事件中的脚本永远不可能得到执行。一般来说,Cliked中脚本的执行时间和两次击鼠标之间的间隔时间之和,不...
  • Python能用来什么?这3大主要用途你一定要知道!

    万次阅读 多人点赞 2018-07-11 08:57:50
    导读:如果你想学Python,或者你刚开始学习Python,那么你可能会问:“我能用Python做什么?”这个问题不好回答,因为Python有很多用途。但是随着时间,我发现有Python主要有以下三大主要应用:Web开发数据科学:...
  • 重新认识java(一) ---- 万物皆对象

    万次阅读 多人点赞 2016-11-27 21:39:37
    如果你现实中没有对象,至少你在java世界里会有茫茫多的对象,听起来是不是很激动呢?
  • ECMA-262把对象定义为:”无需属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,这就相当于说明对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。正因为这样...
  • Qt事件用来描述程序内部或外部发生的动作 任意的QObject对象都具备事件处理的能力 Qt常见的事件继承图如下: QInputEvent:用户输入事件 QDropEvent:用户拖放事件 QPaintEvent:描述操作系统绘制GUI动作的事件...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 493,672
精华内容 197,468
关键字:

对象的事件是用来决定什么的